MCP Registry
Clients need to find your server before they can connect. @FrontMcp({ registry }) serves a domain proof for the MCP Registry, a server card describing your endpoint, and an AI Catalog linking to the card. frontmcp registry publish builds and validates the listing from your project, proves its namespace, and publishes it.
@FrontMcp({ info, apps, registry: { name: "com.example/help-desk", description: "Search support tickets" } })
frontmcp registry keygen
frontmcp registry publish --dry-run
frontmcp registry publish
frontmcp registry status deprecated --server-version 1.0.0 --message "Use 2.x"Reference
@FrontMcp({ registry })
New in 1.10.0. Leave registry out to serve none of these documents. It describes the server; the CLI's separate frontmcp.config.registry says where to publish it.
| Option | Type | Default | Description |
|---|---|---|---|
name | string | none | Registry name, like "com.example/help-desk": a reverse-DNS namespace and a server name, separated by /. Each part has at least two characters; the whole name has at most 200. Enables the card and catalog. |
description | string | none | 1–100 characters. Required when name is set. |
repository | { url: string, source: "github" | "gitlab", subfolder?: string } | none | The source repository, with a path inside a monorepo if needed. |
publicKey | string | from the environment | Base64 Ed25519 public key (32 bytes), or a complete v=MCPv1; k=...; p=... record. Complete records accept ed25519 or ecdsap384 (49 bytes) and are served as given, with surrounding whitespace removed. |
publicKeyEnv | string | "MCP_REGISTRY_PUBLIC_KEY" | Environment variable read on each proof request when publicKey isn't set. |
serverCard | boolean | true when name is set | Serve the server card. false also disables the catalog. |
aiCatalog | boolean | true when the card is served | Serve the catalog linking to the card. |
An invalid option fails validation at startup. An invalid environment key logs a warning once for that variable and serves no proof. Put only the public key on the server: the CLI keeps the private key for publishing.
Documents served
These routes are public, with the server's IP filter and global rate limit still applied. They work on the Node server, serverless HTTP handlers, createFetchHandler() and @frontmcp/edge.
| Request | Response |
|---|---|
GET /.well-known/mcp-registry-auth | text/plain; charset=utf-8, v=MCPv1; k=ed25519; p=<key> for a bare key. Always at the host's root, regardless of http.entryPath. Without a valid key, 404. |
GET <entryPath>/server-card | application/mcp-server-card+json: the SEP-2127 card. name, description and repository come from registry; version, title, websiteUrl and icons come from info. remotes[0] names the server's Streamable HTTP URL and supported protocol versions. |
OPTIONS <entryPath>/server-card | 204. The card's CORS allows any origin (*), even without http.cors. |
GET /.well-known/ai-catalog.json | application/ai-catalog+json, with specVersion: "1.0" and an entry linking to the card. com.example/help-desk becomes urn:air:example.com:mcp:help-desk. |
The card includes only HTTPS websiteUrl and icons; icon URLs longer than 255 characters and titles longer than 100 characters are omitted. It has an ETag; If-None-Match returns 304 when it matches. Set FRONTMCP_PUBLIC_URL to the public base URL behind a proxy. With that URL pinned the card sends Cache-Control: public, max-age=3600; otherwise it uses the request's origin and sends no-store.
Caveats
- With
splitByApp, or when every app isstandalone, there is no server-level endpoint to describe: only the domain proof is served. create(),createDirect()andconnect()in its default mode serve in process and have no HTTP endpoint for these documents. Configured Registry flows are still registered in their scopes.- Changed in 1.10.0: before, a custom route could use
<entryPath>/server-card. It is now reserved forhttp.routes, even withregistryunset./.well-known/*was already reserved. Move a custom route that uses the card's path. - The browser build doesn't read the public-key environment variable. Pass
registry.publicKeywhen running a fetch handler in a page.
Usage
Serving the proof and discovery documents
The tests mount the help desk at /mcp for clients and /mcp/server-card for its card; the Playground's client uses the default root endpoint. The proof and catalog stay at the host's root in both cases. The public key here is a test fixture; deploy with the public key printed by frontmcp registry keygen.
import { App, FrontMcp, Tool, ToolContext } from "@frontmcp/sdk";
@Tool({ name: "count_open_tickets", description: "Count open support tickets", inputSchema: {} })
class CountOpenTickets extends ToolContext {
async execute() { return { open: 3 }; }
}
@App({ id: "help-desk", name: "Help Desk", tools: [CountOpenTickets] })
export class HelpDeskApp {}
export const publicKey = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
export const config = {
info: { name: "help-desk", title: "Help Desk", version: "1.0.0", websiteUrl: "https://desk.example.com" },
apps: [HelpDeskApp],
http: { cors: { origin: ["https://desk.example.com"] } },
registry: { name: "com.example/help-desk", description: "Search support tickets", publicKey },
};
@FrontMcp(config)
export default class Server {}Starting FrontMCP in your browser…
FrontMCP starts when this example comes into view.
Publishing a listing
- Run
frontmcp registry keygen. It writes a 64-character Ed25519 hex seed to~/.frontmcp/keys/mcp-registry.key, with file mode0600, and prints the public key and DNS TXT record.--out <file>changes the path;--forcereplaces an existing key. - Set
MCP_REGISTRY_PUBLIC_KEYon your deployed server to the printed public key, and setFRONTMCP_PUBLIC_URL=https://example.com. Configureregistry.nameanddescriptionas above, and redeploy. - Check
https://example.com/.well-known/mcp-registry-auth. It must return200with the record, without a redirect, on port 443; the Registry reads at most 4096 bytes. - Configure the deployed endpoint in
frontmcp.config.ts, then preview and publish:
import { defineConfig } from "frontmcp";
export default defineConfig({
name: "help-desk",
entry: "./src/main.ts",
registry: { url: "https://example.com/mcp" },
});frontmcp registry publish --dry-run > server.json
frontmcp registry publish --file server.jsonpublish builds server.json from @FrontMcp({ info, registry }) and package.json. Description, version and repository fall back to package.json; the decorator requires a description when registry.name is set. --url overrides registry.url. Set registry.packages: ["npm"] to also list the package's name and version with a stdio transport. An npm listing needs "mcpName": "com.example/help-desk" in the published package's package.json; publish checks the local one first.
Both local checks and POST /v0.1/validate run, including on a dry run. --dry-run writes only the listing to stdout and diagnostics to stderr; it skips login and publishing. --file publishes that JSON as supplied, with validation. Each name@version can be published once, even after deletion: use a new version for the next release.
Choosing how to prove the namespace
publish and status share these authentication methods:
--auth | Proof | Namespace granted |
|---|---|---|
http (default) | The record at https://<domain>/.well-known/mcp-registry-auth | The exact host: example.com grants com.example/*. |
dns | The same record in a TXT record at the domain apex | That domain and its subdomains: com.example/* and com.example.*/*. |
github-oidc | A GitHub Actions ID token; no key or domain proof | io.github.<owner>/*. Give the job permissions: { id-token: write, contents: read }. |
--domain defaults to the namespace reversed: com.example.mcp/help-desk uses mcp.example.com. A github.io domain needs GitHub OIDC. HTTP and DNS login read the private key from --key-file, then MCP_REGISTRY_PRIVATE_KEY, then keygen's default file; both a hex seed and an Ed25519 PEM work. In CI, put the file's content in a secret and expose it as MCP_REGISTRY_PRIVATE_KEY. Keys and login tokens aren't printed.
--registry selects a different Registry, overriding frontmcp.config.registry.registryUrl. It defaults to https://registry.modelcontextprotocol.io; use https://staging.registry.modelcontextprotocol.io to try publishing. --entry selects the entry file instead of the config's entry or auto-detection. The CLI reference lists every flag.
Changing a listing's status
frontmcp registry status deprecated --server-version 1.0.0 --message "Use 2.x"
frontmcp registry status deleted --server-version 1.0.0
frontmcp registry status active --server-version 2.0.0
frontmcp registry status deprecated --all-versions --message "Moved to com.example/support"Pass exactly one of --server-version and --all-versions; --version prints the CLI's version. --message is at most 500 characters and goes only with deprecated or deleted. --name supplies the registry name when you aren't reading it from a project.
Troubleshooting
The proof answers 404
Set registry and a valid public key, in publicKey or its environment variable. The proof needs no registry.name. An invalid environment value logs MCP Registry domain proof not served: …; an invalid decorator key fails validation. Check the host's root path, even when MCP uses /mcp.
The card answers 404
Set registry.name and description, and leave serverCard enabled. The card describes only the server-level endpoint: splitByApp and servers with only standalone apps have none. Disabling the card disables the catalog too.
The card names an internal host or http://
Set FRONTMCP_PUBLIC_URL to the public base URL behind the proxy. Without it the card uses the request's origin and stays uncached. The setting doesn't apply in a browser page.
registry.description is required when registry.name is set
Add a 1–100 character description to the registry option, alongside name.
registry.publicKey must be a base64 Ed25519 public key or a "v=MCPv1; k=...; p=..." record
Use the public key keygen prints, not the private hex seed. A bare Ed25519 key is padded base64 encoding 32 bytes. Complete records must use a supported algorithm and the matching key length.
registry.name must be "<namespace>/<name>" in reverse-DNS form, e.g. "com.acme/weather"
Use a namespace and server name separated by /, with at least two characters in each. The namespace accepts letters, digits, dots and hyphens; the server name also accepts underscores. Each starts and ends with a letter or digit.
registry.publicKeyEnv must be an environment variable name
Use letters, digits and underscores, starting with a letter or underscore. The default is MCP_REGISTRY_PUBLIC_KEY.
The HTTP proof is not ready: …
The CLI fetches the proof before login. Serve it on the exact HTTPS host the namespace names, without redirects, and with the public key matching the private key used to log in. See publishing errors.