Skip to main content
All posts

MCP App Discovery: Server Cards, Manifests, and .well-known Metadata (August 2026)

Abe Wheeler
MCP AppsMCP App FrameworkMCP App TestingChatGPT AppsChatGPT App FrameworkChatGPT App TestingClaude ConnectorsClaude Connector FrameworkMCP Server CardsDiscovery
MCP App discovery starts with a server URL, then moves through public metadata, protocol discovery, tools, resources, and host review.

MCP App discovery starts with a server URL, then moves through public metadata, protocol discovery, tools, resources, and host review.

MCP App discovery has several answers because the phrase covers several jobs. A directory needs a public description before it connects. An MCP client needs to negotiate a protocol version and capabilities. A host needs the current tool and resource catalogs. A protected server needs to tell the host where authentication starts.

Those jobs use different documents and protocol methods. Treating them as one manifest creates stale metadata and confusing failure reports.

TL;DR: MCP Apps do not require one manifest file. Core MCP 2026-07-28 uses server/discover for server identity and capabilities, while older deployments use initialize. The live app surface still comes from tools/list, tool _meta.ui.resourceUri, and resources/read. Server Cards and domain AI Catalogs remain experimental. OAuth keeps its own .well-known metadata.

Discovery Is Four Different Jobs

The most useful way to reason about discovery is to ask what the reader needs to learn.

JobCurrent sourceAuthority
Find a server and its endpointUser or admin configuration, a directory, or an experimental Server Card or AI CatalogAdvisory until the client connects
Learn protocol versions and capabilitiesserver/discover in MCP 2026-07-28, or initialize in 2025-era MCPLive server response
Find tools and app UI resourcestools/list, _meta.ui.resourceUri, and resources/readLive server response
Start authorizationHTTP 401, WWW-Authenticate, protected resource metadata, and authorization server metadataLive HTTP and OAuth metadata

No one document is authoritative for every row. A card can advertise an endpoint that has moved. A cached tool list can be private to one user. A successful OAuth flow says nothing about whether the UI resource has valid HTML.

This layered model also gives tests a useful order. Verify the address, negotiate the protocol, inspect the live catalogs, test authorization, then render the app.

Core Discovery Changed in MCP 2026-07-28

The MCP 2026-07-28 specification removed the core initialize request, initialized notification, and core session lifecycle. A 2026 server must support server/discover; support is optional for clients.

A client can send server/discover with per-request metadata that identifies its protocol version, implementation, and capabilities. A successful response looks like this:

{
  "jsonrpc": "2.0",
  "id": "discover-1",
  "result": {
    "resultType": "complete",
    "supportedVersions": ["2026-07-28"],
    "capabilities": {
      "tools": {},
      "resources": {}
    },
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "example-org/invoice-review",
        "version": "2.3.0"
      }
    },
    "instructions": "Search for an invoice before opening a review.",
    "ttlMs": 3600000,
    "cacheScope": "public"
  }
}

This response answers a narrow set of questions:

  • Which protocol versions can this server use?
  • Which protocol features does it support?
  • What name, version, and optional instructions does it report?
  • How long may this exact response be cached, and is it public or private?

It does not list tool names or UI resources. Clients still call tools/list and read the selected resources. Server identity in io.modelcontextprotocol/serverInfo is self-reported, so clients must not use it as proof of ownership or as an authorization decision.

The 2026 protocol puts similar ttlMs and cacheScope fields on list and read results. Use cacheScope: "public" only when every authorized user receives the same data. A tenant-specific tool catalog, user-specific resource, or permission-filtered response is private.

Supporting 2025 and 2026 clients

Production hosts do not move to a new protocol revision at the same time. Many deployed hosts still use the 2025 initialization flow:

  1. Send initialize with a requested protocol version and client capabilities.
  2. Read the server’s selected version, identity, capabilities, and instructions.
  3. Send notifications/initialized.
  4. Call tools/list, resources/list, and other methods.

A dual-era stdio client can probe with server/discover, then fall back to initialize when the method is unavailable. A 2026 client may instead try its intended request first and retry after UnsupportedProtocolVersionError.

This distinction matters for sunpeak projects today. sunpeak 0.20.x tests the 2025-11-25 MCP lifecycle used by current hosts. The --stateless flag removes stored transport state within that lifecycle; it does not turn the server into a 2026-07-28 implementation. Use the sunpeak MCP 2026 guide when you plan a wire-protocol migration, and keep host compatibility tests for the versions you ship.

Server Cards Are Experimental Pre-Connection Metadata

The current MCP Server Card extension tracks the SEP-2127 proposal. It is experimental, which means clients and servers can prototype against it, but it is not a required part of MCP or MCP Apps.

The proposal is much smaller than earlier Server Card drafts. A card describes public identity and remote connection options. It intentionally leaves out tool lists, resource lists, auth capabilities, and local installation packages because those facts either belong to the live protocol or require a separate distribution system.

A current example looks like this:

{
  "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json",
  "name": "example-org/invoice-review",
  "version": "2.3.0",
  "title": "Invoice Review",
  "description": "Review invoices and open an interactive exception workflow.",
  "websiteUrl": "https://example.com/invoice-review",
  "remotes": [
    {
      "type": "streamable-http",
      "url": "https://mcp.example.com/mcp",
      "supportedProtocolVersions": ["2026-07-28"]
    }
  ]
}

The proposal requires that $schema value. As of August 26, 2026, the static schema URL still returns 404; use the schema checked into the extension repository for validation until the static copy is published. Do not remove the required $schema field from the card in the meantime.

For a Streamable HTTP endpoint at https://mcp.example.com/mcp, the proposal recommends serving the card at:

https://mcp.example.com/mcp/server-card

Use the application/mcp-server-card+json media type. The extension’s deployment guidance also recommends HTTPS, cache headers with an ETag, and cross-origin access for GET requests so directories and browser-based tools can inspect the card.

A card is a hint, not proof. Discovery software should validate its JSON schema and then verify the endpoint against server/discover or the negotiated legacy handshake. Never grant access because a card claims a name, URL, or protocol version.

Where .well-known Fits Now

Earlier Server Card drafts placed each card under a .well-known URL. The current experimental proposal does not recommend that path for an individual server because one domain can host several MCP endpoints.

Domain-wide discovery uses a separate experimental AI Catalog at:

/.well-known/ai-catalog.json

An AI Catalog can point to one or more Server Cards:

{
  "specVersion": "1.0",
  "entries": [
    {
      "identifier": "urn:ai:example.com:mcp:invoice-review",
      "type": "application/mcp-server-card+json",
      "url": "https://mcp.example.com/mcp/server-card"
    }
  ]
}

The catalog uses application/ai-catalog+json. Entries can link to cards or include card data inline. This is useful for organizations that operate several servers under one domain, but the catalog and card are still experimental contracts. Keep your configured endpoint and live MCP responses working without them.

The word “manifest” is still useful in product discussions, but be precise in code and docs. A Server Card is public pre-connection metadata. An AI Catalog is a domain index. Neither one is the MCP App’s live tool and UI manifest.

OAuth Metadata Answers a Different Question

OAuth discovery starts after a protected MCP endpoint rejects an unauthenticated request. The server should return an actual HTTP 401 response with a WWW-Authenticate header that points to protected resource metadata:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp"

That protected resource document identifies the exact MCP resource and its authorization servers. The host can then read authorization server metadata from /.well-known/oauth-authorization-server or /.well-known/openid-configuration.

Do not return a successful HTTP response with an OAuth error hidden inside tool content. Hosts use the HTTP challenge to start authorization. Also keep access tokens, client secrets, tenant data, and private endpoints out of Server Cards and AI Catalogs.

