Skip to content

SEP-2742: Declaring Authentication Methods for Remote MCP Servers#2742

Open
tobinsouth wants to merge 2 commits into
modelcontextprotocol:mainfrom
tobinsouth:sep/auth-declaration-server-card
Open

SEP-2742: Declaring Authentication Methods for Remote MCP Servers#2742
tobinsouth wants to merge 2 commits into
modelcontextprotocol:mainfrom
tobinsouth:sep/auth-declaration-server-card

Conversation

@tobinsouth
Copy link
Copy Markdown

Summary

This SEP adds an auth block to the Remote shape shared by server.json and Server Cards (SEP-2127), so a remote MCP server can declare which authentication methods it supports — and what an OAuth client needs that RFC 8414 / RFC 9728 discovery cannot provide — before the first MCP request.

It declares:

  • Authentication posturenone / optional / required, so clients know whether to authenticate before initialize, lazily on 401, or not at all.
  • A method menu — the server can offer OAuth and/or static-header authentication, and the consumer picks one.
  • OAuth registration modedynamic (RFC 7591 DCR), client-metadata (CIMD I-D), or static (pre-registered, with the client ID either publisher-shared or consumer-supplied).
  • Client credential typenone, client_secret, private_key_jwt, tls_client_auth — so confidential clients are representable.
  • Out-of-band Authorization Server endpoints — for the meaningful fraction of real-world Authorization Servers that don't publish .well-known/oauth-authorization-server, with strict same-origin and validation constraints to prevent IdP Mix-Up.
  • Input.pattern / Input.maxLength — two small validation hints for configuration UIs, additive to the existing Input shape.

Everything that is discoverable from RFC 8414 / RFC 9728 (token endpoint auth method, scopes, signing algorithms, registration endpoints) is deliberately excluded — the auth block carries only facts no protocol channel can answer.

Why now

SEP-2127 (Server Cards) declares headers and URL templates but has no OAuth declaration of any kind. SEP-2633 (mcp.json) has an oauth object with three fields (clientId, scopes, redirectUri) — not enough to express confidential clients, CIMD registration, broken-discovery servers, or "register your own client." Without this gap closed, server publishers who want to be auto-configurable still need per-client README snippets, which is the problem SEP-2633 set out to solve.

The SEP also recommends a small set of corresponding additions to the mcp.json oauth object (registration mode, client credential, endpoints, posture, and a serverCard reference) — those will be raised as review feedback on #2633 separately.

Relationship to existing work

  • SEP-2127 (Server Cards) — this extends the Remote shape that Server Cards inherit from server.json.
  • SEP-2633 (mcp.json) — this defines the publisher-side declaration; mcp.json carries the resolved consumer-side configuration. Recommended mcp.json additions are listed in the SEP and will go to SEP-2633: Standard Client-Side Configuration Format - mcp.json #2633 as review comments.
  • MCP authorization spec — this is purely additive. The default (registration: "dynamic", posture: "required") matches the spec's recommended DCR + PKCE flow exactly. Servers that comply with the auth spec need declare nothing.

Looking for a sponsor

