# MCP (Model Context Protocol)

> How to create a Model Context Protocol configuration: the missing file, server metadata, tools, resources, and CORS.

Cette vérification représente 3 % du score.

*Ces guides de correction sont rédigés en anglais.*

An MCP server is only useful to an agent that knows the URL. Without discovery, somebody has to paste it in by hand, which means your server is reachable by people who already know about you and nobody else.

> **This guide changed with the specification**
>
> Earlier versions of this audit looked for
>
>
>
> /.well-known/mcp.json
>
>  carrying a
>
>
>
> tools[]
>
>  array, and recommended publishing one. That path was never part of the MCP specification and that array was never part of a card. If you followed the old advice, the
>
>
>
> legacy manifest
>
>  section says what to do with the file.

The check looks in four places, in order, and the first hit wins:

- an entry in `/.well-known/ai-catalog.json`
- `/.well-known/mcp/server-card.json`, the Cloudflare and Mintlify convention
- `<endpoint>/server-card`, what the draft extension recommends
- `/.well-known/mcp.json`, legacy, reported and never recommended

---

<a id="not-found"></a>

## No MCP server discoverable

Nothing at any of the four. If you run no MCP server this is nothing to fix and the check reports it as such. If you do run one, agents have to be told its URL by a person.

```
GET /.well-known/mcp/server-card.json
Content-Type: application/mcp-server-card+json

{
  "$schema": "https://modelcontextprotocol.io/schemas/draft/server-card.schema.json",
  "name": "com.example/docs",
  "version": "1.0.0",
  "description": "Search and read the Example documentation.",
  "websiteUrl": "https://example.com",
  "remotes": [
    {
      "type": "streamable-http",
      "url": "https://example.com/mcp",
      "supportedProtocolVersions": ["2026-07-28"]
    }
  ]
}
```

Then reference it from `/.well-known/ai-catalog.json` so one file names everything you offer.

---

<a id="legacy-manifest"></a>

## /.well-known/mcp.json is served

A folk convention that predates server cards, and one this audit used to recommend. It is reported rather than scored: serving it is not an error, and it is not discovery either, because no client looks there.

Publish a server card first. Once a card exists, remove the manifest rather than keeping both: two files describing one server is two places to update and one place to forget.

---

<a id="missing-name"></a>

## The manifest has no name

Your `/.well-known/mcp.json` is missing `name`, so anything reading it has a server with no identity. Add the field if you intend to keep the file working while clients migrate.

> **Use the name you will keep**
>
> The server card names servers in reverse-DNS form —
>
>
>
> com.example/docs
>
> . Writing the same name here means the manifest and the card agree while both exist, and nothing has to be renamed when the manifest goes away.

---

<a id="missing-description"></a>

## The manifest has no description

The description is the one line that decides whether a client connects at all. Without it, whoever is looking at your server sees a name and a URL and has to try it to find out what it does.

Say what the server is for in a sentence, in the terms a caller would use: `"Search and read the Example documentation"`, not `"MCP server"`.

---

<a id="missing-tool-descriptions"></a>

## Some tools have no description

Part of your `tools` array carries a `description` and part does not. A tool with no description is one an agent will not choose, because choosing is exactly what it reads the description for.

The undescribed tools are usually the ones added last, after the file stopped being reviewed. That is also the argument for the live `tools/list` call: a description written next to the handler is written once and cannot fall behind.

---

<a id="no-tool-descriptions"></a>

## No tool has a description

Every entry in `tools` is a bare name. An agent picking between `search` and `query` has nothing to pick on, so it either guesses or calls both.

```
{
  "name": "search_docs",
  "description": "Search the documentation and return matching sections with their URLs.",
  "inputSchema": {
    "type": "object",
    "properties": { "query": { "type": "string" } },
    "required": ["query"]
  }
}
```

Write the description for the caller, not for the changelog: what it does, what it returns, and when to reach for it rather than a neighbouring tool.

---

<a id="empty-tools"></a>

## The tools array is empty

`"tools": []` is a server that declares it can do nothing. It is almost always a placeholder that shipped, or a generator that ran before the tools were registered.

If the server does have tools, list them. If it does not — a resources-only server is legitimate — drop the empty array rather than publishing a claim you do not mean.

---

<a id="no-tools"></a>

## No tools array

The manifest describes a server without saying what can be called on it. For a file whose only purpose is to be read statically, that leaves nothing to read.

> **The card is the opposite**
>
> A server card must
>
> not
>
>  carry a
>
>
>
> tools
>
>  array — see
>
>
>
> a tools array in the card
>
> . The two files disagree on this because they answer different questions: the manifest was a static catalogue, the card is a pointer to a live server you call
>
> tools/list
>
>  on.

