Build your first tool
This page takes a one-tool function package from nothing to a published version an agent can use. The starter tool, double_value, doubles a number; replace its body with your own logic once the loop works.
Before you start
Section titled “Before you start”- The Owner, Admin or Developer role in the workspace. These roles can create, build and publish tools.
- For the CLI path: the
caploCLI (install it), signed in withcaplo auth loginand with a workspace selected withcaplo workspace use. See Sign in from the CLI. - For the CLI path: Node.js 24 and npm. Tool packages run on Node 24, and
caplo tool testrefuses any other major version. Local tests run on macOS and Linux; on Windows, use WSL.
Create, test and publish
Section titled “Create, test and publish”-
Create the package.
caplo tool initwrites a package from the SDK template into a new or empty directory and names it after the directory:Terminal window caplo tool init double-value --type functioncd double-valueYou get
caplo.yaml,package.json,tsconfig.json,src/tool.ts(the tool),src/stdio.ts(runs it for local MCP clients) andtests/double-value.json(a fixture). Use--type mcpfor an MCP package with several tools; see Define tools and use the call context. -
Install dependencies.
Terminal window npm installThis also writes
package-lock.json. Keep it and commit it: Caplo installs exactly what the lockfile lists, and a push without a lockfile fails to build.yarn.lockandpnpm-lock.yamlalso work. -
Look at the tool.
src/tool.tsexports onedefineTool:import { defineTool } from "@caplo/tools";export default defineTool<{ value: number }, { result: number }>({name: "double_value",description: "Doubles a number",inputSchema: { type: "object", properties: { value: { type: "number" } }, required: ["value"] },outputSchema: { type: "object", properties: { result: { type: "number" } }, required: ["result"] },async handler({ value }, { log }) {log.info("doubling", { value });return { result: value * 2 };},});caplo.yamlnames the package and sets the tool’s policy:effect: readandtimeoutSeconds: 5. -
Call it by hand.
caplo tool devopens MCP Inspector (started withnpx) connected to your package, so you can list and call the tool:Terminal window caplo tool devSecrets your
caplo.yamldeclares are read from environment variables of the same name. To use the package from another local MCP client instead, runcaplo tool dev --no-inspector, which speaks MCP on standard input and output. -
Run the fixtures.
caplo tool testbundles the package the way Caplo’s builder does, checkscaplo.yamlagainst the tools the code lists, and runs every fixture intests/:Terminal window caplo tool testbundle 7.5 KB (d64251e71094, esbuild 0.28.2)tool double_value__double_value (read, 5s)capabilities subject-context/1 effect-outcome/1 operation-status/1server advertises subject-context/1 effect-outcome/1ok tests/double-value.json (doubles 21)1 passed, 0 faileddouble_value__double_valueis the name the model sees. See Test tools with fixtures. -
Push and build.
caplo tool pushuploads the folder as the package’s workspace draft (creating the package on the first push), builds it, and streams the build log.node_modules,.gitand anything your.gitignorefiles ignore are left out.Terminal window caplo tool pushThe build installs from your lockfile, bundles the code, runs safety checks and lists the tools with no network access. It ends with
Build <id> succeededand the tools it found, or with an error code such aslockfile_requiredortool_policy_missing. -
Publish.
caplo tool publishturns the latest succeeded build into the next version:Terminal window caplo tool publishPublished double-value version 1 from build <BUILD_ID>double_value__double_value (read, 5s)
Tool Studio is a browser editor at studio.caplo.si for writing, building, testing and publishing tool packages without installing anything.
-
In the console, open Tools, select Add tools, then Create package. Studio opens on this workspace in a new tab; sign in if it asks.
-
In the Packages view, select Create Package. Choose Function (one tool written as a function) or MCP server (several tools in one server), then enter a package name such as
double-value: a lowercase letter, then lowercase letters, digits or dashes. -
Studio creates the package from the same template as the CLI. Edit
src/tool.tsandcaplo.yaml. Your edits are saved to the package’s workspace draft. When you savepackage.json, Studio resolves the dependencies and updates the lockfile for you. -
In the Tools view, select Build. When the build succeeds, Studio offers Run Tests and Publish. To call a tool by hand, select Call Tool on it, fill in the arguments and select Send Call.
-
Select Run Fixture Tests to run the files in
tests/. -
In the Versions view, select Publish and confirm. Studio builds the draft first if it changed since the last build.
You can return to a package from the console: Tools lists each package with Open in Studio.
Give the tool to an agent
Section titled “Give the tool to an agent”A published tool appears on every agent’s Tools & actions tab, grouped under its package. Select it there, or list it in agent YAML by package and tool name:
tools: - double-value/double_valueThen publish the agent. Its release pins version 1 of double-value; later versions reach the agent when you publish the agent again. See Give an agent tools.
Next steps
Section titled “Next steps”- Replace the starter with real logic that calls your API: Define tools and use the call context and Secrets and network access.
- For tools that change data, read Write tools safely before you publish.
Let an AI agent do it
Section titled “Let an AI agent do it”If you already have a REST API, a coding agent can wrap it as a tool package with read and write tools, declared secrets and an egress list.
Wrap my REST API as a Caplo tool package
Builds an MCP tool package with read and write tools for an existing REST API, with declared secrets, an egress list and fixtures.
Show prompt
Goal: turn the REST API in this repository (or the one I describe) into a Caplo tool package that agents can call. Read operations become `read` tools, operations that change data become `write` tools that the customer confirms. The package declares the secret names it needs and the exact hosts it calls.
Read these pages first:
- https://docs.caplo.si/tools/first-tool.md
- https://docs.caplo.si/tools/define-tool-and-context.md
- https://docs.caplo.si/tools/write-tools.md
- https://docs.caplo.si/tools/secrets-and-egress.md
- https://docs.caplo.si/tools/testing.md
- https://docs.caplo.si/reference/caplo-yaml.md
Values I supply: package name `<PACKAGE_NAME>` (lowercase letters, digits and dashes), API base URL `<API_BASE_URL>`, and the operations to expose `<OPERATIONS>` (if I leave this out, propose a list).
Steps:
1. Find the API's routes, request and response shapes, and authentication scheme (an OpenAPI file, route handlers, or client code). List the operations you plan to expose, each marked `read` or `write`, with one sentence on what the agent would use it for. Mark anything that changes data, sends a message or costs money as `write`. Show me the list and wait for my answer.
2. Check that `node --version` reports v24. Then create the package and install dependencies:
```sh
caplo tool init tools/<PACKAGE_NAME> --type mcp
cd tools/<PACKAGE_NAME>
npm install
```
3. In `src/server.ts`, register one tool per operation with `server.registerTool`. Tool names are lowercase with underscores (for example `get_order`, `cancel_order`). For each tool:
- Write a description that says what it does and when to use it.
- Give every input a description; use enums, patterns and length limits; keep inputs to what the model can know.
- Define an `outputSchema` and return `structuredContent` with only the fields an agent needs, mapped explicitly from the API response.
- Get secrets with `context(extra).secrets.get("<SECRET_NAME>")` and pass `signal` to every `fetch`.
- For write tools, send `idempotencyKey` from the context to the API (as an `Idempotency-Key` header if the API supports it). Throw on any response you cannot interpret, so the outcome counts as unknown rather than failed.
Keep `capabilities: { logging: {} }` on the server and log with `context(extra).log`, never `console.log`, and never log secrets or personal data.
4. Edit `caplo.yaml`: one `tools` entry per tool with `effect` and `timeoutSeconds` (1 to 30); `confirmation: required` on every write tool; `egress` with the API's exact host names (no scheme or path); `secrets` with the names the code reads, such as `<SECRET_NAME>`. Remove the template's example tools, fixtures and secret.
5. Update `src/stdio.ts` so `serveStdio` lists the same secret names.
6. Write one fixture per read tool in `tests/`, plus one for an expected error such as an unknown ID. Only use fixture `secrets` values that I give you for a test or sandbox environment; if I give none, ask me.
7. Run `caplo tool test` and fix every failure. Then ask me before running `caplo tool test --remote`, which pushes the package as a workspace draft and runs the fixtures on Caplo with the egress list enforced. Fix any `egress denied` line by correcting the `egress` list.
Verify: `caplo tool test` passes, `caplo tool test --remote` passes with no `egress denied` lines, and every tool has an `effect` that matches what it does.
Rules:
- Never write a secret value into code, `caplo.yaml`, fixtures, commit messages or this chat. Tell me to set values myself with `caplo tool secret set <SECRET_NAME>` (it prompts without echoing) or to bind a workspace secret with `caplo tool secret bind <SECRET_NAME> <workspace-secret>`.
- Do not run `caplo tool publish`, `caplo tool push` or `caplo publish` without my explicit approval. Publishing creates a version agents can pin.
- Do not mark a write tool `confirmation: none` unless I ask for it.
- Keep the package's existing lockfile in the commit; the build installs only what it lists.