This sits at the intersection of the Server Card WG and the Auth IG. Tagging @dsp-ant, @SamMorrowDrums, and @tadasant for thoughts on whether this is the right shape and where it should live (this SEP, a follow-up to #2127, or an extension). Happy to rework the layering if the WG prefers a different split.

Adds an auth block to the Remote shape shared by server.json and Server
Cards (SEP-2127), declaring authentication posture, supported methods
(OAuth and static headers), OAuth registration mode, client credential
type, and out-of-band Authorization Server endpoints for servers without
RFC 8414 discovery. Also adds pattern and maxLength validation hints to
the existing Input shape.

Cross-references SEP-2633 (mcp.json) for the corresponding resolved
client-side fields.
@tobinsouth tobinsouth changed the title SEP: Declaring Authentication Methods for Remote MCP Servers SEP-2742: Declaring Authentication Methods for Remote MCP Servers May 19, 2026
Copy link
Copy Markdown
Contributor

@nbarbettini nbarbettini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I fully support fleshing out server cards and pinning down the relationship between server cards and mcp.json. Flagging some spec-related places where I was confused or I think there is ambiguity that needs to be sorted out.


1. **A server that supports OAuth has no way to say so in its Server Card.** The `Remote` shape in SEP-2127 (and the underlying `server.schema.json`) declares headers via `KeyValueInput` but has no field for OAuth at all. A registry, IDE, or marketplace ingesting a Server Card cannot tell whether the server expects an OAuth flow, an API key, or nothing.

2. **A server that supports both OAuth and a static API key can declare neither as a choice.** `headers[]` is a flat list. There is no way to say "use OAuth, or alternatively send an API key" — common for servers whose enterprise customers prefer key-based service accounts while individual users prefer OAuth.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

common for servers whose enterprise customers prefer key-based service accounts while individual users prefer OAuth

A flag here for discussion - is this a pattern we want to encourage? Authorization says HTTP servers SHOULD implement OAuth 2.1, and later says authorization servers MUST implement OAuth 2.1. From that, we can argue that preferring key-based service accounts is certainly a choice a server developer can make, but it is out of compliance with the spec.


2. **A server that supports both OAuth and a static API key can declare neither as a choice.** `headers[]` is a flat list. There is no way to say "use OAuth, or alternatively send an API key" — common for servers whose enterprise customers prefer key-based service accounts while individual users prefer OAuth.

3. **A confidential OAuth client is unrepresentable.** `mcp.json`'s `oauth` object has no field for a client secret, a private key, or a client certificate. Servers whose Authorization Servers require `client_secret_post`, `client_secret_basic`, `private_key_jwt` (RFC 7523), or `tls_client_auth` (RFC 8705) cannot be expressed. The configuration falls back to a hand-rolled `Authorization` header, which abandons the OAuth flow entirely.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't client_secret_post, etc. discoverable via AS metadata?


3. **A confidential OAuth client is unrepresentable.** `mcp.json`'s `oauth` object has no field for a client secret, a private key, or a client certificate. Servers whose Authorization Servers require `client_secret_post`, `client_secret_basic`, `private_key_jwt` (RFC 7523), or `tls_client_auth` (RFC 8705) cannot be expressed. The configuration falls back to a hand-rolled `Authorization` header, which abandons the OAuth flow entirely.

4. **Servers with non-standard discovery cannot be configured.** RFC 9728 / RFC 8414 discovery is the standard, but a meaningful fraction of real-world Authorization Servers do not publish `.well-known/oauth-authorization-server` or publish it incorrectly. Snowflake's Custom OAuth security integration, for example, exposes its endpoints at `/oauth/authorize` and `/oauth/token-request` and publishes no discovery document. There is no escape hatch in any of the configuration formats.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm a spec-purist, but I'm wary of providing this escape hatch. Saying "it's okay if you don't follow the spec, we'll try to make it work anyway" means that the spec loses significant leverage.


5. **Client ID Metadata Documents are unrepresentable.** [The CIMD Internet-Draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/) defines a registration model where the `client_id` _is_ an HTTPS URL pointing to a metadata document — no DCR, no per-server pre-registration. A `clientId` field that contains an HTTPS URL cannot be distinguished from a static client identifier that happens to look like a URL. The connection behavior is completely different.

6. **Servers that accept anonymous requests but offer authenticated tools have no way to say so.** Many servers serve `initialize` and `tools/list` without authentication but require it for some `tools/call` operations, returning 401 lazily. A client that doesn't know this pre-authenticates unnecessarily; a client that doesn't know auth is _required_ wastes a round-trip on a guaranteed 401.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see the desire for expressing this, but IMO it is a bigger conversation than just whether it can be expressed in a server card. Currently the Authorization spec has no carve-out for "some things require authorization, and some things don't".


4. **Servers with non-standard discovery cannot be configured.** RFC 9728 / RFC 8414 discovery is the standard, but a meaningful fraction of real-world Authorization Servers do not publish `.well-known/oauth-authorization-server` or publish it incorrectly. Snowflake's Custom OAuth security integration, for example, exposes its endpoints at `/oauth/authorize` and `/oauth/token-request` and publishes no discovery document. There is no escape hatch in any of the configuration formats.

5. **Client ID Metadata Documents are unrepresentable.** [The CIMD Internet-Draft](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/) defines a registration model where the `client_id` _is_ an HTTPS URL pointing to a metadata document — no DCR, no per-server pre-registration. A `clientId` field that contains an HTTPS URL cannot be distinguished from a static client identifier that happens to look like a URL. The connection behavior is completely different.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CIMD support is also discoverable via metadata, right? What additional benefit is there in pre-declaring it?

| --- | --- | --- | --- |
| `type` | `"oauth"` | Yes | Discriminator. |
| `registration` | `"dynamic"` \| `"client-metadata"` \| `"static"` | No (default `"dynamic"`) | How the OAuth client is established. See [Registration modes](#registration-modes). |
| `clientId` | `string` | When `registration` is `"static"` and the publisher provides a shared client ID | The OAuth client identifier. See [Static registration and `clientId`](#static-registration-and-clientid). |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to think of a scenario where the resource server publishes a shared client ID. It seems odd to me. What do you have in mind?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, now I see the static registration section below. It feels like an OAuth antipattern to me.


- **`dynamic`** — The client registers itself with the Authorization Server at runtime using RFC 7591 Dynamic Client Registration. No `clientId` is declared. The Authorization Server may issue a `client_secret` in the registration response, which the client stores. This is the default and the recommended mode for new servers, per the MCP authorization specification.

- **`client-metadata`** — The client identifier is an HTTPS URL pointing to a Client ID Metadata Document, per [draft-ietf-oauth-client-id-metadata-document](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/). There is no registration step and no per-server state. The client supplies its own metadata document URL as `client_id`. No `clientId` is declared in the server's `auth` block (the URL belongs to the _client_, not the server). _Note: this mode depends on the MCP authorization specification adopting CIMD support; it is included here so the configuration format does not need a breaking change when it does._
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **`client-metadata`** — The client identifier is an HTTPS URL pointing to a Client ID Metadata Document, per [draft-ietf-oauth-client-id-metadata-document](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/). There is no registration step and no per-server state. The client supplies its own metadata document URL as `client_id`. No `clientId` is declared in the server's `auth` block (the URL belongs to the _client_, not the server). _Note: this mode depends on the MCP authorization specification adopting CIMD support; it is included here so the configuration format does not need a breaking change when it does._
- **`client-metadata`** — The client identifier is an HTTPS URL pointing to a Client ID Metadata Document, per [draft-ietf-oauth-client-id-metadata-document](https://datatracker.ietf.org/doc/draft-ietf-oauth-client-id-metadata-document/). There is no registration step and no per-server state. The client supplies its own metadata document URL as `client_id`. No `clientId` is declared in the server's `auth` block (the URL belongs to the _client_, not the server).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CIMD is in! 🙂

Comment on lines +313 to +328
#### Optional auth — authenticate only for write tools

```json
{
"type": "streamable-http",
"url": "https://mcp.docs.example.com/mcp",
"auth": {
"posture": "optional",
"methods": [
{ "type": "oauth", "registration": "dynamic" }
]
}
}
```

The client connects without credentials, lists tools, and offers authentication when a tool returns 401.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What information does this convey to the client that it isn't possible to convey today? Or said another way: knowing this info, how should the client behave differently than today?

My read is that the client will still follow the pattern of "try a request, and if it returns 401 with WWW-Authenticate, perform an authorization flow" which is what clients already do today.


### Why posture is three-valued, not boolean

`required` and `none` are obvious. `optional` matters because the MCP authorization specification's lazy-401 model means many servers can legitimately serve `initialize` and `tools/list` to anonymous clients while gating individual tools. A boolean conflates "you must authenticate before connecting" with "you can authenticate if you want a better experience." Clients need to distinguish them: a client that doesn't know auth is required wastes a round-trip; a client that doesn't know auth is optional pre-authenticates everyone.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a client that doesn't know auth is optional pre-authenticates everyone.

Does it though? To my knowledge, MCP clients don't pre-authenticate today.


### Why declare a menu, not a choice

A Server Card describes what a server supports; an `mcp.json` describes what one consumer chose. Putting `auth.methods` on the publisher-side document keeps the layering consistent with how SEP-2127 and SEP-2633 already split concerns — `server.json` is "highly configurable," `mcp.json` is "fully resolved." Without a menu, a server that supports both OAuth and an API key must publish two Server Cards.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A Server Card describes what a server supports; an mcp.json describes what one consumer chose.

I like this framing! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants