All posts

MCP App Discovery: Server Cards, Manifests, and .well-known Metadata

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 metadata, protocol handshakes, tools, resources, and host review.

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

Developers keep asking a few versions of the same question:

  • Where is the MCP App manifest?
  • What should go in /.well-known/mcp.json?
  • How does a host know my app has UI before it calls a tool?
  • Are MCP Server Cards the same thing as OAuth metadata?
  • Can a registry or crawler discover my MCP App without running the whole server?

Those searches are understandable because MCP Apps sit between web app packaging, MCP tool discovery, OAuth discovery, and host review. The answer is split across a few layers.

TL;DR: There is no required MCP App manifest file today. An MCP App is discovered through the MCP server itself: initialize, tools/list, _meta.ui.resourceUri, resources/read, and tool results. OAuth uses separate .well-known metadata. MCP Server Cards are a draft discovery proposal that could let clients and registries read public server metadata before connecting, but they do not replace tool metadata, resource metadata, OAuth metadata, or conformance tests.

The Discovery Flow Today

Right now, the reliable MCP App discovery path is protocol-first.

  1. A user or admin gives the host an MCP server URL, usually an HTTPS Streamable HTTP endpoint like https://example.com/mcp.
  2. The host sends initialize to negotiate the MCP protocol version and read server capabilities.
  3. The host calls tools/list to discover tool names, descriptions, schemas, annotations, and metadata.
  4. For any UI-capable tool, the host reads _meta.ui.resourceUri.
  5. The host calls resources/read for that ui:// URI.
  6. The server returns MCP App HTML plus resource metadata, including CSP, permissions, domain, and presentation hints.
  7. When the model calls the tool, the host passes tool input and result data into the iframe.

That is the app contract.

If a host never gets a valid tools/list response, it never sees your app tool. If the tool does not point at a ui:// resource, the host has no UI to render. If resources/read fails, React never mounts. If the tool result has empty content, non-UI clients get a bad fallback.

This is why MCP App conformance testing starts with discovery checks before browser automation.

Why People Search for an MCP App Manifest

The word “manifest” fits the problem. Web apps have a manifest.json. Browser extensions have manifests. App stores ask for metadata before review. AI hosts need to know what your server does before they let models call it.

MCP Apps do not currently have one canonical “app manifest” file.

Instead, different parts of the app are declared where the host needs them:

QuestionCurrent source of truth
Where do I connect?User/admin configuration, directory listing, or future server card
What MCP version and capabilities does the server support?initialize result
What tools can the model call?tools/list
Which tool opens a UI?Tool _meta.ui.resourceUri
Who can call the tool?Tool _meta.ui.visibility
What HTML should the host render?resources/read for the ui:// resource
What can the iframe load or request?Resource _meta.ui.csp and _meta.ui.permissions
How does auth work?OAuth protected resource metadata and authorization server metadata
What should non-UI clients see?Tool result content

That split can feel scattered at first, but it has a useful property: metadata lives next to the thing it controls. Tool routing lives on the tool. Sandbox policy lives on the resource. Auth discovery lives in OAuth metadata. Runtime capabilities live in the handshake.

Where Server Cards Fit

The MCP roadmap points toward better server discovery. The most concrete work in that area is the SEP-1649 draft for MCP Server Cards.

As of June 5, 2026, treat server cards as a draft proposal. The useful idea is still clear: expose public server metadata through a standard document so clients, registries, crawlers, and scanners can learn about a server before opening a full MCP session.

For HTTP servers, the draft proposes a .well-known JSON endpoint and an MCP resource version of the same information. The public metadata can include:

  • server name, title, version, and description
  • icon and documentation URLs
  • protocol version
  • transport type and endpoint path
  • server capabilities, such as tools and resources
  • required client capabilities
  • authentication requirements
  • whether tools, resources, and prompts are static or dynamic

That is pre-connection metadata. It helps a client answer “what is this server and where do I connect?” It does not replace the real MCP handshake.

A Minimal Server Card Shape for an MCP App

If you experiment with server cards, keep the document small and public.