The discovery documents now have clear boundaries:

Document or methodAnswers
Server CardWhat public server is this, and where can I connect?
AI CatalogWhich public AI endpoints or cards does this domain advertise?
server/discover or initializeWhat does the connected server support now?
OAuth metadataHow can this client obtain authorized access?
tools/list and resources/readWhat can this authorized client call and render?

MCP App UI Discovery Still Starts at tools/list

MCP Apps add UI linkage to the live tool catalog. A UI-capable tool uses nested _meta.ui.resourceUri:

{
  "name": "show-invoice-review",
  "title": "Show Invoice Review",
  "description": "Open the invoice review interface for one invoice.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "invoiceId": { "type": "string" }
    },
    "required": ["invoiceId"]
  },
  "_meta": {
    "ui": {
      "resourceUri": "ui://invoice-review/main.html",
      "visibility": ["model", "app"]
    }
  }
}

The older _meta["ui/resourceUri"] spelling remains a compatibility field, but new code should use nested _meta.ui.resourceUri. The host reads that URI and expects HTML with the MCP App MIME type:

{
  "uri": "ui://invoice-review/main.html",
  "mimeType": "text/html;profile=mcp-app",
  "text": "<!doctype html><html>...</html>",
  "_meta": {
    "ui": {
      "csp": {
        "connectDomains": ["https://api.example.com"],
        "resourceDomains": ["https://cdn.example.com"]
      },
      "prefersBorder": true
    }
  }
}

The tool link matters more than resources/list. The MCP Apps specification permits a tool-linked UI resource to be readable without appearing in the general resource catalog, so hosts should read each tool’s URI directly. A Server Card cannot tell the host which UI belongs to one tool call.

After rendering, the app and host use the MCP Apps View protocol. Its ui/initialize request is separate from the removed core MCP initialize request. The View lifecycle remains necessary even when the server side uses MCP 2026-07-28.

See the tool and resource contract for the full linkage and MCP App resource metadata for CSP, permissions, domain, and presentation fields.

Test the Chain, Not Just the Card

A good discovery test proves that live tools point to readable app resources. With the current sunpeak test API, the core check is:

import { test, expect } from 'sunpeak/test';

