ProtoboxProtobox
Production MCP

How AI agents discover your MCP server automatically

A practical guide to publishing .well-known/mcp.json and DNS TXT records so any AI agent can find and connect to your MCP server without manual configuration.

DGDean GroverFounder, ProtoboxFollow
July 18, 2026
13 min read
Warm watercolor illustration of an AI agent following DNS records and a well-known signpost to locate an MCP server

You ship an MCP server with three tools your AI agent needs: customer lookup, order history, and ticket creation. You write the docs, paste the URL into a config file, and tell every developer on the team where to find it.

Six weeks later, someone builds a new agent and hardcodes the wrong URL. Another team has a staging server and a production server at the same path and keeps deploying against the wrong one. A third team switches to a new MCP client that reads a different config format. Nobody notices until a production agent calls the wrong endpoint and starts returning errors nobody can trace.

This is the manual-configuration problem. The July 28 MCP specification release candidate has a clean answer: auto-discovery.

What MCP discovery actually solves

Auto-discovery means an AI agent can find and connect to your MCP server knowing only your domain name. No URL pasting. No config files to keep synchronized. No setup guide that's three months stale.

The mechanics work in two layers. The first is an HTTP probe: the agent sends a GET request to https://yourdomain.com/.well-known/mcp.json and reads a structured document that describes your server. The second is DNS: a TXT record at _mcp.yourdomain.com advertises the same information through the global DNS system, so agents that only have your domain can find your server before they make any HTTP request.