---

<a id="no-resources"></a>

## No resources array

Resources are the data an MCP server exposes for reading, as opposed to the tools it exposes for calling: files, records, documents addressed by URI. Your manifest lists none.

Plenty of servers are genuinely tools-only, and this is reported rather than treated as an error. If yours does expose readable data, list it — a resource an agent cannot see is one it will try to reach through a tool call instead.

---

<a id="no-version"></a>

## No protocol version

Nothing in the manifest says which revision of MCP the server speaks, so a client cannot tell whether its own revision will work before connecting.

```
{
  "protocolVersion": "2026-07-28"
}
```

`2026-07-28` is the current revision. The server card spells this as `supportedProtocolVersions`, an array, because a server usually speaks more than one — see [no supportedProtocolVersions](#no-protocol-version).

---

<a id="invalid-json"></a>

## The card is not valid JSON

Served and unparseable. Check for a trailing comma, and check the response is not an HTML error page returned with a `200`.

---

<a id="missing-field"></a>

## A required field is missing

A server card declares four things: `$schema`, `name`, `version` and `description`. The description is the one most often left out and the one an agent reads to decide whether to connect at all.

---

<a id="name-format"></a>

## A name that is not in reverse-DNS form

Names are reverse-DNS so that a registry listing a thousand servers has no collisions: `com.example/docs`, `io.github.owner/server`. A bare word like `docs` is not globally unambiguous, and half the servers in the world would like to be called it.

---

<a id="tools-in-card"></a>

## A tools array in the card

The schema deliberately omits it. Tool lists come from a live `tools/list` call, and a static copy in a cached file drifts from the server the first time you ship a change.

A card is a pointer, not an inventory: it says where the server is and what it speaks, and the server says what it can do.

---

<a id="no-remotes"></a>

## No remotes declared

The card describes a server and does not say where it is. Declare at least one:

```
"remotes": [
  {
    "type": "streamable-http",
    "url": "https://example.com/mcp",
    "supportedProtocolVersions": ["2026-07-28"]
  }
]
```

---

<a id="remote-type"></a>

## An unrecognised transport

Two are defined: `streamable-http` and `sse`. SSE is the legacy transport; new servers use streamable-http. Anything else is skipped by a client that validates.

---

<a id="remote-url"></a>

## A remote with no url

Every remote needs an absolute `https://` URL. A relative path cannot be resolved by a client that found the card through a catalog on another origin.

---

<a id="no-protocol-version"></a>

## No supportedProtocolVersions

Without it a client has to connect and negotiate to find out whether it can talk to you at all. Declaring the revisions you speak, newest first, turns that into a decision made before the connection.

---

<a id="unknown-protocol-version"></a>

## An unrecognised revision

MCP revisions are dates. A semantic version, or a date that was never released, will not match anything a client knows. The released revisions are `2026-07-28`, `2025-11-25`, `2025-06-18`, `2025-03-26` and `2024-11-05`.

---

<a id="stale-protocol-version"></a>

## Only pre-2025-06 revisions supported

Clients on newer revisions may not negotiate down. `2026-07-28` removed sessions and `initialize`, made `MCP-Protocol-Version` mandatory on every POST, and added `server/discover`, so the gap between old and current is a different handshake rather than a few extra fields.

---

<a id="no-cors"></a>

## No CORS on the card

Browser-based agents fetch the card cross-origin and are refused without it. The extension recommends both of these:

```
Access-Control-Allow-Origin: *
Cache-Control: public, max-age=3600
```

The card is a public document, so the permissive origin costs nothing. If the header is missing here it is usually missing everywhere; the [HTTP Headers guide](https://axrush.com/guides/http-headers#no-cors) covers setting it once, at the edge.

---

<a id="catalog-no-version"></a>

## The catalog declares no version

The card was found through `/.well-known/ai-catalog.json` and that catalog carries no `specVersion`, so a consumer cannot tell which revision of the catalog shape to expect. See the AI Catalog guide.

---

<a id="catalog-entry"></a>

## An incomplete catalog entry

An entry needs an `identifier`, a `type`, and either a `url` or an inline `data` object. Missing one, it is listed and unusable.

---

<a id="catalog-entry-type"></a>

## An unrecognised entry type

Three types are defined: `application/mcp-server-card+json`, `application/a2a-agent-card+json` and `application/ai-catalog+json`. An entry outside them is skipped.

---

Représentation Markdown de https://axrush.com/guides/mcp-discovery