test('ui tools point to readable MCP App resources', async ({ mcp }) => {
  const { tools } = await mcp.listTools();
  const { resources } = await mcp.listResources();
  const uiTools = tools.filter((tool) => tool._meta?.ui?.resourceUri);

  expect(uiTools.length).toBeGreaterThan(0);

  for (const tool of uiTools) {
    const resourceUri = tool._meta!.ui!.resourceUri!;
    expect(resourceUri).toMatch(/^ui:\/\//);
    expect(tool.description).toBeTruthy();

    const listed = resources.find((resource) => resource.uri === resourceUri);
    if (listed) {
      expect(listed.mimeType).toBe('text/html;profile=mcp-app');
    }

    const html = await mcp.readResource(resourceUri);
    expect(html.toLowerCase()).toContain('<!doctype html>');
  }
});

This test does not require every linked UI resource to appear in resources/list. It does require each link to resolve, which is what the host needs.

If you publish a Server Card, test its separate public contract:

import { test, expect } from '@playwright/test';

test('server card is valid public metadata', async ({ request }) => {
  const response = await request.get('/mcp/server-card');

  expect(response.ok()).toBe(true);
  expect(response.headers()['content-type']).toContain(
    'application/mcp-server-card+json',
  );

  const card = await response.json();
  expect(card.$schema).toBe(
    'https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json',
  );
  expect(card.remotes[0].url).toMatch(/^https:\/\//);
  expect(JSON.stringify(card)).not.toMatch(/secret|bearer|api[_-]?key/i);
});

Add protocol-specific tests for every wire version you support. For 2026, assert resultType, supportedVersions, ttlMs, and cacheScope in server/discover. For 2025 hosts, keep the initialize and notifications/initialized path under test. Then exercise the UI in host replicas because valid JSON does not prove that the sandbox policy, bridge, and tool result work together.

A Deployment Checklist

  • The configured MCP endpoint is reachable over HTTPS.
  • Every shipped protocol version has a negotiation test.
  • server/discover cache scope matches whether results vary by user or tenant.
  • tools/list includes stable names, useful descriptions, schemas, and annotations.
  • Every UI tool has nested _meta.ui.resourceUri.
  • Every linked ui:// resource is readable, whether or not it appears in resources/list.
  • Every app resource uses text/html;profile=mcp-app and valid _meta.ui policy.
  • Tool results include useful text content for clients that do not render UI.
  • Protected endpoints return HTTP 401 with a valid OAuth resource metadata pointer.
  • Optional Server Cards and AI Catalogs contain public facts only and pass their schemas.
  • Card endpoints use the right media type, CORS policy, caching, and ETag behavior.
  • CI renders the app in replicas of each host you support.

sunpeak discovers project tools and resources from src/tools/ and src/resources/, then runs the same protocol and UI path through its MCP App inspector and test fixtures. That makes it practical to keep protocol negotiation, tool linkage, resource reads, auth, and host rendering in one CI suite.

Publish a Server Card or AI Catalog when a directory or internal inventory can use it. Keep the live MCP contract as the source of truth, and test each discovery layer on its own terms.

Get Started

Documentation →
npx sunpeak new

Further Reading

Frequently Asked Questions

Does an MCP App need a manifest file?

No single manifest is required. A host discovers the live app contract through MCP: server/discover on the 2026-07-28 protocol or initialize on 2025-era protocols, then tools/list, each tool's _meta.ui.resourceUri, and resources/read. OAuth metadata handles authentication. Experimental Server Cards can publish a small public description before a client connects.

What replaced initialize in MCP 2026-07-28?

The 2026-07-28 core protocol removed initialize, initialized, and core sessions. A client can call server/discover to get supported versions, capabilities, server identity, instructions, and cache policy. Because server/discover is optional for clients, a client can also attempt another request and retry after an UnsupportedProtocolVersionError.

What is an MCP Server Card?

A Server Card is an experimental JSON description of an MCP server. The current SEP-2127 work describes identity, version, website, and remote connection details. It does not list the server's tools or replace a live server/discover, tools/list, or resources/read response.

Should a Server Card live under /.well-known?

The current experimental proposal recommends serving an individual card next to its Streamable HTTP endpoint, such as /mcp/server-card, not under /.well-known. Domain-wide discovery uses the separate experimental /.well-known/ai-catalog.json document, whose entries can link to Server Cards.

What is the difference between OAuth metadata and a Server Card?

OAuth metadata tells a host how to authorize access to a protected MCP resource. A Server Card tells discovery software what a server is and where a remote endpoint lives. A 401 response can point to /.well-known/oauth-protected-resource metadata; the card must not contain tokens or act as an authorization decision.

How do hosts discover MCP App UI resources?

A host calls tools/list, finds _meta.ui.resourceUri on a UI tool, and reads that ui:// URI with resources/read. The returned resource uses text/html;profile=mcp-app and carries _meta.ui sandbox and presentation policy. A tool-linked UI resource may be readable without appearing in resources/list.

Is the MCP Apps ui/initialize request affected by core MCP 2026?

No. The MCP Apps View protocol has its own ui/initialize exchange between the rendered app and its host. That exchange is separate from the core MCP initialize method removed in 2026-07-28, so the matching names should not be treated as the same lifecycle step.

How should I test MCP App discovery?

Test each layer separately. Check any public Server Card or AI Catalog, negotiate the protocol version, list tools, read every tool-linked UI resource, validate MIME type and resource metadata, exercise OAuth challenges, and render the UI in each target host replica. Keep these checks in CI because a valid card does not prove that the live app contract works.