When both layers are in place, any compliant MCP client (Claude, Cursor, or a client you haven't heard of yet) can connect to your server from your domain name alone. That's it.

The timing matters here. With the July 28 spec formalizing stateless MCP, discovery becomes more important, not less. Stateless architecture means any server instance can handle any request, and load balancers no longer need sticky sessions. But it also means there's no persistent session to hold configuration. The discovery document becomes the authoritative record of what the cluster supports. Agents read it fresh, connect accordingly, and the cluster can scale horizontally without breaking anything.

Building the .well-known/mcp.json endpoint

The .well-known directory is an IETF-standardized pattern for machine-readable metadata at a predictable URL. You've seen it for /.well-known/openid-configuration and /.well-known/security.txt. MCP follows the same convention.

A minimal discovery document for an agent tools server:

.well-known/mcp.json
{
  "schemaVersion": "2026-07-28",
  "name": "Acme CX Tools",
  "version": "1.4.2",
  "endpoint": "https://mcp.yourdomain.com",
  "transport": ["streamable-http", "sse"],
  "authentication": {
    "schemes": ["oauth2", "apikey"],
    "oauth2": {
      "authorizationEndpoint": "https://auth.yourdomain.com/oauth/authorize",
      "tokenEndpoint": "https://auth.yourdomain.com/oauth/token",
      "scopes": ["mcp:read", "mcp:write"]
    }
  },
  "capabilities": {
    "tools": true,
    "resources": false,
    "prompts": false,
    "extensions": ["com.yourdomain.cx-metadata"]
  }
}

The endpoint field points to your actual MCP server, not the domain hosting the discovery file. This lets you host the discovery document on your marketing site or a CDN while the MCP server itself lives at a different subdomain or behind a separate load balancer.

Serving this from a Next.js route:

src/app/.well-known/mcp.json/route.ts
import { mcpDiscoveryDocument } from '@/lib/mcp-config'
 
export async function GET() {
  return Response.json(mcpDiscoveryDocument, {
    headers: {
      'Cache-Control': 'public, max-age=3600',
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    }
  })
}

The Cache-Control header matters. Agents probe this URL on every cold start. Cache for an hour and you add no measurable latency to agent initialization.

The Access-Control-Allow-Origin: * header is also required. Agents running in browser contexts fail the OPTIONS preflight if you omit it on this specific route. The MCP server itself needs careful CORS configuration, but the discovery document is fully public metadata. Open it wide.

With Express or Fastify:

discovery-route.ts
import express from 'express'
import { mcpDiscoveryDocument } from './mcp-config'
 
const app = express()
 
app.get('/.well-known/mcp.json', (_req, res) => {
  res
    .setHeader('Cache-Control', 'public, max-age=3600')
    .setHeader('Access-Control-Allow-Origin', '*')
    .json(mcpDiscoveryDocument)
})

Publishing DNS TXT records for broader reach

HTTP discovery requires an agent that already knows your domain. DNS discovery goes further: it works from the domain name alone, before any HTTP connection is made.

You publish a single DNS TXT record at _mcp.yourdomain.com. The value follows a structured format:

_mcp.yourdomain.com TXT "v=mcp1 url=https://mcp.yourdomain.com transport=streamable-http auth=oauth2 caps=tools"

The fields:

  • v=mcp1: protocol version, always mcp1 for the 2026 spec
  • url: the MCP server endpoint agents should connect to
  • transport: streamable-http, sse, or stdio for local servers
  • auth: the primary authentication scheme
  • caps: comma-separated list of capabilities

Set the TTL to 300 seconds. Changes to your server endpoint or capabilities need to propagate quickly, and a 300s TTL means most recursive resolvers refresh within five minutes.

Verify the record is publishing:

dig TXT _mcp.yourdomain.com +short
# "v=mcp1 url=https://mcp.yourdomain.com transport=streamable-http auth=oauth2 caps=tools"

For multi-agent deployments, the DNS-AID framework extends this with a structured naming pattern: _<agent-name>._<protocol>._agents.<domain>. A deployment with separate agents for support, billing, and sales can publish:

_support._mcp._agents.yourdomain.com TXT "v=mcp1 url=https://mcp.yourdomain.com/support ..."
_billing._mcp._agents.yourdomain.com TXT "v=mcp1 url=https://mcp.yourdomain.com/billing ..."
_sales._mcp._agents.yourdomain.com   TXT "v=mcp1 url=https://mcp.yourdomain.com/sales ..."

An agent handling a billing inquiry discovers the billing-specific server directly, without loading tools meant for support flows. The tool count stays low; the calls stay precise.

How agents actually use this

When a user asks Claude to connect to your tools, here's what happens:

sequenceDiagram
    participant U as User
    participant A as AI Agent
    participant DNS as DNS Resolver
    participant WK as .well-known
    participant MCP as MCP Server
 
    U->>A: Use acme.com tools for my order
    A->>DNS: Query _mcp.acme.com TXT
    DNS-->>A: endpoint, transport, auth
    A->>WK: GET acme.com/.well-known/mcp.json
    WK-->>A: Full capability document
    A->>MCP: Initialize + negotiate capabilities
    MCP-->>A: Tools list
    A->>U: Connected. I can access 3 tools.
MCP auto-discovery: from domain to first tool call

The agent runs the DNS lookup and HTTP probe in parallel. If both succeed, it merges the results, with the full .well-known document taking precedence over the abbreviated DNS record. If only one succeeds, it uses what it has. If neither succeeds, it falls back to any manually-configured endpoint.

This fallback behavior is what makes discovery safe to ship. Existing setups keep working. Discovery adds a faster, more reliable path for setups that implement it.

The 2026 spec also uses the discovery document for capability negotiation before initialization. An agent that needs the Tasks extension can read the extensions array in the discovery document and bail out early if the server doesn't support it. This avoids connecting, initializing, failing a capability check, and then disconnecting. One HTTP request replaces a full session lifecycle for incompatible clients.

Security: what to include and what to leave out

The discovery document is public metadata. It will be indexed. Treat it like a public API schema.

Include in the discovery document:

  • Your endpoint URL (already knowable by anyone who attempts a connection)
  • Supported transport types and authentication schemes
  • The list of capability types (tools, resources, prompts)
  • Extensions your server supports
  • Server name and version

Don't include:

  • Internal hostnames or private IP addresses
  • Specific tool names or descriptions (those come from tools/list after authentication)
  • Any information that would help an attacker map your internal architecture
  • Authentication credentials of any kind

A common mistake is including the full tools list in the discovery document to save an extra round trip. Don't. The tools list is behind authentication for a reason. It tells an attacker exactly what your agent can do before they've proven they're authorized to use it. The discovery document says "we have tools"; the authenticated session says which ones.

Discovery with a hosted MCP server

If your MCP server runs on Protobox, the stale-URL problem largely disappears at the source: your server lives at one stable hosted URL that you paste into Claude, Cursor, or ChatGPT, and each toolset -- a named, scoped bundle of tools -- gets its own scoped MCP URL. Rotating tools in and out of a toolset doesn't change the URL agents connect to.

Discovery still earns its place when you want agents to find your tools from your own domain. The endpoint field in your discovery document can point at your hosted MCP URL while the document itself is served from your marketing site or a CDN, exactly as in the examples above. Publish the .well-known/mcp.json on your domain, set endpoint to your hosted server URL, add the matching DNS TXT record, and any compliant client can go from your domain name to a live tool session without anyone pasting a URL.

A generic client connects via discovery like this:

agent-setup.ts
import { McpClient } from '@modelcontextprotocol/sdk'
 
// Connect using just the domain (no hardcoded URLs)
const mcpClient = await McpClient.discover('tools.yourcompany.com', {
  auth: {
    type: 'oauth2',
    clientId: process.env.MCP_CLIENT_ID,
    clientSecret: process.env.MCP_CLIENT_SECRET,
  }
})
 
// Tools populated from the capability document
const tools = await mcpClient.listTools()
console.log(`Connected to ${tools.length} tools via discovery`)
 
const result = await mcpClient.callTool('lookup_customer', {
  customerId: 'cust_7723'
})

The McpClient.discover() call handles the DNS probe and HTTP probe concurrently, validates the capability document, authenticates with the server, and returns a ready-to-use client. Your agent code never touches a URL directly. If you rotate your MCP server's hostname, update the discovery document and the DNS record, and agents pick up the change on their next connection without any code change.

Once agents are connecting, usage analytics on the server side tell you the rest of the story: which tools agents call, how often, and which ones never get used -- the first signal that a tool description isn't discoverable in the semantic sense, even when the server is discoverable in the DNS sense.

claude_desktop_config.json
Live
{
"mcpServers":
{
"orders":
{
"url": "https://orders.protobox.app/mcp",
"transport": "sse",
"apiKey": "pbk_live_...a4f2"
}
}
}
Tools
12 registered
Latency p99
38ms
Auth
Bearer + scopes

Testing discoverability before it matters

Before going to production, run:

# Test HTTP discovery
curl -s https://yourdomain.com/.well-known/mcp.json | jq .
 
# Test DNS discovery
dig TXT _mcp.yourdomain.com +short
 
# Full diagnostic via the MCP SDK CLI
npx mcp-discover yourdomain.com
 
# Dry-run connection and tool list fetch
npx mcp-connect --discover yourdomain.com --list-tools

mcp-discover is the right tool here: it tests both channels, validates the discovery document schema, and attempts a real MCP initialization to catch capability mismatches. Two common mistakes it catches:

Wrong endpoint in the discovery document. The endpoint field should be the MCP server's WebSocket or HTTP endpoint, not the discovery URL. mcp-discover attempts an actual initialization against the endpoint in the document, so a wrong URL fails the test immediately.

Capability mismatch. If your discovery document says "tools": true but your MCP server's tools/list returns an empty array, agents connect and silently fail. The --list-tools flag catches this in the testing phase.

Add mcp-discover yourdomain.com to your CI pipeline. A broken discovery document is silent by default. No errors appear until an agent tries to connect in production.

What ships with the July 28 spec

The July 28 release candidate formalizes two things relevant to discovery. First, the extensions map. Servers can now advertise which MCP extensions they support in the discovery document, before any session is established. An agent that needs the Tasks extension, for long-running work like ticket resolution or order processing, can read the discovery document and confirm support before connecting, rather than connecting and failing at capability negotiation.

Second, the relationship between discovery and the stateless architecture. The previous MCP spec used server-side sessions to hold client capabilities. With stateless MCP, that state moves to the client and to the discovery document. Every request is now self-contained; every discovery probe returns the same information regardless of which server instance responds. This is what makes auto-discovery the right foundation for the new architecture: the discovery document isn't just a convenience. It's the contract between client and cluster.

Existing posts cover the stateless migration in detail and the extensions framework changes if you want the full picture. Discovery is the layer that connects those changes to what your agents actually do at connection time.

The two-minute version

If you're short on time, here's what to ship today:

  1. Create a .well-known/mcp.json file with your server's endpoint, transport type, and auth scheme
  2. Serve it at yourdomain.com/.well-known/mcp.json with a 1-hour cache header and Access-Control-Allow-Origin: *
  3. Add a DNS TXT record at _mcp.yourdomain.com with the abbreviated metadata
  4. Run npx mcp-discover yourdomain.com to verify both channels work

The three teams from the beginning of this post all solve their problems the same way: they stop hardcoding URLs and start publishing discovery documents. The developer who built the wrong-endpoint agent finds your server by domain name. The team with the staging/production confusion queries different DNS records per environment. The team using the new MCP client connects without touching any config file.

Your agents connect faster. Your developers stop pasting stale URLs. And any compliant MCP client that comes along later finds your tools automatically.

One stable MCP URL, no stale configs

Protobox hosts your MCP server at one URL that never drifts: connect your apps, APIs, and docs, scope tools into toolsets with their own scoped URLs, and paste them into Claude, Cursor, or ChatGPT. Usage analytics show which tools agents actually call. Free tier covers your first server: 1,000 requests a month, no credit card.

Start free
DG

Founder, Protobox

Building TBD bio at Protobox — tools, testing, and observability for customer experience.

Changelog MCP y notas para devs

Actualizaciones cortas y ocasionales sobre la spec MCP, nuevas funciones de Protobox y patrones que vemos en producción. Sin marketing innecesario.

Sé de los primeros

Frequently Asked Questions