> ## Documentation Index
> Fetch the complete documentation index at: https://docs.37audits.com/llms.txt
> Use this file to discover all available pages before exploring further.

# MCP Server Auditor

> Probes a live MCP endpoint, negotiates its real protocol version, and audits OAuth 2.0 compliance when the server requires authorization

> [!NOTE]
>
> **AI Readiness Auditor** — resolves the real MCP endpoint from the audited URL (directly, or via a published Server Card/AI catalog), negotiates the protocol version it actually runs, and validates the handshake or (for OAuth-gated servers) its authorization challenge and discovery metadata.

> **AI Readiness**
>
> Confirms the site's MCP server speaks a known protocol date so AI clients can connect.

> **Security**
>
> For OAuth-gated servers, validates the 401 challenge and RFC 9728/RFC 8414 discovery metadata.

This auditor talks to the live [Model Context Protocol](https://modelcontextprotocol.io/) server. It does **not** re-audit Server Card document structure — that belongs to the [MCP Server Card Auditor](https://docs.37audits.com/auditors/mcp-server-card-auditor.md). Rule `301` is the only card cross-check: advertised `remotes[].supportedProtocolVersions` versus the live handshake.

Real remote MCP servers are almost always gated by OAuth 2.0. This auditor has no way to obtain a bearer token, so when a candidate endpoint returns `401 Unauthorized` it audits the compliance of the challenge and discovery metadata instead of treating the server as unreachable.

## How it works

Users most often give a project's main domain rather than the exact MCP endpoint path, so the auditor builds a list of candidate endpoints before probing:

1. **Discover endpoints** — uses `/.well-known/ai-catalog.json` as a link source for Server Card remotes, then `/.well-known/mcp/server-card.json` and `/mcp/server-card`. Reads the official `remotes[].url` array when present, and falls back to a top-level `url` field for real-world cards that predate or diverge from that schema (e.g. Resend's). Always adds the given URL itself and `{origin}/mcp` as fallbacks, so an exact endpoint URL (e.g. `.../v2/mcp`) still works even when no card is published.
2. **Negotiate** — for each candidate in turn, `POST server/discover` at the newest known protocol date (`2026-07-28`), with the `MCP-Protocol-Version`/`Mcp-Method` headers and `_meta` fields the live spec requires. On a `400 UnsupportedProtocolVersionError`, retries once with the newest mutually-known date from the server's advertised `supported` list — this is the actual protocol-version negotiation the server runs, not an assumption.
3. **Legacy fallback** — if `server/discover` is not implemented (HTTP 404/405, an unrecognized 400 body, or a `-32601 Method not found` error), falls back to a legacy `initialize` handshake at `protocolVersion` `2025-11-25`. Accepts `application/json` or a single `text/event-stream` event.
4. **OAuth challenge** — a `401` response from any candidate stops the search and short-circuits into OAuth compliance evaluation instead of the handshake path: does `WWW-Authenticate` carry a compliant `Bearer resource_metadata` challenge (RFC 9728 §5.1), does that Protected Resource Metadata document validate, does its authorization server publish valid metadata (RFC 8414, falling back to OIDC discovery) over HTTPS, and does that metadata advertise Dynamic Client Registration (`registration_endpoint`, RFC 7591) and PKCE `S256` so clients can actually connect. A candidate that fails outright (no response, non-2xx/non-401 status) is skipped in favor of the next candidate; when none produced a handshake or a `401`, a candidate that answered `403`/`407` is reported as a blocked gateway (`403`), and the two `/.well-known/oauth-*` documents are probed directly at the origin so one blocked independently of the endpoint is still caught (`330`).
5. **Dispatch** — a successful discover result is audited by the `2026-07-28` class. An initialize result is audited by the class for the negotiated `protocolVersion`. Unknown dates fail `402` and skip definition rules.
6. **Aggregate** — `ERROR` (`500`) wins, then auditor FAILs (`400`/`401`/`402`/`403` on the handshake path, `427`–`430` on the OAuth path), then that version's FAILs, then WARNINGs (including the OAuth `330`/`331`/`332`). If every rule passes, the audit is `SUCCESS` (`200`) — either a working handshake or a spec-compliant OAuth challenge.
7. **Specification pins** — every run emits `37A-McpServerAuditor-100` through `104` (one per MCP protocol date) and `105` (MCP Authorization). Those checks do not change overall status.

## What it audits

Every row maps to one `checkRule*` method and one `CheckCode`. Definition rules (`410`–`426`, `310`–`321`) run only for the negotiated protocol date, on the handshake path. OAuth rules (`427`–`430`, plus the `331`/`332` warnings) run on the `401` challenge path. `403` (blocked gateway) and `330` (blocked origin discovery document) run on the no-handshake/no-`401` path. Pins `100`–`105` are emitted on every run, including fetch errors. Skip `301` when no Server Card was found. CheckCode suffixes use status ranges: `100`–`199` information, `200` success, `300`–`399` warning, `400`–`499` fail, `500`–`599` error.

| CheckCode | Status | When it fires | Recommendation |
| - | - | - | - |
| `37A-McpServerAuditor-100` | `INFORMATION` | Every run. Records that this auditor implements MCP Protocol 2024-11-05. | — |
| `37A-McpServerAuditor-101` | `INFORMATION` | Every run. Records that this auditor implements MCP Protocol 2025-03-26. | — |
| `37A-McpServerAuditor-102` | `INFORMATION` | Every run. Records that this auditor implements MCP Protocol 2025-06-18. | — |
| `37A-McpServerAuditor-103` | `INFORMATION` | Every run. Records that this auditor implements MCP Protocol 2025-11-25. | — |
| `37A-McpServerAuditor-104` | `INFORMATION` | Every run. Records that this auditor implements MCP Protocol 2026-07-28. | — |
| `37A-McpServerAuditor-105` | `INFORMATION` | Every run. Records that this auditor implements MCP Authorization (RFC 9728, RFC 8414). | — |
| `37A-McpServerAuditor-400` | `FAIL` | No candidate endpoint (Server Card/catalog remotes, the given URL, `{origin}/mcp`) ever returned a usable JSON-RPC 2.0 result or even an HTTP 2xx status. | Expose an MCP endpoint at one of `{attemptedEndpoints}`, or list remotes\[].url on a Server Card at `{origin}/.well-known/mcp/server-card.json`; last response was `{observed}`. |
| `37A-McpServerAuditor-401` | `FAIL` | A candidate answered HTTP 2xx but the body is not a JSON-RPC 2.0 response with a `result`. | Return a JSON-RPC 2.0 object with jsonrpc="2.0" and a result from `{endpoint}`; last response was `{observedSnippet}`. |
| `37A-McpServerAuditor-402` | `FAIL` | The negotiated `protocolVersion` (or every `supportedVersions` entry) is not one of the five known dates. | Advertise a known MCP protocol version from `{endpoint}`; replace `{version}` with 2026-07-28 or another supported date. |
| `37A-McpServerAuditor-403` | `FAIL` | A candidate answered an unauthenticated request with HTTP `403`/`407` instead of a `401` OAuth challenge — usually a proxy, API gateway, or WAF in front blocking the request before it reaches the server. | Return HTTP 401 with `WWW-Authenticate: Bearer resource_metadata="..."` from `{endpoint}`, or reconfigure the proxy/gateway/WAF so it forwards unauthenticated MCP requests instead of blocking them with `{observed}`. |
| `37A-McpServerAuditor-410` | `FAIL` | `InitializeResult` for MCP 2024-11-05 is missing `protocolVersion`, `capabilities`, or `serverInfo`. | Add `{missingFields}` to the initialize result at `{endpoint}` (MCP 2024-11-05 requires protocolVersion, capabilities, serverInfo). |
| `37A-McpServerAuditor-411` | `FAIL` | `serverInfo` is present but `name` or `version` is missing (MCP 2024-11-05). | Set serverInfo.name and serverInfo.version on `{endpoint}` (MCP 2024-11-05 Implementation requires both). Observed serverInfo is `{serverInfo}`. |
| `37A-McpServerAuditor-412` | `FAIL` | `protocolVersion` is present and is not `2024-11-05`. | Set protocolVersion to "2024-11-05" on `{endpoint}`; observed value is `{observedVersion}`. |
| `37A-McpServerAuditor-413` | `FAIL` | `InitializeResult` for MCP 2025-03-26 is missing required fields. | Add `{missingFields}` to the initialize result at `{endpoint}` (MCP 2025-03-26 requires protocolVersion, capabilities, serverInfo). |
| `37A-McpServerAuditor-414` | `FAIL` | `serverInfo` is present but `name` or `version` is missing (MCP 2025-03-26). | Set serverInfo.name and serverInfo.version on `{endpoint}` (MCP 2025-03-26 Implementation requires both). Observed serverInfo is `{serverInfo}`. |
| `37A-McpServerAuditor-415` | `FAIL` | `protocolVersion` is present and is not `2025-03-26`. | Set protocolVersion to "2025-03-26" on `{endpoint}`; observed value is `{observedVersion}`. |
| `37A-McpServerAuditor-416` | `FAIL` | `InitializeResult` for MCP 2025-06-18 is missing required fields. | Add `{missingFields}` to the initialize result at `{endpoint}` (MCP 2025-06-18 requires protocolVersion, capabilities, serverInfo). |
| `37A-McpServerAuditor-417` | `FAIL` | `serverInfo` is present but `name` or `version` is missing (MCP 2025-06-18). | Set serverInfo.name and serverInfo.version on `{endpoint}` (MCP 2025-06-18 Implementation requires both). Observed serverInfo is `{serverInfo}`. |
| `37A-McpServerAuditor-418` | `FAIL` | `protocolVersion` is present and is not `2025-06-18`. | Set protocolVersion to "2025-06-18" on `{endpoint}`; observed value is `{observedVersion}`. |
| `37A-McpServerAuditor-419` | `FAIL` | `InitializeResult` for MCP 2025-11-25 is missing required fields. | Add `{missingFields}` to the initialize result at `{endpoint}` (MCP 2025-11-25 requires protocolVersion, capabilities, serverInfo). |
| `37A-McpServerAuditor-420` | `FAIL` | `serverInfo` is present but `name` or `version` is missing (MCP 2025-11-25). | Set serverInfo.name and serverInfo.version on `{endpoint}` (MCP 2025-11-25 Implementation requires both). Observed serverInfo is `{serverInfo}`. |
| `37A-McpServerAuditor-421` | `FAIL` | `protocolVersion` is present and is not `2025-11-25`. | Set protocolVersion to "2025-11-25" on `{endpoint}`; observed value is `{observedVersion}`. |
| `37A-McpServerAuditor-426` | `FAIL` | `serverInfo.icons` is present and at least one icon is missing `src`. | Set src on every icon in serverInfo.icons at `{endpoint}` (MCP 2025-11-25 Icon requires src). Observed icons are `{icons}`. |
| `37A-McpServerAuditor-422` | `FAIL` | `DiscoverResult` is missing `cacheScope`, `capabilities`, `resultType`, `supportedVersions`, or `ttlMs`. | Add `{missingFields}` to the server/discover result at `{endpoint}` (MCP 2026-07-28 requires cacheScope, capabilities, resultType, supportedVersions, ttlMs). |
| `37A-McpServerAuditor-423` | `FAIL` | `supportedVersions` is present and is an empty array. | Put at least one MCP protocol date in supportedVersions on `{endpoint}`; observed value is \[]. |
| `37A-McpServerAuditor-424` | `FAIL` | `cacheScope` is present and is not `public` or `private`. | Set cacheScope to "public" or "private" on `{endpoint}`; observed value is `{observedScope}`. |
| `37A-McpServerAuditor-425` | `FAIL` | `ttlMs` is present and is not a non-negative integer. | Set ttlMs to a non-negative integer (milliseconds) on `{endpoint}`; observed value is `{observedTtl}`. |
| `37A-McpServerAuditor-427` | `FAIL` | The endpoint returned HTTP 401 without a compliant `WWW-Authenticate` challenge (missing header, missing `Bearer` scheme, or missing `resource_metadata`). | Return WWW-Authenticate: Bearer resource\_metadata="[https://.../.well-known/oauth-protected-resource](https://.../.well-known/oauth-protected-resource)" on 401 responses from `{endpoint}`; observed header was `{observed}`. |
| `37A-McpServerAuditor-428` | `FAIL` | The `resource_metadata` URL is unreachable, not valid JSON, or missing `resource`/a non-empty `authorization_servers` array. | Publish RFC 9728 Protected Resource Metadata at `{metadataUrl}` with "resource" and a non-empty "authorization\_servers" array; observed body is `{observedSnippet}`. |
| `37A-McpServerAuditor-429` | `FAIL` | Neither RFC 8414 Authorization Server Metadata nor OIDC Discovery for the first `authorization_servers` entry returns `authorization_endpoint` and `token_endpoint`. | Publish OAuth 2.0 Authorization Server Metadata (RFC 8414) or OIDC Discovery for `{authServerUrl}` with authorization\_endpoint and token\_endpoint; tried `{triedUrls}`. |
| `37A-McpServerAuditor-430` | `FAIL` | Any discovered OAuth URL (resource\_metadata, authorization server, its authorization\_endpoint or token\_endpoint) is `http://` instead of `https://`. | Serve `{insecureUrls}` over HTTPS; MCP Authorization (OAuth 2.1 §1.5) requires HTTPS for all authorization endpoints. |
| `37A-McpServerAuditor-300` | `WARNING` | Newest version the server speaks is not `2026-07-28`. | Upgrade `{endpoint}` from `{newestVersion}` to 2026-07-28 (server/discover). |
| `37A-McpServerAuditor-301` | `WARNING` | A Server Card was found and `remotes[].supportedProtocolVersions` disagree with the live handshake. | Update remotes\[].supportedProtocolVersions on the Server Card to `{liveVersions}` (card currently lists `{cardVersions}`). |
| `37A-McpServerAuditor-310` | `WARNING` | `InitializeResult` for MCP 2024-11-05 has no `instructions` text. | Add an instructions string to the initialize result at `{endpoint}` (MCP 2024-11-05). |
| `37A-McpServerAuditor-311` | `WARNING` | `capabilities` has keys outside the 2024-11-05 known set. | Remove or rename `{unknownKeys}` on `{endpoint}`, or negotiate a later protocol version. MCP 2024-11-05 known keys: `{knownKeys}`. |
| `37A-McpServerAuditor-312` | `WARNING` | `InitializeResult` for MCP 2025-03-26 has no `instructions` text. | Add an instructions string to the initialize result at `{endpoint}` (MCP 2025-03-26). |
| `37A-McpServerAuditor-313` | `WARNING` | `capabilities` has keys outside the 2025-03-26 known set. | Remove or rename `{unknownKeys}` on `{endpoint}`, or negotiate a later protocol version. MCP 2025-03-26 known keys: `{knownKeys}`. |
| `37A-McpServerAuditor-314` | `WARNING` | `InitializeResult` for MCP 2025-06-18 has no `instructions` text. | Add an instructions string to the initialize result at `{endpoint}` (MCP 2025-06-18). |
| `37A-McpServerAuditor-315` | `WARNING` | `capabilities` has keys outside the 2025-06-18 known set. | Remove or rename `{unknownKeys}` on `{endpoint}`, or negotiate a later protocol version. MCP 2025-06-18 known keys: `{knownKeys}`. |
| `37A-McpServerAuditor-316` | `WARNING` | `serverInfo` is present and `title` is absent or blank (MCP 2025-06-18). | Add serverInfo.title on `{endpoint}` (MCP 2025-06-18). Observed serverInfo is `{serverInfo}`. |
| `37A-McpServerAuditor-317` | `WARNING` | `InitializeResult` for MCP 2025-11-25 has no `instructions` text. | Add an instructions string to the initialize result at `{endpoint}` (MCP 2025-11-25). |
| `37A-McpServerAuditor-318` | `WARNING` | `capabilities` has keys outside the 2025-11-25 known set. | Remove or rename `{unknownKeys}` on `{endpoint}`, or negotiate a later protocol version. MCP 2025-11-25 known keys: `{knownKeys}`. |
| `37A-McpServerAuditor-319` | `WARNING` | `serverInfo` is missing recommended `title`, `description`, `icons`, or `websiteUrl` (MCP 2025-11-25). | Add `{missingFields}` to serverInfo on `{endpoint}` (MCP 2025-11-25). Observed serverInfo is `{serverInfo}`. |
| `37A-McpServerAuditor-320` | `WARNING` | `DiscoverResult` for MCP 2026-07-28 has no `instructions` text. | Add an instructions string to the server/discover result at `{endpoint}` (MCP 2026-07-28). |
| `37A-McpServerAuditor-321` | `WARNING` | `capabilities` has keys outside the 2026-07-28 known set. | Remove or rename `{unknownKeys}` on `{endpoint}`. MCP 2026-07-28 known keys: `{knownKeys}`. |
| `37A-McpServerAuditor-330` | `WARNING` | An OAuth discovery document at the origin (`/.well-known/oauth-protected-resource` or `/.well-known/oauth-authorization-server`), probed directly as a client would, is present but blocked (HTTP `403`/`407` or `5xx`) rather than reachable or a clean `404`. | Serve `{blockedUrls}`, or reconfigure the proxy/gateway so it forwards requests to the `/.well-known/oauth-*` paths instead of blocking them. |
| `37A-McpServerAuditor-331` | `WARNING` | The challenge and discovery chain are compliant, but the authorization server advertises no `registration_endpoint`, so clients that rely on OAuth 2.0 Dynamic Client Registration (RFC 7591) — such as Claude.ai — cannot obtain a `client_id` and fail to connect. | Publish a `registration_endpoint` and enable Dynamic Client Registration (RFC 7591) on the authorization server, or pre-register every client that connects to `{endpoint}`. |
| `37A-McpServerAuditor-332` | `WARNING` | The authorization server does not advertise `S256` in `code_challenge_methods_supported`; MCP Authorization mandates PKCE with S256 for public clients. | Advertise `"code_challenge_methods_supported": ["S256"]` in the authorization server metadata and enforce PKCE on `{endpoint}`. |
| `37A-McpServerAuditor-500` | `ERROR` | The URL is empty or invalid, or the probe threw before a verdict. | Retry the audit of `{url}`; verify the URL is reachable. Last error was `{error}`. |
| `37A-McpServerAuditor-200` | `SUCCESS` | No ERROR, FAIL, or WARNING findings, on either path. | Handshake path: `MCP endpoint "{endpoint}" speaks {versions} (newest {newestVersion}); handshake {handshakeMethod} succeeded.` OAuth path: `MCP endpoint "{url}" requires OAuth 2.0 authorization (RFC 9728); challenge and discovery metadata are spec-compliant.` |

## Output documentation

| Status | Description | Test logic |
| - | - | - |
| **INFORMATION** | Specification pins only; do not change overall status | Every run includes `100`–`104` naming MCP Protocol 2024-11-05 through 2026-07-28 and `105` naming MCP Authorization (RFC 9728, RFC 8414), with their schema/spec URLs. |
| **SUCCESS** | A known handshake succeeded, or the OAuth challenge and discovery metadata are compliant | Handshake: `server/discover` or `initialize` returned a JSON-RPC 2.0 result; the negotiated date is known; required handshake fields are present; no outdated-version warning (`300`). OAuth: the endpoint returned `401` with a compliant challenge, valid Protected Resource Metadata, valid Authorization Server Metadata served over HTTPS, advertising Dynamic Client Registration and PKCE S256. |
| **WARNING** | The server works but is outdated, incomplete, or disagrees with its card | Newest date is not 2026-07-28 (`300`); card `supportedProtocolVersions` ≠ live versions (`301`); missing `instructions` (`310`, `312`, `314`, `317`, `320`); unknown capability keys (`311`, `313`, `315`, `318`, `321`); missing `serverInfo.title` (`316`); incomplete recommended `serverInfo` (`319`); an origin OAuth discovery document is present but blocked (`330`); the compliant authorization server advertises no `registration_endpoint` for Dynamic Client Registration (`331`) or no PKCE `S256` (`332`). |
| **FAIL** | No usable MCP handshake and no compliant OAuth challenge, or the result violates that date's schema | No endpoint answered from any candidate (`400`); not JSON-RPC 2.0 with `result` (`401`); unknown protocol date (`402`); an unauthenticated request was blocked with `403`/`407` instead of a `401` challenge, typically by a proxy/gateway/WAF (`403`); missing initialize fields / identity / `protocolVersion` (`410`–`421`); invalid icons (`426`); missing or invalid discover fields (`422`–`425`); non-compliant 401 challenge (`427`); invalid Protected Resource Metadata (`428`); invalid Authorization Server Metadata (`429`); non-HTTPS OAuth URL (`430`). |
| **ERROR** | The request cannot be audited | Empty, blank, or invalid URL, or an unexpected exception before a verdict (`500`). |

## Risks and considerations

### AI clients cannot guess the handshake

* Without a reachable MCP endpoint, agents cannot list tools or complete a session against the site.
* A server that only speaks an unknown protocol date will be skipped by clients that implement the five dates this auditor pins.

### Card and runtime can drift

* A Server Card that advertises different `supportedProtocolVersions` than the live handshake steers clients toward a connection that will fail or negotiate down.
* Keep `remotes[].url` and protocol dates in the card aligned with the running server.

### Most remote servers require OAuth, and clients must discover it correctly

* MCP Authorization is optional per spec, but nearly every production remote MCP server (this auditor's own, Resend's, Atlassian's) enforces it via a `401` challenge.
* A `401` without a compliant `WWW-Authenticate: Bearer resource_metadata="..."` header leaves clients with no standard way to find where to authenticate.
* Protected Resource Metadata and Authorization Server Metadata that are unreachable, malformed, or served over plain HTTP block or downgrade every client's authorization flow, not just this auditor's.

### A proxy or gateway in front can block the handshake or discovery independently

* When the MCP endpoint sits behind a reverse proxy, API gateway, or WAF, that layer can reject unauthenticated requests with `403`/`407` instead of letting the server answer with a `401` challenge (`403`) — the endpoint looks dead to every client even though the server is healthy. This is exactly how a missing gateway-injected header took 37 Audits' own MCP offline: clients saw `403` and reported registration failures.
* The MCP endpoint and each `/.well-known/oauth-*` document are often separate gateway routes that can fail independently, so a discovery document can stay blocked (`330`) after the endpoint is fixed. This auditor probes those documents directly at the origin, the way a client does, rather than only following the `401` challenge header.
* A compliant challenge still is not enough to connect: clients such as Claude.ai rely on Dynamic Client Registration (`registration_endpoint`, `331`) and PKCE `S256` (`332`); a server that omits either leaves those clients unable to register or to complete the authorization code flow.

### Older initialize vs `server/discover`

* Protocol `2026-07-28` replaces `initialize` with `server/discover` and requires `cacheScope`, `ttlMs`, `resultType`, and `supportedVersions`.
* Staying on an initialize-only date (`300`) works for older clients but misses discover-based metadata and caching hints.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.