MCP Server Cards and .well-known Discovery: Make Your Server Auto-Discoverable
A practical guide to MCP Server Cards and .well-known discovery endpoints so AI clients can automatically find and connect to your MCP server — with code for Express, Next.js, and FastAPI.
Mohammed Kafeel
Machine Learning Researcher
On this page
- What Is MCP Server Discovery? (And Why Does It Matter?)
- The Two Discovery Proposals: SEP-1649 vs SEP-1960
- What Is an MCP Server Card? (The JSON Schema Explained)
- How Does the Discovery Flow Work? (Step by Step)
- How to Implement .well-known Discovery (Code Examples)
- The 5 Most Common Pitfalls (And How to Avoid Them)
- How to Test Your Discovery Endpoint
- Security Considerations
- Production Readiness Checklist
- FAQ
- Useful Sources
You've built an MCP server. But if an AI client can't find it automatically, it might as well not exist.
Right now, most MCP integrations still require users to manually paste a URL into a config file. That's friction. It's the equivalent of handing someone a map instead of a GPS. The MCP ecosystem is moving fast to fix this - and MCP server discovery is the mechanism that makes auto-configuration possible. (This is the publisher's side of the coin; from the consumer's side, see our guide to finding MCP servers.)
In this guide, we'll walk through exactly how to implement it: the .well-known endpoint, the Server Card JSON schema, code examples for three popular stacks, and everything you need to be production-ready.
🗝️ TL;DR - Key Takeaways
- MCP server discovery lets AI clients find your server automatically, without manual configuration.
- Serve a JSON "Server Card" at
/.well-known/mcp/server-card.json(SEP-1649).- Also serve it at
/.well-known/mcp.jsonfor SEP-1960 compatibility.- The Server Card describes your transport URL, capabilities, protocol version, and optional auth.
- Always use HTTPS, set CORS headers, and add
Cache-Control.- As of June 2026, neither SEP is an official standard yet - but major clients are already implementing support.
- Test with
curland the MCP Inspector.
What Is MCP Server Discovery? (And Why Does It Matter?)
MCP server discovery is a mechanism that lets AI clients - Claude Desktop, Cursor, Cline, and others - automatically locate your server, understand what it does, and connect to it without any manual configuration from the user. (If you're new to the protocol, start with what the Model Context Protocol is.)
Today, connecting to an MCP server typically looks like this: a user opens a config file, pastes in a transport URL, maybe sets an auth token, and hopes they got it right. It's tedious. It breaks. And it's a barrier to adoption.
The fix is a well-known URI (a standard web path where apps expose machine-readable metadata). The concept comes from RFC 8615, the IETF standard that reserves the /.well-known/ path prefix on any web server for exactly this kind of service metadata. You've already seen it in action - /.well-known/oauth-authorization-server, /.well-known/openid-configuration. Same idea, new use case.
Think of it like a business card permanently taped to your front door. Any client that knows the convention can walk up, read the card, and know exactly how to reach you - no phone call required.
The problem MCP discovery solves
Without discovery, every MCP integration has three friction points:
- Manual endpoint configuration - users must know and type the exact transport URL.
- No pre-connection metadata - clients must complete a full initialization handshake just to learn the server's name and capabilities.
- No automated indexing - registries and IDE extensions can't crawl domains to find available servers. (Discovery and the MCP server registry approach are complementary ways to solve this.)
With a .well-known/mcp.json or /.well-known/mcp/server-card.json endpoint in place, a client can resolve all three problems with a single unauthenticated GET request before any connection is established.
The Two Discovery Proposals: SEP-1649 vs SEP-1960
There are currently two active proposals in the MCP ecosystem for standardizing discovery. Neither is an official part of the MCP specification as of June 2026, but both have real-world adoption and are actively being implemented by clients.
SEP-1649: MCP Server Cards
SEP-1649 (opened October 14, 2025, authored by @dsp-ant and @nickcoai) proposes a rich "Server Card" document served at /.well-known/mcp/server-card.json. Its focus is comprehensive server metadata: who you are, what you do, how to connect, and what capabilities you offer.
The Server Card is designed to mirror the MCP initialization response - so clients can get initialization-equivalent data without actually initializing a session.
SEP-1960: Manifest Discovery
SEP-1960 takes a lighter-weight approach. It targets /.well-known/mcp and focuses specifically on endpoint enumeration and authentication discovery. Where SEP-1649 gives you the full picture, SEP-1960 gives you a concise manifest of available endpoints with their individual auth configurations.
Side-by-side comparison
| Feature | SEP-1649 (Server Cards) | SEP-1960 (Manifest) |
|---|---|---|
| Path | /.well-known/mcp/server-card.json |
/.well-known/mcp |
| Focus | Server metadata and capabilities | Endpoint enumeration and auth |
| Required fields | $schema, version, protocolVersion, serverInfo, transport, capabilities |
mcp_version, endpoints |
| Auth discovery | Optional authentication object |
Built-in auth object per endpoint |
| RFC basis | RFC 8615 (well-known URI convention) | RFC 8615 |
| Multiple endpoints | Single transport definition | Array of endpoints |
| Static tool listing | Optional (can list tools inline) | Not included |
Our recommendation: implement both
Serve the same Server Card JSON at both paths. It's a few extra lines of routing code and it maximizes compatibility regardless of which proposal a client has implemented. We'll show you exactly how in the code examples below.
⚠️ Status note: As of June 2026, SEP-1649 is labeled "Draft" on GitHub and SEP-1960 is an active proposal. The MCP Server Card Working Group is actively working toward standardization. Implement now to get ahead of the curve - but expect minor schema changes before finalization.
What Is an MCP Server Card? (The JSON Schema Explained)
An MCP Server Card is a JSON document that acts as your server's identity. Here's the full structure:
{
"$schema": "https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",
"version": "1.0",
"protocolVersion": "2025-06-18",
"serverInfo": {
"name": "my-mcp-server",
"title": "My MCP Server",
"version": "1.2.0",
"description": "Description of what this server does",
"homepage": "https://example.com"
},
"transport": {
"type": "streamable-http",
"endpoint": "/mcp"
},
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": true, "listChanged": true },
"prompts": { "listChanged": false }
},
"authentication": {
"required": false,
"schemes": []
},
"instructions": "Optional usage instructions for AI clients",
"tools": "dynamic",
"resources": "dynamic",
"prompts": "dynamic"
}
Note on the
$schemaURL: The SEP-1649 specification referenceshttps://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json. Some earlier examples usedhttps://modelcontextprotocol.io/schemas/server-card/v1.0- use thestatic.subdomain form as it reflects the latest proposal text.
Field-by-field breakdown
$schema (string, required)
Points to the JSON Schema definition for the Server Card format. Clients use this to validate your document.
version (string, required)
The version of the Server Card schema itself - currently "1.0". Not your server version.
protocolVersion (string, required)
The MCP protocol version your server supports. This must match your MCP SDK version. The current stable protocol version is "2025-06-18". A mismatch here will cause capability negotiation failures.
serverInfo (object, required)
Your server's identity. Includes:
name- a machine-readable identifier (no spaces, kebab-case recommended)title- a human-readable display nameversion- your server software's semver versiondescription- what your server does, in plain English
transport (object, required)
How clients connect. For HTTP-based servers:
type:"streamable-http"(the current standard),"sse", or"stdio"endpoint: the path relative to your domain (e.g.,"/mcp")
capabilities (object, required)
Mirrors the MCP ServerCapabilities interface. Set tools, resources, and prompts as objects (with optional listChanged and subscribe booleans) to indicate what your server supports.
authentication (object, optional)
Whether auth is required and which schemes are supported. For public servers, set "required": false. For OAuth-protected servers, list "schemes": ["oauth2"] and point clients to your /.well-known/oauth-protected-resource endpoint. (For the full flow, see our MCP OAuth with PKCE guide.)
tools / resources / prompts (string or array, optional)
Set to "dynamic" if the list changes per session (the common case). Or provide a static array of tool/resource/prompt definitions for clients to inspect before connecting - useful for security scanning and registry indexing.
How Does the Discovery Flow Work? (Step by Step)
Here's exactly what happens when an AI client encounters a domain that might host an MCP server:
Step 1: Well-known probe
The client sends GET /.well-known/mcp/server-card.json to your domain. No auth, no session - just a plain HTTP request. If it gets a 200 with valid JSON, discovery proceeds.
Step 2: Metadata parsing The client reads the Server Card. It extracts the transport type, the endpoint URL, the protocol version, the capabilities, and any authentication requirements. This is the "pre-connection briefing."
Step 3: Auth negotiation (if required)
If authentication.required is true, the client follows the OAuth 2.1 flow. It fetches /.well-known/oauth-protected-resource (RFC 9728) to find the authorization server, then initiates the token exchange. The MCP spec mandates OAuth 2.1 for remote HTTP servers.
Step 4: Session initialization
The client connects to the transport endpoint (e.g., https://example.com/mcp) and begins the JSON-RPC handshake - sending an initialize request with its own capabilities.
Step 5: Capability confirmation
During initialize, the server returns its ServerCapabilities. The client cross-checks these against the Server Card. If they match, the session is live. If they don't match, the client may warn the user or abort. (If the host/client/server roles in this flow are unfamiliar, see our breakdown of MCP server architecture.)
The key insight: steps 1–2 happen before any TCP connection to your MCP endpoint. That's what makes discovery powerful - clients can make informed decisions without expensive round-trips.
How to Implement .well-known Discovery (Code Examples)
Let's get your server discoverable. Here are implementations for the three most popular stacks. Each example serves the Server Card at both paths for maximum compatibility.
Express.js
import express from 'express';
import cors from 'cors';
const app = express();
// CORS for all origins - Server Cards are public metadata
app.use(cors({
origin: '*',
methods: ['GET', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'Mcp-Session-Id'],
}));
const serverCard = {
"$schema": "https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",
"version": "1.0",
"protocolVersion": "2025-06-18",
"serverInfo": {
"name": "my-analytics-mcp-server",
"title": "My Analytics MCP Server",
"version": "2.1.0",
"description": "Provides analytics data and reporting tools",
"homepage": "https://example.com"
},
"transport": {
"type": "streamable-http",
"endpoint": "/mcp"
},
"capabilities": {
"tools": { "listChanged": true },
"resources": { "listChanged": true },
"prompts": {}
},
"authentication": {
"required": false,
"schemes": []
},
"tools": "dynamic",
"resources": "dynamic"
};
const discoveryHeaders = {
'Content-Type': 'application/json',
'X-Content-Type-Options': 'nosniff',
'Cache-Control': 'public, max-age=3600',
'Access-Control-Allow-Origin': '*',
};
// SEP-1649 path
app.get('/.well-known/mcp/server-card.json', (_req, res) => {
res.set(discoveryHeaders);
res.json(serverCard);
});
// SEP-1960 compatibility path
app.get('/.well-known/mcp.json', (_req, res) => {
res.set(discoveryHeaders);
res.json(serverCard);
});
app.listen(3000);
Next.js App Router
// app/api/.well-known/mcp/server-card.json/route.ts
// Also create: app/api/.well-known/mcp.json/route.ts (same content)
import { NextResponse } from 'next/server';
const serverCard = {
"$schema": "https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",
"version": "1.0",
"protocolVersion": "2025-06-18",
"serverInfo": {
"name": "my-nextjs-mcp-server",
"title": "My Next.js MCP Server",
"version": "1.0.0",
"description": "MCP server built with Next.js",
"homepage": "https://example.com"
},
"transport": {
"type": "streamable-http",
"endpoint": "/api/mcp"
},
"capabilities": {
"tools": { "listChanged": true },
"resources": {},
"prompts": {}
},
"authentication": {
"required": false,
"schemes": []
},
"tools": "dynamic"
};
const headers = {
'Cache-Control': 'public, max-age=3600',
'Access-Control-Allow-Origin': '*',
'X-Content-Type-Options': 'nosniff',
};
export async function GET() {
return NextResponse.json(serverCard, { headers });
}
export async function OPTIONS() {
return new NextResponse(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
Next.js note: The App Router maps file paths to URL paths. Place the file at
app/api/.well-known/mcp/server-card.json/route.tsand it'll be served at/api/.well-known/mcp/server-card.json. If you need it at the root (without/api), use a Next.jsrewritesrule or a custom server.
FastAPI (Python)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["GET", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "Mcp-Session-Id"],
)
SERVER_CARD = {
"$schema": "https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",
"version": "1.0",
"protocolVersion": "2025-06-18",
"serverInfo": {
"name": "my-fastapi-mcp-server",
"title": "My FastAPI MCP Server",
"version": "1.0.0",
"description": "MCP server built with FastAPI",
"homepage": "https://example.com"
},
"transport": {
"type": "streamable-http",
"endpoint": "/mcp"
},
"capabilities": {
"tools": {"listChanged": True},
"resources": {},
"prompts": {}
},
"authentication": {
"required": False,
"schemes": []
},
"tools": "dynamic"
}
DISCOVERY_HEADERS = {
"Cache-Control": "public, max-age=3600",
"Access-Control-Allow-Origin": "*",
"X-Content-Type-Options": "nosniff",
}
# SEP-1649 path
@app.get("/.well-known/mcp/server-card.json")
async def server_card():
return JSONResponse(content=SERVER_CARD, headers=DISCOVERY_HEADERS)
# SEP-1960 compatibility path
@app.get("/.well-known/mcp.json")
async def server_card_compat():
return JSONResponse(content=SERVER_CARD, headers=DISCOVERY_HEADERS)
FastAPI note: Some reverse proxies (nginx, Caddy) strip or rewrite paths containing dots. Test your
/.well-known/routes through your full proxy stack, not just locally.
The 5 Most Common Pitfalls (And How to Avoid Them)
Pitfall 1: Missing CORS headers
AI clients are often browser-based or run in sandboxed environments. Without Access-Control-Allow-Origin: *, cross-origin requests to your discovery endpoint will be silently blocked. Every discovery endpoint needs CORS headers. No exceptions.
Pitfall 2: Using HTTP instead of HTTPS
The SEP-1649 spec is clear: the .well-known endpoint must be accessible via HTTPS in production. More importantly, your transport.endpoint URL must also resolve over HTTPS. Clients will reject plain HTTP transport URLs for remote servers. Use HTTPS everywhere, full stop.
Pitfall 3: stdout pollution in JSON-RPC
This one bites stdio-based servers especially hard. If your server writes debug logs or console.log output to stdout, those strings get mixed into your JSON-RPC response stream. The client tries to parse "DEBUG: connected\n{...}" as JSON and fails. Route all debug output to stderr, never stdout.
Pitfall 4: Protocol version mismatches
Your protocolVersion field in the Server Card must match what your MCP SDK actually implements. If you're running the TypeScript SDK and your protocolVersion says "2025-06-18" but the SDK is negotiating "2025-11-25", clients may reject the session. Check your SDK's changelog and keep this field in sync.
Pitfall 5: Missing Cache-Control headers
Without Cache-Control: public, max-age=3600, every client request hits your server fresh. For a popular server, that's a lot of unnecessary load. Set the header. One hour (3600 seconds) is a sensible default - long enough to reduce hammering, short enough to pick up config changes reasonably quickly.
How to Test Your Discovery Endpoint
Quick curl validation
# Check response headers (look for Content-Type, Cache-Control, CORS)
curl -I https://yourdomain.com/.well-known/mcp/server-card.json
# Pretty-print the JSON response
curl -s https://yourdomain.com/.well-known/mcp/server-card.json | python3 -m json.tool
# Test the SEP-1960 compatibility path too
curl -s https://yourdomain.com/.well-known/mcp.json | python3 -m json.tool
# Simulate a cross-origin browser request (tests CORS)
curl -H "Origin: https://claude.ai" -I https://yourdomain.com/.well-known/mcp/server-card.json
What to look for in the response headers:
Content-Type: application/json✓Access-Control-Allow-Origin: *✓Cache-Control: public, max-age=3600✓- HTTP
200status ✓
MCP Inspector
The MCP Inspector is the official developer tool for testing MCP servers. Run it with:
npx @modelcontextprotocol/inspector@latest
It launches a browser UI on port 6274. Connect to your server's transport URL (e.g., https://example.com/mcp) and verify that tools, resources, and prompts are all listed correctly. Think of it as Postman for the MCP protocol.
JSON Schema validation
Validate your Server Card against the official schema at https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json using any JSON Schema validator (e.g., jsonschemavalidator.net or the ajv CLI tool):
# Using ajv-cli
npx ajv-cli validate \
-s https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json \
-d ./server-card.json
Security Considerations
Your .well-known endpoint is public by design. That's intentional - but it comes with responsibilities.
Information disclosure. Don't put anything sensitive in your Server Card. No internal IP addresses, no private endpoint paths, no API keys, no infrastructure details. The card is world-readable. Treat it like a public README.
CORS policy. Use Access-Control-Allow-Origin: * for public servers - it's safe because Server Cards are read-only metadata with no credentials. For private or enterprise servers, restrict CORS to known client origins.
DoS protection. Rate-limit the /.well-known/ endpoint. It's unauthenticated and publicly accessible, which makes it a target for abuse. Even a simple rate limit of 60 requests/minute per IP is enough to deter casual hammering.
MITM attacks. Always serve over HTTPS with a valid TLS certificate. Clients should (and increasingly do) validate TLS certs when fetching Server Cards. A compromised card primarily affects discoverability, but it could redirect clients to a malicious endpoint - so don't cut corners on TLS.
Tool description security. If your tools handle sensitive operations, think carefully about what you expose in static tool listings. You can always set "tools": "dynamic" to defer tool discovery to the post-connection tools/list call, which happens over an authenticated session.
Production Readiness Checklist
Before you ship, run through this list:
- Server Card served at
/.well-known/mcp/server-card.json(SEP-1649) - Server Card also served at
/.well-known/mcp.json(SEP-1960 compatibility) - HTTPS only - no plain HTTP transport URLs
-
Access-Control-Allow-Origin: *header present -
Cache-Control: public, max-age=3600header present -
Content-Type: application/jsonheader present -
X-Content-Type-Options: nosniffheader present -
protocolVersionmatches your MCP SDK version -
transport.endpointis publicly reachable - Tested with
curl -I(headers) andcurl -s | python3 -m json.tool(JSON) - Tested with MCP Inspector
- JSON validated against the official schema
- Rate limiting configured on the endpoint
- No sensitive data (IPs, tokens, internal paths) in the Server Card
- OPTIONS preflight handled (for browser-based clients)
📋 Key Takeaways
- MCP server discovery eliminates manual configuration by exposing a Server Card at a predictable
.well-knownURL.- SEP-1649 defines the
/.well-known/mcp/server-card.jsonpath with rich metadata. SEP-1960 defines/.well-known/mcp.jsonwith a focus on endpoint enumeration. Implement both.- The MCP Server Card JSON document describes your transport, capabilities, protocol version, and auth requirements.
- The model context protocol discovery flow is: probe → parse → auth → initialize → confirm.
- MCP auto-discovery requires HTTPS, CORS headers, and Cache-Control - get all three right.
- Neither SEP is finalized as of June 2026, but adoption is growing fast. Implement now.
FAQ
What is .well-known/mcp.json and why do I need it?
/.well-known/mcp.json is a standardized URL path where MCP servers expose a JSON document describing how to connect to them. It follows the well-known URI convention from RFC 8615. You need it so AI clients can discover and auto-configure your server without requiring users to manually enter transport URLs or auth settings.
Is MCP server discovery part of the official MCP specification?
Not yet, as of June 2026. Both SEP-1649 (Server Cards) and SEP-1960 (Manifest Discovery) are active proposals - labeled "Draft" in the MCP GitHub repository. The MCP Server Card Working Group is actively working toward standardization. The core MCP spec at modelcontextprotocol.io/specification/2025-06-18 doesn't yet include discovery, but it's widely expected to be incorporated in a future revision.
Do I need to implement both SEP-1649 and SEP-1960?
You don't have to, but we strongly recommend it. The two proposals target the same problem from slightly different angles, and different clients may implement one or the other. Since both paths can serve identical JSON content, the cost is minimal - just add a second route that returns the same Server Card document.
What happens if an AI client doesn't support discovery?
Nothing breaks. Discovery is fully backward-compatible. Clients that don't support .well-known discovery simply skip the probe and fall back to manual configuration or the standard MCP initialization handshake. Your server continues to work normally - discovery is additive, not a replacement.
Can I use .well-known discovery with stdio transport?
Not really. The .well-known endpoint is an HTTP mechanism, and stdio transport runs over standard input/output (typically for local processes). The SEP-1649 spec notes that HTTP-based transports should provide a Server Card via .well-known, while all servers should also expose their card as an MCP resource at mcp://server-card.json (accessible post-connection). For stdio servers, the MCP resource approach is the relevant discovery mechanism.
How often do AI clients poll the discovery endpoint?
Clients should respect your Cache-Control header. With Cache-Control: public, max-age=3600, a well-behaved client won't re-fetch the card for at least an hour. In practice, most clients fetch it once per session setup, not continuously. That said, always implement rate limiting - not all clients are well-behaved.
What security risks does the discovery endpoint introduce?
The main risks are information disclosure (exposing internal details in the card), DoS (unauthenticated endpoint getting hammered), and MITM (serving over HTTP). All three are mitigable: keep the card to public metadata only, add rate limiting, and enforce HTTPS. A compromised Server Card primarily affects discoverability rather than session security, since the actual MCP connection still requires full initialization and authentication.
Useful Sources
- SEP-1649: MCP Server Cards - GitHub Issue #1649 - The primary proposal, authored by
@dsp-antand@nickcoai, opened October 14, 2025. - MCP Official Documentation - Getting started guide and full protocol reference.
- MCP Specification 2025-06-18 - Current stable protocol specification.
- MCP Inspector - Official interactive tool for testing and debugging MCP servers.
- RFC 8615 - Well-Known Uniform Resource Identifiers - The IETF standard defining the
/.well-known/path convention (May 2019). - IANA Well-Known URIs Registry - Official registry of all registered
.well-knownURI suffixes.
Keep reading
MCP Server Discovery at Scale: Registry and Server Cards Explained
Over 10,000 public MCP servers exist — and an AI agent can't hardcode them all. Here's how MCP discovery works at scale: well-known URIs, Server Cards, the official Registry, and RAG filtering.
MCP for Data Pipelines: Connecting Databases, Warehouses, and Live APIs
Model Context Protocol lets AI agents query databases, transform data, and call live APIs through a single standardized interface. Here's everything data engineers need to know.
Deploying Microsoft MCP Gateway on Kubernetes for Enterprise AI Agents
A hands-on guide to deploying Microsoft MCP Gateway on Kubernetes — architecture, step-by-step setup, enterprise security, observability, and scaling for production AI agent workloads.