{
  "$schema": "https://static.modelcontextprotocol.io/schemas/mcp-server-card/v1.json",
  "version": "1.0",
  "protocolVersion": "2025-06-18",
  "serverInfo": {
    "name": "invoice-review",
    "title": "Invoice Review",
    "version": "1.0.0"
  },
  "description": "Review invoices, flag exceptions, and open an interactive invoice UI.",
  "iconUrl": "https://example.com/icon.png",
  "documentationUrl": "https://example.com/docs/mcp",
  "transport": {
    "type": "streamable-http",
    "endpoint": "/mcp"
  },
  "capabilities": {
    "tools": {
      "listChanged": true
    },
    "resources": {
      "listChanged": true
    }
  },
  "authentication": {
    "required": true,
    "schemes": ["oauth2"]
  },
  "tools": "dynamic",
  "resources": "dynamic"
}

For an MCP App, tools: "dynamic" and resources: "dynamic" are often the right default because your app surface is still defined by tools/list and resources/read. You can list static tools if they are safe to publish before connection, but do not turn the server card into a duplicate of your whole server.

The server card should point toward the app. The MCP protocol should still prove the app.

What Not to Put in Discovery Metadata

Server-card metadata is meant to be easy to fetch, cache, crawl, and inspect. Assume it is public.

Do not include:

  • API keys, bearer tokens, client secrets, or signing keys
  • internal hostnames, private IPs, staging URLs, or VPN-only paths
  • customer IDs, tenant IDs, user IDs, or session data
  • full proprietary workflows that do not need to be public
  • tool descriptions that reveal sensitive operations before auth
  • OAuth client secrets or reviewer test credentials

If a tool list is sensitive, mark tools as dynamic and expose the details only after the normal authenticated MCP connection. Discovery should reduce setup friction, not publish private state.

OAuth Metadata Is Separate

OAuth discovery answers a different question: how does the host authenticate users for this MCP server?

For protected MCP Apps, hosts commonly need:

  • protected resource metadata at /.well-known/oauth-protected-resource
  • authorization server metadata at /.well-known/oauth-authorization-server or /.well-known/openid-configuration
  • supported scopes
  • token endpoint details
  • dynamic client registration support when the host requires it

That metadata belongs to auth. It does not describe your UI resources, tool schemas, app display modes, or public server overview.

Think of the layers this way:

LayerPurpose
Server cardPublic pre-connection server overview
OAuth metadataAuth server and scope discovery
initializeProtocol version and capability negotiation
tools/listModel-callable and app-callable tool contract
resources/readApp HTML and iframe metadata
Tool resultRuntime data and fallback text

Keep those layers separate and your debugging gets easier. A connection error is not the same as an OAuth discovery error. An OAuth success is not proof that the app UI resource is valid.

How MCP App UI Discovery Actually Works

The key field for UI is still _meta.ui.resourceUri.

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

The host reads that tool metadata and then fetches the resource:

{
  "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
    }
  }
}

That resource metadata tells the host how to sandbox and present the iframe. A server card can say “this MCP server has tools and resources.” It cannot tell the host which UI to render for a specific tool call. That link still belongs on the tool.

How to Prepare Without Waiting for the Spec

You do not need to wait for server cards to make your MCP App easier to discover and safer to review.

Start with the protocol surface:

  • Give every app tool a stable name, title, description, schema, and annotations.
  • Put _meta.ui.resourceUri on every UI-capable tool.
  • Put _meta.ui.visibility on app-only tools so model discovery stays clean.
  • Return useful content from UI tools, even when structuredContent drives the iframe.
  • Keep resource metadata complete: CSP, permissions, stable domains, and border hints where needed.
  • Serve OAuth discovery metadata if your app needs auth.
  • Publish clear docs for your MCP endpoint, auth scopes, and expected user flows.

Then add tests:

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

test('ui tools are discoverable and resources are readable', async ({ mcp }) => {
  const tools = await mcp.listTools();
  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 resource = await mcp.readResource(resourceUri);
    const content = resource.contents[0];

    expect(content.mimeType).toBe('text/html;profile=mcp-app');
    expect(content.text).toContain('<!doctype html>');
  }
});

If you also serve a server card, test it like any other public contract:

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

test('server card is public metadata only', async ({ request }) => {
  const response = await request.get('/.well-known/mcp/server-card.json');
  expect(response.ok()).toBe(true);
  expect(response.headers()['content-type']).toContain('application/json');

  const card = await response.json();
  expect(card.transport.endpoint).toBe('/mcp');
  expect(JSON.stringify(card)).not.toMatch(/secret|token|api_key/i);
});

