Early AccessEvery agent is free to connect — no card, no checkout.

Documentation menu
Building agents

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.

On this page

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. An instructions-only agent needs only these; an agent with actions 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.

FieldTypeNotes
namestring3–80 characters.
descriptionstring?Up to 4000 characters.
system_promptstringThe agent’s instruction set (50–20,000 characters).
toolsTool[]Up to 40 tools (see below).
example_promptsstring[]1–5 example prompts that show the agent in use.
compatibilityobject?Optional per-client version hints (Claude / ChatGPT / Gemini).
Category is a hint, not an exact match
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.

FieldTypeAlso acceptsNotes
taglinestring?—A one-line pitch shown on the card (up to 140 characters).
categorystring?category_slug · primary_categoryThe primary category hint (resolved to a discipline — see above).
additional_category_slugsstring[]?additional_categories · subcategoriesExtra category hints (e.g. an industry). Up to 8.
tagsstring[]?—Free-form search tags. Up to 16.
llmsstring[]?—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 / disciplinestring?taxonomyName the two axes explicitly instead of a single category hint.
versionstring?—A self-described release version (semver, e.g. "1.0.0"). FindAgent tracks the authoritative version separately — this is descriptive metadata.
changelogstring?release_notesHuman 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 fieldTypeNotes
licensestring?A short license id, e.g. "MIT" (≤64 chars).
homepagestring?Project homepage. Must be an https:// URL (≤500 chars).
docs_urlstring?Documentation link. https:// only (≤500 chars).
support_urlstring?Support / issues link. https:// only (≤500 chars).
support_emailstring?A support email address.
iconstring?Listing icon URL. https:// only (≤500 chars).
author / maintainerobject?A { name, url? } credit. name is required (≤120 chars); url (optional) must be https://.
You never lose what you wrote
These listing fields aren't part of the validated execution manifest — the runtime never reads them, so the schema ignores any it doesn't recognize. They are prefill hints the importer reads and the submit builder re-validates: anything the manifest carries is filled in for you to confirm or change, and anything it omits you set in the builder. Nothing here is executable; these fields only describe the listing.

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 of GET / POST / PUT / PATCH / DELETE), a url (which may contain {param} placeholders bound from the tool input), optional headers and body_template, and an optional auth_ref pointing 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_hosts binds 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.
  • type describes the value shape so the install UI renders the right input: string (plain text, the default), secret (masked), or json (a JSON blob — e.g. a service-account key file). A json key that must be signed into a token (a Google/GCP service-account JWT) only works for a code-bundle or hosted agent whose code can perform the signing — a pure declarative http action can attach a credential but cannot sign a JWT, so reach for code-bundle when the API needs signed short-lived tokens. This also changes which hosts you must allow: a plain type: secret API key on a Google API (e.g. a Gemini key hitting generativelanguage.googleapis.com) reaches the data host directly, so that one host is enough. A type: json service-account key first exchanges its signed JWT for a short-lived token at oauth2.googleapis.com before 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.
  • description is 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.
  • required marks whether the buyer must supply the slot. Defaults to true; set it false for a credential the agent degrades without.
  • auth_scheme selects 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 (the Authorization value verbatim, e.g. token <x> / ApiKey <x>), or header (a CUSTOM header instead of Authorization — set header_name, e.g. X-API-KEY for Metabase or X-Figma-Token for Figma, with an optional prefix the runtime prepends). A Bearer token sent to a Basic-auth API is rejected, so declare the scheme the target expects.
  • install_host — set true for a self-hosted target whose host isn't known until install (a company's own GitLab, Jira Server, Airflow, or Metabase). Leave allowed_hosts empty for that slot: the buyer types their host at install and it becomes the slot's audience. A tool's url can 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 optional host_example (e.g. gitlab.mycorp.com) to hint the placeholder. This works for a code-bundle agent 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 an agent with code 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) or oauth (the buyer connects a provider once from their dashboard and the runtime resolves the acquired access token for this slot). For oauth you also set provider (e.g. github) — a provider id that must match FindAgent's integrations registry. An OAuth slot leaves allowed_hosts empty and does not use install_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).
What the buyer pastes depends on the scheme
The install screen shows a per-scheme format hint so a buyer enters the value the runtime expects. A 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

