Developer guide
Build a tool for Foundry
A Foundry tool is a small JavaScript function plus a manifest. It runs in a WebAssembly sandbox with only the capabilities a human grants it, on a backend that already lives on the drive. Any AI agent that can make an HTTP call can run it.
Your first tool
A tool is two files — a manifest and the source. Here's one that reads a repo's star count.
manifest.json
{
"name": "star-counter",
"version": "1.0.0",
"description": "Reads a GitHub repo's star count and remembers it.",
"author": "you",
"params_schema": {
"type": "object",
"properties": { "repo": { "type": "string" } },
"required": ["repo"]
},
"capabilities": {
"vault": { "credentials": ["github"] },
"storage": { "namespace": "star-counter", "quota_bytes": 1048576 }
}
} tool.js
// tool.js — a Foundry tool is one async run(input) function.
async function run({ repo }) {
// Call GitHub as the user, via Vault. The token is attached server-side;
// this code never sees it.
const res = await foundry.vault.fetch({
credential: "github",
url: `https://api.github.com/repos/${repo}`,
});
const stars = JSON.parse(res.body).stargazers_count;
await foundry.kv.set(repo, stars); // persisted on the drive, in this tool's namespace
return { repo, stars };
} That's the whole thing. You export one async function run(input); whatever you
return is the JSON the agent gets back. Every host call is asynchronous, so always await it.
The foundry API
The foundry global is the entire surface — storage, queues, caching, blobs, web
requests, and credentials by reference. There's nothing to install; it's injected into the
sandbox. Every call is brokered by the host, which enforces your grant — the JavaScript side
is never trusted to limit itself.
| Call | Returns | |
|---|---|---|
await foundry.kv.set(key, value) | — | Persist a JSON-serializable value in the tool’s namespace. |
await foundry.kv.get(key) | value | null | Read it back across runs. |
await foundry.kv.del(key) | — | Delete a key. |
await foundry.cache.set(key, value, ttlSeconds) | — | Like kv, but expires after the TTL. |
await foundry.cache.get(key) | value | null | Returns null once expired. |
await foundry.queue.push(name, msg) | — | Append to a named FIFO work queue. |
await foundry.queue.pop(name) | msg | null | Take the oldest message. |
await foundry.blob.put(name, bytes) | — | Object / blob storage for larger payloads. |
await foundry.blob.get(name) | bytes | null | Read a blob back. |
await foundry.http.fetch(req) | status, headers, body, truncated | req = method, url, headers, body. Reaches only granted hosts. |
await foundry.vault.fetch(req) | status, headers, body | req = credential, method, url, headers, body. Vault attaches the secret; the tool never sees it. |
foundry.log(level, msg) / console.log(…) | — | Surfaced in the run’s logs. |
http.fetch can reach only the hosts your manifest declares and the user grants. vault.fetch routes through Vault,
which attaches the credential server-side — the raw secret never enters your tool or the agent's context.
The manifest
The manifest is your tool's identity and its capability request. You declare what the tool needs; the human grants a subset (possibly narrower) when they install it. Enforcement happens in the host at every call, never in your JavaScript.
| Field | Required | |
|---|---|---|
name | yes | Unique tool id (lowercase). Used in the run URL. |
version | yes | Semver. A changed manifest re-prompts the user for consent. |
description | yes | One line, shown in the catalog. |
author | no | Your handle. Tools are listed as author/name. |
params_schema | no | JSON Schema for input — how the agent knows what to pass. |
capabilities.network.hosts | no | Hostnames foundry.http.fetch may reach. |
capabilities.vault.credentials | no | Vault credential names the tool may use by reference. |
capabilities.storage.namespace | no | Storage bucket (defaults to the tool name). |
capabilities.storage.quota_bytes | no | Storage cap in bytes. |
capabilities.limits.mem_pages / wall_ms | no | Sandbox memory + wall-clock ceilings. |
Publishing
- Author and test the tool locally in the Foundry GUI (dev tools run behind a per-run human consent).
- Package it into a
tool.zip(manifest + source) and submit it to the marketplace. - The registry runs an AI exploit review, then signs the artifact.
Malware or a sandbox-escape attempt. Never published.
Published unverified — installable, with a warning.
Published Verified.
Verified isn't a safety guarantee — it means a reviewer found nothing. The real floor is the same for every tool: the sandbox, plus the capabilities the user grants at install. That's why unverified tools are still safe to try.
How an agent runs it
Once a tool is installed and granted, any agent reads the catalog and runs it by name over the local API — the same zero-install REST + guide shape as the rest of mykeep.
# The agent lists the catalog, then runs a granted tool by name.
GET /v1/foundry/tools -> [{ name, version, description, params_schema }]
POST /v1/foundry/tools/star-counter
{ "repo": "tetratelabs/wazero" }
-> { "ok": true, "result": { "repo": "...", "stars": 4321 }, "logs": [] } The agent can list and run granted tools, but never install, grant, or author them — that's the human's control plane.