Run these tests before E2E tests. A rendered browser test cannot clearly explain that your server card has a stale endpoint or your tool points at a missing resource.

How sunpeak Helps

In a sunpeak project, most of the app discovery contract is generated from conventions:

  • tools are discovered from src/tools/
  • resources are discovered from src/resources/
  • a tool’s resource field links it to a resource directory
  • the dev server serves the MCP endpoint and app resources
  • the inspector renders the same tool/resource chain locally
  • the test fixtures can call MCP protocol methods and render resources in host replicas

That does not remove the need to understand discovery. It gives you one place to test it.

Use sunpeak inspect against any MCP server to verify the app surface manually, then put the conformance checks in CI. If server cards become part of your deployment process, add them as another small test layer next to OAuth metadata and tool discovery.

A Practical Checklist

Before you publish or submit an MCP App, check the full discovery path:

  • Public MCP endpoint is reachable over HTTPS.
  • initialize returns the expected protocol version and capabilities.
  • tools/list includes every app tool with useful names and descriptions.
  • UI tools include _meta.ui.resourceUri.
  • App-only tools are hidden from the model with _meta.ui.visibility.
  • Every ui:// resource can be read.
  • Every app resource uses text/html;profile=mcp-app.
  • Resource _meta.ui includes CSP, permissions, domain, and presentation fields where needed.
  • UI tool results include useful content for non-UI clients.
  • OAuth metadata is public and valid if auth is required.
  • Optional server-card metadata contains only public server facts.
  • Discovery checks run in CI before browser tests.

That is the difference between “my React component works on localhost” and “a host can find, trust, render, and recover from this app.”

Start with the protocol path. Add server cards when they help your distribution and tooling story. Do not let the search for a manifest distract from the metadata the hosts already use today.

Get Started

Documentation →
npx sunpeak new

Further Reading

Frequently Asked Questions

Does an MCP App need a manifest file?

Today, an MCP App does not need a separate manifest file in the way a web app uses manifest.json. The app contract lives in the MCP server: initialize advertises server capabilities, tools/list advertises tool schemas and metadata, _meta.ui.resourceUri links tools to UI resources, resources/read returns the app HTML and resource metadata, and OAuth discovery documents describe authentication when needed.

What is an MCP Server Card?

An MCP Server Card is a proposed discovery document for HTTP-based MCP servers. The draft proposal describes a JSON document served from a .well-known URL and as an MCP resource so clients, registries, crawlers, and security scanners can learn basic server metadata before running a full MCP initialization handshake.

Is /.well-known/mcp/server-card.json required for MCP Apps?

No. As of June 5, 2026, server cards are still a draft proposal, not a required part of MCP App deployment. Existing MCP Apps still work through the normal connection flow: host configuration, initialize, tools/list, resources/read, and tool calls. Publishing a server card can still be useful for discovery experiments, documentation, and future readiness.

What should an MCP App expose in a server card?

Expose public metadata only: server name, title, version, description, documentation URL, icon URL, transport type, endpoint path, supported MCP capabilities, authentication requirements, and whether tools or resources are dynamic. Do not include API keys, bearer tokens, private endpoints, customer-specific data, internal network names, or full business logic.

What is the difference between OAuth metadata and an MCP Server Card?

OAuth metadata tells a host how to authenticate: protected resource metadata, authorization server metadata, scopes, and token handling. An MCP Server Card tells a client or crawler what the MCP server is, where to connect, which protocol version it supports, and what public capabilities it exposes. They are related discovery documents, but they answer different questions.

How do hosts discover MCP App UI resources?

A host lists tools, finds a UI-capable tool with _meta.ui.resourceUri, reads the referenced ui:// resource, checks the resource MIME type and _meta.ui fields, then renders the returned HTML in a sandboxed iframe when the tool runs. Server cards can point clients toward the server, but tool and resource metadata still define the actual MCP App UI.

How should I test MCP App discovery?

Test the discovery chain in layers. Fetch any public well-known metadata you serve. Run initialize and tools/list against the MCP endpoint. Assert that every UI tool has _meta.ui.resourceUri, read every referenced resource with resources/read, verify the MCP App HTML MIME type, and confirm each UI tool returns useful text content for clients that do not render UI.