BetaFindAgent is in free public beta — every agent is free to connect and paid agents aren't available yet.
A manifest describes an agent; it never ships code. Here is the full anatomy, a working end-to-end example, and the checks the review pipeline runs before your listing goes live.
A declarative manifest can describe most REST APIs. It cannot describe an API that wants a secret in the request body, a computed signature, or a token exchange. Here's the full diagnostic.
Four fields — kind, delivery, exec and targets — decide whether an agent installs onto a machine or connects over a hosted URL. Get one wrong and the agent silently won't appear in the client.
If your agent is real code — a working MCP server, a TypeScript module, a Python package — you do not write a manifest for it. You connect GitHub, point at the repo, and the platform generates the manifest: tools, credential slots, and the network allowlist, derived from what's actually in the code.
This matters more than it sounds. A hand-written manifest is a claim about the code; a generated one
is a reading of it. When they disagree, the hand-written one is wrong, and it stays wrong silently.
That's why creators never author a findagent.json — and why it's regenerated on every version
bump rather than edited.
What follows is what the importer reads, how to lay a repo out so it reads correctly, and what the sandbox does once the agent is live.
flowchart TD
Repo["Your GitHub repo"] --> Imp["Importer reads:<br/>DXT manifest.json<br/>MCP config<br/>.env.example keys<br/>package.json<br/>README"]
Imp --> Gen["GENERATED manifest<br/>entrypoint · runtime<br/>allowed_hosts · mcp mode<br/>credential_slots · env · ui"]
Gen --> Scan["Static scan<br/>(fails closed)"]
Scan --> Human["Human review"]
Human --> Live["Listed — connect-only"]
Live --> Run["Per-run sandbox<br/>isolated · ephemeral<br/>default-deny egress"]
Run --> Hosts["Only hosts in allowed_hosts"]
style Scan fill:#1f2937,color:#fff
style Run fill:#1f2937,color:#fff| Source in your repo | What it yields |
|---|---|
DXT manifest.json |
Tool names, descriptions, input schemas |
| MCP server config | Whether the bundle ships its own server (native) or needs wrapping (wrap); the start command |
.env.example keys |
Credential slots and the non-secret env contract |
package.json |
Runtime kind and version hint; entrypoint candidates; dependencies |
| README | Description, tagline candidates, listing metadata, usage examples |
The generated manifest carries a handful of code-specific fields on top of the usual ones:
{
"kind": "code-bundle",
"entrypoint": { "path": "src/agent.ts", "export": "handler" },
"runtime": { "kind": "node", "version": "20" },
"allowed_hosts": ["api.vendor.com", "api.github.com"],
"mcp": { "mode": "wrap" },
"ui": { "path": "web/dist" },
"env": {
"VENDOR_REGION": "Which regional endpoint to use: us or eu. Defaults to us.",
"LOG_LEVEL": "info | debug. Defaults to info."
},
"credential_slots": [
{
"ref": "vendor_key",
"label": "Vendor API key",
"type": "secret",
"auth_scheme": "bearer",
"allowed_hosts": ["api.vendor.com"],
"required": true
}
],
"delivery": "mcp.remote",
"exec": "findagent-hosted"
}Field by field:
entrypoint — a bundle-relative path plus the export to call. export defaults to
handler.runtime — kind is node or python; the optional version is a hint the sandbox resolves
to an image.allowed_hosts — the run-time egress allowlist, and it is default-deny. A host not on
this list is simply unreachable from the sandbox. This is separate from the per-credential
allowed_hosts inside a slot: the bundle-level list says where the process may connect, the
slot-level list says where a secret may travel. Both apply.mcp.mode — wrap or native, covered below.ui — an optional static frontend at a bundle-relative path, served in a sandboxed iframe.
Absent means no UI.env — a non-secret contract: UPPER_SNAKE names with descriptions. Configuration, not
credentials.flowchart LR
subgraph W["mode: wrap"]
W1["Your exported function"] --> W2["Runtime generates<br/>an MCP server around it"]
W2 --> W3["Tools exposed"]
end
subgraph N["mode: native"]
N1["Your bundle ships<br/>its own MCP server"] --> N2["Declared command + args<br/>start it"]
N2 --> N3["Tools exposed"]
endwrap is for code that isn't an MCP server yet — a module with functions you want exposed. The
runtime generates the server around your entrypoint export.
native is for a repo that already is an MCP server. It declares the command and args that
start it, and the runtime just runs it.
If you already maintain a working MCP server, native is the right mode and the import is close to
lossless. If you're bringing a library across, wrap saves you writing server boilerplate you'd
only maintain twice.
Everything about the code path exists so that submitted code can run without anyone having to trust it.
| Property | What it means |
|---|---|
| Isolated | The run has no access to other agents, other runs, or the host |
| Ephemeral | Per-run. State does not carry between invocations |
| Default-deny egress | Only hosts in allowed_hosts are reachable — everything else fails |
| Never local | Code executes on the platform, never on a buyer's machine |
| Platform-owned lifecycle | Storing, scanning, building and executing the bundle are platform responsibilities |
Two consequences worth internalising before you design:
Ephemeral means no local state. No caching between runs, no writing a file in one call and
reading it in the next, no in-memory session. Anything that must persist goes to an external store
you declare in allowed_hosts — and that store's credential goes in a slot, bound to its host.
Default-deny means listing every host. Including the ones you forget: your error tracker, your own API, the CDN a dependency phones home to. If it isn't listed, it isn't reachable.
Build-time egress — the network access needed to install packages from registries or fetch prebuilt binaries — is admin-managed, per agent. It is not something the manifest declares, and it's a separate surface from run-time egress by design: the set of hosts a build needs has nothing to do with the set a running agent should reach.
The importer is good, but it can only read what's there. Five things that raise import fidelity from "needs a review pass" to "correct on the first read":
.env.exampleThis is the single highest-leverage file, because it's where credential slots come from.
# .env.example
# Vendor API key. Create at https://vendor.com/settings/api-keys
# Needs: read on Reports. Host: api.vendor.com
VENDOR_API_KEY=
# Which regional endpoint to use: us | eu. Defaults to us. NOT a secret.
VENDOR_REGION=usComment every key with what it is, where to get it, and which host it authenticates against. Those
comments become the buyer-facing description at install time — the difference between a slot
labelled "Vendor API key" and one that tells the buyer exactly which menu to click.
Mark non-secret configuration clearly. It belongs in env, not in a credential slot, and the
distinction is not always guessable from the name.
A DXT manifest.json with named tools, one-sentence descriptions and input schemas imports
directly. If you don't have one, the importer falls back to weaker signals and you'll spend the
review round correcting names.
Tool names should already be MCP-shaped in your code: snake_case, verb + object.
The importer derives the egress allowlist, but a README section makes it verifiable — for the reviewer and for you:
## Network access
- `api.vendor.com` — the vendor REST API (all tool calls)
- `api.github.com` — used only by `sync_issues`If a host in your code doesn't appear in that list, that's worth a second look before you submit.
One clear export at a predictable path. src/agent.ts exporting handler needs no guessing;
a barrel file re-exporting from four places does.
Description, tagline and usage examples are lifted from it. A README that opens with build instructions produces a listing that opens with build instructions.
You publish a new version by re-pulling the repo, not by editing the manifest. The importer re-reads, regenerates, and the new version goes through the scan and review again.
That's why hand-editing a generated manifest is a dead end: the next re-pull overwrites it. If the
generated output is wrong, fix the repo — add the .env.example comment, add the tool description,
add the host to the README — and re-pull.
A published version can also be rolled back to a prior one if a release goes wrong.
| Symptom | Cause | Fix |
|---|---|---|
| Calls fail with a network error in the sandbox | A host missing from allowed_hosts |
Default-deny; list every host, including error trackers and your own API |
| Works locally, breaks hosted, no obvious error | Code relies on local filesystem or state between runs | The sandbox is ephemeral — externalise persistence |
| Credential slot generated with a vague label | .env.example key had no comment |
Comment every key with purpose, source and host |
| A non-secret ends up as a masked credential slot | Nothing marked it as configuration | Say "NOT a secret" in the .env.example comment |
| Import produces wrong or missing tools | No DXT manifest.json; weak fallback signals |
Ship a manifest with named tools and schemas |
| Manifest edit disappears after a version bump | The manifest is generated | Fix the repo, then re-pull |
| Offered as a local install and rejected | mcp.stdio or exec: user-local on a code bundle |
Connect-only, always hosted |
| Build fails fetching a dependency | Build-time egress not permitted for that registry | Admin-managed per agent — raise it during review |
.env.example complete, every key commented with purpose, source and hostmanifest.json present with tool names, descriptions and input schemasmcp.mode matches reality — native if the repo is already an MCP serverenv, or in any committed filefindagent.jsonmcp.stdio or exec: user-localDo I write a manifest for a code agent? No. The GitHub submit flow generates it from the repo, including tools, credential slots and the egress allowlist, and regenerates it on every version bump. A hand-edited manifest is overwritten by the next re-pull.
What does the importer read from my repository?
Five sources: the DXT manifest.json, MCP server config, .env.example keys, package.json and
the README. Together they yield the tools, credential slots, runtime, entrypoint and network
allowlist.
What is the difference between wrap and native mode?
wrap means the runtime generates an MCP server around your entrypoint export, for code that isn't
a server yet. native means the bundle ships its own MCP server and declares the command and args
that start it.
Where does a code agent run? In an isolated, ephemeral, per-run sandbox on the platform — never on a buyer's machine. That's why code agents are connect-only and cannot be installed locally.
Why can't my code agent reach a host it uses?
The bundle-level allowed_hosts is a default-deny run-time egress allowlist. Hosts that aren't
listed are unreachable, including error trackers, telemetry endpoints and your own API.
Can I put secrets in the env block?
No. env is a non-secret configuration contract of UPPER_SNAKE names and descriptions. Secrets flow
through audience-bound credential_slots, which bind each value to the hosts it may be sent to.
Related: Nine reasons a declarative agent becomes a code agent · Connect vs install · Audience-bound credentials