An agent with actions 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_length caps the payload size, deny_patterns is a prompt-injection deny-list, and pii_redaction redacts categories (credit_card, email, phone, iban, national_id, and more) from the payload.
  • output — applied after the agent returns. secret_leak_scan is mandatory: the platform always runs it (on the raw result), and a manifest that tries to set it false fails validation. Optional: pii_redaction strips the same PII categories from the RESULT before the caller sees it (e.g. an action that pulls customer data into a Slack digest), and schema validates the result shape.
  • actions — a per-tool policy keyed by tool name. approval gates a write: none (default), human (the client must confirm before the action runs; where a client can't prompt, the call proceeds), or human_strict (same confirm, but if the client can't prompt the call is BLOCKED — fail-closed, for irreversible or high-value actions). max_amount is a spend cap compared against a top-level numeric argument under one of amount / 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) and idempotent (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

FieldValuesWhat it means
kindstatic-recipe · mcp-tool · autonomous-agent† · skills-bundle · code-bundleThe shape of the agent. Defaults to static-recipe.
deliveryprompt · mcp.stdio · mcp.remoteHow a client reaches the agent — as paste-in instructions, a local stdio MCP server, or a hosted remote MCP URL.
execuser-local · findagent-hostedWhere it runs. Defaults to user-local.
targetsclaude-desktop · claude-code · chatgpt · cursor · vscode · gemini-cli · windsurf · cli · webWhich clients the agent is meant for.
authnone · api-key · oauth-deviceFindAgent 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 agent.

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). An agent with actions, whose tools carry real action bindings FindAgent's runtime executes, is the opposite of a listing and is never shown as one.

code-bundle runs in a sandbox
The code-bundle kind (see the code-bundle section below), served hosted, 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. Agents with code are scanned and reviewed before they publish, and connecting over the hosted gateway is how you use a hosted one. A creator may also offer a serve: ["local"] bundle that you run on your own machine; FindAgent never runs that one. See The Code part of an agent and the security model.

Agents made of skills

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. One agent carries at most 40 skills; an import with more is refused rather than trimmed, and so is a SKILL.md whose name is not 1-64 lowercase letters, digits and single hyphens. A missing description, one over 1,024 characters, or a name that differs from its folder still imports, with a warning.
  • router_skill_id — optional id of the entry/router skill the runtime can surface first.

A client that supports the MCP Skills extension (io.modelcontextprotocol/skills) can also list and read each skill as a skill://<name>/SKILL.md resource, with a SHA-256 digest and size it can check against the file it reads. A skill is published this way only when its frontmatter carries a valid name that matches its directory and a description, and when its directory holds nothing but the SKILL.md.

Because skill bodies are never executed, an agent made of skills adds no code-execution surface — the same declarative keystone as an agent with actions. Creators bring their own repo through the GitHub-connected submit flow, or upload their own files.

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:

  • skills — the agent's tools as listed on its page. Each skill keeps the id you declared and an optional JSON-Schema input_schema (type object, up to 8,000 characters, nested no deeper than about 12 levels, same-document $ref only) so a client can show real argument fields. A schema over a limit is left out rather than clipped. When your repo's findagent.json declares skills, a re-pull reads them from there before any other manifest in the repo.
  • entrypoint (required) — the handler the runtime invokes: a bundle-relative path (e.g. src/agent.ts) and the export name to call (defaults to handler).
  • runtime (required) — the execution runtime: a kind (node or python) and an optional version hint the sandbox resolves to a concrete image. The supported runtimes are Node 22/24 and Python 3.13; see Runtimes.
  • mcp (required) — how the bundle is exposed over MCP: mode: "wrap" (the runtime auto-generates an MCP server around the entrypoint) or mode: "native" (the bundle ships its own MCP server, declaring the command + args that start it).
  • serve — optional list of how the bundle is offered: hosted (FindAgent runs it in the sandbox; the default when absent), local (the buyer downloads and runs it on their own machine; FindAgent never runs it and there is no egress allowlist), or both. A local-only bundle has no hosted gateway URL. See Run on your machine.
  • allowed_hosts — the default-deny egress allowlist of the hosted sandbox; the only hosts its 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-relative path) 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-bound credential_slots, the same slot type an agent with actions 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 when served hosted (the default). A bundle that declares serve local is run by the buyer on their own machine instead.

Example

A small agent with one HTTP action and a host-bound credential slot:

findagent.json
{
  "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"]
}

The file is read from findagent.json at the repository root (or .findagent/findagent.json). A repository that already keeps the same v1.1 manifest as manifest.json is read too, but only when it declares schema_version "1.1", and only after the findagent.json paths: when both exist the findagent.json is the manifest and a second file that also declares actions is refused, never merged. A manifest.json without that version (a DXT manifest) is not a FindAgent manifest.