Manifest spec
An agent manifest describes everything an agent is — its prompt, its tools, how it reaches a client, and what credentials it needs. A manifest only describes; it never ships executable code.
Core fields
Every manifest carries a small core: a name, an optional description, the system prompt, example prompts, and the tools the agent can use. A plain recipe needs only these; a tool-using (doer) agent adds the layer below that lets it actually do things — tool actions, credential slots, guardrails, delivery, and client targets. You rarely write a manifest by hand: the submit builder and the GitHub/manifest importer assemble and validate it for you.
| Field | Type | Notes |
|---|---|---|
name | string | 3–80 characters. |
description | string? | Up to 4000 characters. |
system_prompt | string | The agent’s instruction set (50–20,000 characters). |
tools | Tool[] | Up to 40 tools (see below). |
example_prompts | string[] | 1–5 example prompts that show the agent in use. |
compatibility | object? | Optional per-client version hints (Claude / ChatGPT / Gemini). |
category is a loose hint, not a required exact slug. FindAgent resolves it against its two-axis taxonomy — a primary discipline plus an optional industry, each with a subcategory — so a plain "finance" lands on the Finance & Accounting discipline rather than failing to match. Importers that can name the axes may send a taxonomy object instead. The full shape (kind, tools, credential slots, guardrails) is also auto-detected on import, so a tool-using manifest is brought in completely even when it doesn't spell out a schema version.Listing metadata
Beyond the executable core, a manifest may carry the marketplace listing fields that decide how the agent is presented and found. All are optional — the submit builder lets you fill or edit any of them — but if you include them in the manifest, the importer reads them so you don't retype what you already wrote. Each also accepts a couple of common aliases, so a manifest exported from another tool imports cleanly.
| Field | Type | Also accepts | Notes |
|---|---|---|---|
tagline | string? | — | A one-line pitch shown on the card (up to 140 characters). |
category | string? | category_slug · primary_category | The primary category hint (resolved to a discipline — see above). |
additional_category_slugs | string[]? | additional_categories · subcategories | Extra category hints (e.g. an industry). Up to 8. |
tags | string[]? | — | Free-form search tags. Up to 16. |
llms | string[]? | — | Legacy client-target hint. You do NOT need to set this — a hosted agent connects to every MCP client over the gateway, so it defaults to the universal set. Kept for back-compat; unknown values are dropped. |
industry / discipline | string? | taxonomy | Name the two axes explicitly instead of a single category hint. |
version | string? | — | A self-described release version (semver, e.g. "1.0.0"). FindAgent tracks the authoritative version separately — this is descriptive metadata. |
changelog | string? | release_notes | Human release notes for this version (up to 10,000 characters). |
A manifest may also carry a metadata object with the professional trust + support links buyers look for. All fields are optional, and every URL and the icon must be an https:// URL. When you submit, an invalid field rejects the submission so you can fix it; when a manifest is imported, an invalid metadata block is simply dropped from the prefill for you to re-enter.
| metadata field | Type | Notes |
|---|---|---|
license | string? | A short license id, e.g. "MIT" (≤64 chars). |
homepage | string? | Project homepage. Must be an https:// URL (≤500 chars). |
docs_url | string? | Documentation link. https:// only (≤500 chars). |
support_url | string? | Support / issues link. https:// only (≤500 chars). |
support_email | string? | A support email address. |
icon | string? | Listing icon URL. https:// only (≤500 chars). |
author / maintainer | object? | A { name, url? } credit. name is required (≤120 chars); url (optional) must be https://. |
Tools
A tool is a named capability the agent can call. A tool carries an MCP-shaped name (1–64 chars, a-zA-Z0-9_-, unique within the manifest), a description (10–500 chars — a shorter one fails validation), an optional JSON-Schema input_schema / output_schema, an optional response_kind (text / json / binary), optional behaviour annotations, and an optional action binding that says what it does when called. A manifest carries up to 40 tools.
Annotations map directly onto how clients and runtimes gate a tool: readOnlyHint, destructiveHint, idempotentHint, and openWorldHint — plus the FindAgent framing hints interactiveHint and execution (client / app) that drive the interactive / app-only tool types. A source that marks a mutating action readOnlyHint: true is overridden — the security floor wins.
Actions
A tool's action is the only thing that does work, and it comes from a fixed, auditable set of binding kinds — never arbitrary code:
- http — an HTTP request. Declares
method(one ofGET/POST/PUT/PATCH/DELETE), aurl(which may contain{param}placeholders bound from the tool input), optionalheadersandbody_template, and an optionalauth_refpointing at a credential slot. - prompt-template — a templated prompt the runtime fills and returns. A powerful pattern: have the step return a refusal token instead of an answer when its input is insufficient, so “don't answer without enough to go on” becomes a value the step can return rather than mere prompt advice. Useful tokens creators return:
NO POLICY FOUND,INSUFFICIENT SOURCE,NO USER-FACING CHANGE,INSUFFICIENT CONTEXT. A downstream step (or the caller) can branch on the token instead of acting on a low-confidence answer.
Multi-agent orchestration (one agent calling another) is served by a Department, not a tool action.
Credential slots
When an action needs a secret, the manifest declares a credential slot and the action references it by auth_ref. Each slot has a ref, a human label, an optional env var name, and — importantly — allowed_hosts:
allowed_hostsbinds the secret to an audience. The runtime only attaches the credential when the request's destination host matches (exact or subdomain).- A slot with no allowed host is refused — a credential without an audience is a cross-host exfiltration risk.
typedescribes the value shape so the install UI renders the right input:string(plain text, the default),secret(masked), orjson(a JSON blob — e.g. a service-account key file). Ajsonkey that must be signed into a token (a Google/GCP service-account JWT) only works for acode-bundleor hosted agent whose code can perform the signing — a pure declarativehttpdoer can attach a credential but cannot sign a JWT, so reach forcode-bundlewhen the API needs signed short-lived tokens. This also changes which hosts you must allow: a plaintype: secretAPI key on a Google API (e.g. a Gemini key hittinggenerativelanguage.googleapis.com) reaches the data host directly, so that one host is enough. Atype: jsonservice-account key first exchanges its signed JWT for a short-lived token atoauth2.googleapis.combefore the data call, so it must allow the Google auth hosts (oauth2.googleapis.com,www.googleapis.com,accounts.google.com) alongside the data host — miss the token host and the call is blocked by the egress guard before it starts.descriptionis optional buyer-facing help text shown above the field at install time (e.g. “Paste your GA4 service-account JSON key”). It never holds a secret.requiredmarks whether the buyer must supply the slot. Defaults totrue; set itfalsefor a credential the agent degrades without.auth_schemeselects how the secret is attached to the request:bearer(default —Authorization: Bearer <token>, what GitHub and most modern APIs expect),basic(Authorization: Basic <base64(id:token)>— Atlassian / Jira / Confluence and other API-token services; the buyer supplies the base64, the runtime never re-encodes it),raw(theAuthorizationvalue verbatim, e.g.token <x>/ApiKey <x>), orheader(a CUSTOM header instead ofAuthorization— setheader_name, e.g.X-API-KEYfor Metabase orX-Figma-Tokenfor Figma, with an optionalprefixthe runtime prepends). A Bearer token sent to a Basic-auth API is rejected, so declare the scheme the target expects.install_host— settruefor a self-hosted target whose host isn't known until install (a company's own GitLab, Jira Server, Airflow, or Metabase). Leaveallowed_hostsempty for that slot: the buyer types their host at install and it becomes the slot's audience. A tool'surlcan reference it as{install_host}(e.g.https://{install_host}/api/v4/projects). The secret is only ever sent to the host the buyer entered — nothing widens the audience. Add an optionalhost_example(e.g.gitlab.mycorp.com) to hint the placeholder. This works for acode-bundleagent too (e.g. a self-hosted Trivy or on-prem endpoint): the buyer's host is threaded into that run's sandbox egress and the secret is bound to it, so a code agent can also target infrastructure only the buyer knows the address of.auth_acquisition— how the buyer gets the secret:paste(the default — the buyer pastes a key or token at install) oroauth(the buyer connects a provider once from their dashboard and the runtime resolves the acquired access token for this slot). Foroauthyou also setprovider(e.g.github) — a provider id that must match FindAgent's integrations registry. An OAuth slot leavesallowed_hostsempty and does not useinstall_host: the token's audience is the provider's own API host(s), fixed server-side from the registry, so a single connection can be reused across agents without ever reaching a host the provider doesn't own. The provider must be enabled on FindAgent for the connect option to appear (GitHub is the first live provider).
bearer slot takes the raw token as-is. A basic slot takes the Base64 of id:token (e.g. email and API token joined by a colon) — pasting the raw token here is the most common “failed to parse auth token” mistake with Atlassian / Jira. A header slot takes just the key value (any prefix you declared is added automatically), and a raw slot takes the whole authorization value including the scheme word (e.g. token ghp_…). The same hint appears in the in-chat setup panel and in the CLI, so you don't have to explain it in your description. An auth_acquisition: "oauth" slot skips pasting entirely — the buyer clicks Connect once and there is no value to enter.Guardrails
A doer agent can declare guardrails the platform enforces. The manifest only declares the rails — the FindAgent gateway, not the agent's own code, enforces them, the same way allowed_hosts and credential slots are declared in the manifest but policed by the runtime. A creator may only tighten the rails; mandatory ones can never be disabled.
- input — applied before a call reaches the agent.
max_lengthcaps the payload size,deny_patternsis a prompt-injection deny-list, andpii_redactionredacts categories (credit_card,email,phone,iban,national_id, and more) from the payload. - output — applied after the agent returns.
secret_leak_scanis mandatory: the platform always runs it (on the raw result), and a manifest that tries to set itfalsefails validation. Optional:pii_redactionstrips the same PII categories from the RESULT before the caller sees it (e.g. a doer that pulls customer data into a Slack digest), andschemavalidates the result shape. - actions — a per-tool policy keyed by tool name.
approvalgates a write:none(default),human(the client must confirm before the action runs; where a client can't prompt, the call proceeds), orhuman_strict(same confirm, but if the client can't prompt the call is BLOCKED — fail-closed, for irreversible or high-value actions).max_amountis a spend cap compared against a top-level numeric argument under one ofamount/amount_cents/total/value(in whatever unit your tool accepts — declare the cap in the same unit). It is a convention-based guard, not a semantic analyzer: an amount under a different key, nested, or sent as a string is NOT covered.rate_limit(e.g.10/hour) andidempotent(whether the gateway may safely auto-retry the action after a crash) round it out. requires— an ORDER rail: a list of tool names that must have run recently before this tool may run (e.g.create_issue: { requires: ["search_issues"] }— “always search before you create”). Enforced as a real gate, not prompt advice: the required tool must have been called by the same user within a 30-minute window or the call is blocked; on pass, your recent searches are shown back to you so you can re-search if none fits.
Delivery, kind, exec, and targets
| Field | Values | What it means |
|---|---|---|
kind | static-recipe · mcp-tool · autonomous-agent† · skills-bundle · code-bundle | The shape of the agent. Defaults to static-recipe. |
delivery | prompt · mcp.stdio · mcp.remote | How a client reaches the agent — as a paste-in recipe, a local stdio MCP server, or a hosted remote MCP URL. |
exec | user-local · findagent-hosted | Where it runs. Defaults to user-local. |
targets | claude-desktop · claude-code · chatgpt · cursor · vscode · gemini-cli · windsurf · cli · web | Which clients the agent is meant for. |
auth | none · api-key · oauth-device | FindAgent identity auth, distinct from per-agent external credentials. |
† autonomous-agent is a reserved roadmap kind with no distinct runtime behavior yet — a submission using it is treated as an mcp-tool doer.
A listing-only MCP server — a server someone already runs, shown in the MCP directory — is a separate catalogue shape, not a manifest kind enum value: the listing points at an external server rather than describing an agent FindAgent runs. The directory shows each listing's connection so you can filter by it: Hosted (a remote MCP URL you add as a connector) or Local (a command you run on your own machine — FindAgent never runs it). A doer agent, whose tools carry real action bindings FindAgent's runtime executes, is the opposite of a listing and is never shown as one.
code-bundle kind (see the code-bundle section below) runs a creator's real code inside an isolated, ephemeral sandbox FindAgent provisions for each run — never on your own machine, and walled off from FindAgent's own app and data. Code agents are scanned and reviewed before they publish, and connecting over the hosted gateway is the way you use them. See Code agents and the security model.Skills-bundle agents
A kind: "skills-bundle" manifest packages a whole skills-or-rules repository — Claude Agent Skills, Cursor / Windsurf / Continue rules, an AGENTS.md, and similar — as a single agent. Each skill is declarative content (a Markdown SKILL.md), so the runtime serves it as an MCP prompt rather than executing it. The bundle adds:
skills_source— an R2 reference (kind: "r2"+key) to the imported skill files the CLI downloads and the runtime serves.skills— the per-skill discovery metadata (id, name, description, tags) synthesized from each skill's frontmatter.router_skill_id— optional id of the entry/router skill the runtime can surface first.
Because skill bodies are never executed, a skills bundle adds no code-execution surface — the same declarative keystone as a doer agent. Importing an arbitrary public repository is an admin-only seeding path; creators bring their own repo through the GitHub- connected submit flow.
Code-bundle agents
A separate manifest shape covers agents whose behaviour is real creator code rather than declared tool bindings. A code-bundle manifest sets kind: "code-bundle" and adds — entrypoint, runtime, and mcp are required; the rest are optional:
entrypoint(required) — the handler the runtime invokes: a bundle-relativepath(e.g.src/agent.ts) and theexportname to call (defaults tohandler).runtime(required) — the execution runtime: akind(nodeorpython) and an optionalversionhint the sandbox resolves to a concrete image.mcp(required) — how the bundle is exposed over MCP:mode: "wrap"(the runtime auto-generates an MCP server around the entrypoint) ormode: "native"(the bundle ships its own MCP server, declaring thecommand+argsthat start it).allowed_hosts— the default-deny egress allowlist; the only hosts the sandbox network policy lets the bundle reach.build_command/install_cwd— an optional custom build step and the working directory to install + build from (for a bundle whose package root isn't the repo root).ui— an optional static frontend (a bundle-relativepath) served later inside a sandboxed iframe; absent means no UI.env— a declared non-secret env contract (UPPER_SNAKE names + descriptions). Secrets never go here; they flow through audience-boundcredential_slots, the same slot type a doer agent uses.
A code-bundle manifest reuses the same credential_slots, guardrails, and skills shapes. The security keystone is unchanged: the manifest only describes a bundle — storing, scanning, and sandboxed execution of the referenced code are platform responsibilities, and the bundle runs only inside the isolated sandbox, never on your machine during a normal install.
Example
A small doer agent with one HTTP tool and a host-bound credential slot:
{
"name": "GA Report Builder",
"description": "Pulls a traffic summary and writes it to a sheet.",
"tagline": "Weekly traffic summaries, straight to your sheet.",
"version": "1.2.0",
"changelog": "Add a weekly-digest tool and fix the date-range parser.",
"system_prompt": "You are a focused analytics assistant ...",
"kind": "mcp-tool",
"category": "analytics",
"additional_category_slugs": ["marketing"],
"tags": ["ga4", "reporting", "analytics"],
"metadata": {
"license": "MIT",
"homepage": "https://example.com/ga-report-builder",
"docs_url": "https://example.com/docs",
"support_email": "support@example.com",
"author": { "name": "Jane Doe", "url": "https://example.com" }
},
"tools": [
{
"name": "get_traffic_summary",
"description": "Fetch a traffic summary for a date range.",
"input_schema": { "type": "object", "properties": { "range": { "type": "string" } } },
"annotations": { "readOnlyHint": true },
"action": {
"type": "http",
"method": "GET",
"url": "https://api.example.com/v1/traffic?range={range}",
"auth_ref": "analytics_token"
}
}
],
"credential_slots": [
{
"ref": "analytics_token",
"label": "Analytics API token",
"env": "ANALYTICS_TOKEN",
"allowed_hosts": ["api.example.com"]
}
],
"example_prompts": ["Summarize last week's traffic."],
"delivery": { "mcp": { "stdio": { "command": "findagent-mcp", "args": ["run", "ga-report-builder"] } } },
"exec": "user-local",
"targets": ["claude-desktop", "cursor", "cli"]
}