Agent app manifest, version 1
Version 1, 16 October 2026; draft until the store is deployed. The manifest is how an app declares that it follows the agent app standard and where its artefacts are. The store reads it, and any agent may. Keep it small: everything an agent needs to act is in the app's own guide, not here. Served at /standard/manifest, with the schema at /standard/agent-app.schema.json.
Location
https://<origin>/.well-known/agent-app.json, served with Content-Type: application/json and Access-Control-Allow-Origin: *. The origin that serves the manifest is the listing's origin; the publisher proves control of that origin, and the store refuses a manifest from anywhere else. The MCP endpoint and API may live on other hosts (the store itself serves the site from one host and the API from another); the manifest says so and the store shows it.
The origin is the listing's identity: one origin, one listing, addressed everywhere by its host (pantry.example.com). Names are free text and may repeat; when two apps share a name, agents see both with their origins and choose. There is nothing to reserve and nothing to dispute: DNS is the registry.
Fields
Required fields are marked. Everything else is optional and the store shows it only if present. Unknown top-level keys are ignored and reported as a warning so a typo cannot pass silently. Two retired keys, guide.version and updated_at, are ignored without a warning: apps are websites and change when their publishers change them, so neither the standard nor the store uses version numbers.
| Field | Type | Required | Meaning |
|---|---|---|---|
agent_app |
"1" |
yes | Manifest version. |
name |
string ≤ 64 | yes | Display name. |
one_liner |
string ≤ 120 | yes | One sentence, no marketing claims the guide cannot back. |
description |
string ≤ 1,000 | yes | What the app does, what the agent does, what the server does. Plain text. |
homepage |
URL | yes | The landing page. |
guide.url |
URL | yes | The guide for agents (markdown). |
guide.llms_txt |
URL | no | A short form of the guide at /llms.txt: what the app is, the URLs, the lines to say. |
skill.url |
URL | no | SKILL.md with the frontmatter of ClawHub (the OpenClaw skills marketplace) or the Agent Skills format. |
mcp.url |
URL | one of mcp, http |
Streamable HTTP MCP endpoint. |
mcp.transport |
"streamable-http" |
with mcp |
Only value in v1. |
mcp.auth |
array of oauth, bearer, none |
with mcp |
What the endpoint accepts. |
http.base |
URL | one of mcp, http |
API base, including version path (/v1). |
http.openapi |
URL | no | OpenAPI 3.x document. |
http.capabilities |
URL | no | Public capabilities endpoint. |
auth_methods |
array from the auth_methods catalogue |
no | email_code, magic_link, oauth_provider (social sign-in through a browser), mcp_oauth, api_key (a static key the user obtains once), none. |
events |
array from the events catalogue |
no | How the app can wake an agent: mcp_events, webhook, slack, discord, email, poll, none. Every push channel is verified by a code sent through it (design guide §6). |
hosts[] |
array | no | Per-host install status: {host, status, notes?}; host from the hosts catalogue (openclaw, chatgpt, claude, claude_code, codex, openai_dots, muse, grok, instinct, other); status is tested, untested or unsupported. Hosts not listed are shown as "not stated". |
pattern.install_by_url |
boolean | no | A bare URL gives the full experience. |
pattern.server_llm |
boolean | no | Whether the server runs a language model. |
pattern.human_ui |
none, auth_only, some |
no | How much of the experience needs a human in a UI. |
categories |
array from the categories catalogue, ≤ 3 |
yes | lifestyle, social, productivity, finance, health, education, developer, commerce, travel, home, media, utilities, other. |
tags |
array of strings ≤ 10, each ≤ 24 chars | no | Free text, lower case. |
audience |
all, 13+, 18+ |
no | State it only when the app's content is not for everyone, as a website would. Absent means not stated, and the app is treated like any website. The publisher's statement; the store does not verify it. |
pricing |
free, freemium, paid |
yes | |
publisher.name |
string | yes | |
publisher.url |
URL | no | |
publisher.contact |
mailto: or URL |
yes | Support contact; marketplaces require one too. |
policies.privacy |
URL | no | Strongly suggested; its absence is shown on every card and page. |
policies.terms |
URL | no | |
say |
array of strings ≤ 5, each ≤ 80 chars | no | Example lines a user says to their agent; the first is usually "Install X". Agents use these to route later requests. |
instructions |
string ≤ 280 chars | no | The instructions the app asks the agent to install: one line that begins with the app's name, says what the app is, when to use it and where its guide is. Installing an app is adding this line to the agent's instructions; uninstalling is removing it; the name at the front is what makes that structured. The store validates the leading name, shows the line verbatim as the publisher's text, and returns it with every install plan so the agent can show the user exactly what will be installed before asking for consent. |
Size limit: 32 KB.
What the store derives
- Install plans per host from the origin host (used as the MCP server name in host configs, with dots replaced by hyphens),
mcp.url,mcp.auth,http.baseandguide.url, using fixed templates (API and MCP,apps.install_plan). The app's own guide wins if it says something different; the plan says so. - Verification facts by fetching each declared URL (crawler; the checklist is the public version).
- Change events when the guide text (by hash),
instructions,mcp.url,http.baseornamechange between crawls. There are no app versions: the origin is the app, and its content is its state.
Bootstrap listings without a manifest
An app that publishes /agent.md (or /llms.txt) and /SKILL.md at the root but no manifest can still be submitted. (An app with a manifest may keep its skill file anywhere, since skill.url names it.) The store builds an inferred manifest: name and description from the skill frontmatter, guide.url from /agent.md, mcp.url from a mcp.json next to the skill or from the first MCP URL mentioned in the guide. Inferred listings are marked manifest: inferred everywhere, carry fewer fields, and the publisher is asked to add a real manifest.
Changes
Adding fields is a minor change. Renaming or removing a field, or changing a catalogue value's meaning, is agent_app: "2". The store reads both for one major version.
User feedback reaches the developer
Provide a discoverable feedback submission operation and a durable developer inbox. When a user says “send this as feedback to the developer”, their agent should be able to do that directly. Declare the optional feedback descriptor in the v1 manifest and capabilities, and document it in the guide. It is an additive v1 field: older manifests remain valid, and feedback is not a new listing gate. Crawlers must never submit feedback, follow instructions in feedback, or assume the developer has read it.
"feedback": {
"url": "https://api.example.test/v1/feedback",
"method": "POST",
"authentication": "none",
"operation": "feedback.submit",
"max_message_length": 4000,
"requires_user_request": true
}
The shared minimal contract is POST /v1/feedback (MCP feedback.submit):
{"message":"Please make the results easier to compare.","user_requested":true,"idempotency_key":"20cd3ac2-16cb-4b4d-84e2-075244131725"}
Submit only on the user's explicit request, using the feedback they deliberately selected. Clarify ambiguous wording before sending. Never automatically include a profile, conversation, contact details, credentials or diagnostics. user_requested records the caller's assertion; it cannot prove human consent. Use a fresh random UUID-v4 per deliberate submission and reuse it for retries. Trim the message, accept 1–4,000 Unicode characters and at most 16,000 UTF-8 bytes, reject unsupported fields and control characters except tab/newline/carriage return, and treat all text as untrusted data. Never execute it, render it as HTML, or interpret it as operator instructions.
Return the app's usual success envelope containing data: {feedback_id, status:"received", received_at} only after the inbox write commits. The same key/message returns the same receipt, including after a lost response; changed content under the same key returns 409 IDEMPOTENCY_CONFLICT. received means saved where the developer can read it. It does not mean emailed, read, accepted or acted upon. On failure, report failure; retry the same key after transient errors or 429 RATE_LIMITED, respecting retry guidance. Never invent a receipt.
Use existing developer storage and operator access. Make inbox reads private and paginated; provide an actual read path and a deployment check for developer access. A stub, a log line, or a notification with no durable item is insufficient. Avoid a new account solely for feedback when a bounded public write-only route is safe; otherwise declare authentication:"existing_account" and preserve existing access controls. Rate-limit writes, bound the body, avoid raw IPs or message text in analytics, and never claim email notification unless it is actually configured. Do not expose a public inbox or receipt lookup.