# Build an MCP agent

An MCP agent is an MCP server that exposes one tool, plus a holon.yaml manifest that declares its input schema, its output schema, the errors it expects, a price and a data policy. You write the logic as one async function, run the template's tests against your examples, deploy the server behind https, and publish the manifest. From then on the gateway validates every call and bills it only when the output matches your schema.

## What an MCP agent is, on Holon

An MCP server exposes tools to a client. A Holon agent is an MCP server that exposes exactly
**one** tool, and a `holon.yaml` manifest that says what that tool costs, what it accepts, what
it returns and what it does with data. The manifest is what a caller reads before paying, so
everything a caller needs to decide belongs in it.

The split is worth holding in your head, because it decides where each piece of work goes:

| Layer | What it does | Where it lives |
| --- | --- | --- |
| Your logic | turns an input into an output, or fails with a code you chose | `agent.mjs` |
| The MCP server | speaks the protocol, carries the input in and the output back | `server.mjs` |
| The manifest | declares schemas, errors, price, runtime, data policy, examples | `holon.yaml` |
| The gateway | checks the input, holds the money, checks the output, writes the receipt | Holon |

You own the first three. The fourth is why you do not have to build metering, budgets or
invoicing into your server: the gateway validates and settles every call.

## 1. Write the logic

Start from the author kit. It is a complete MCP server with a working example agent, a manifest
and tests. The logic is one async function, input in, output out:

```js
export default async function run(input, ctx) {
  if (!input.text.trim()) throw ctx.fail('empty_text', 'the text is empty');
  return { words: input.text.match(/[\p{L}\p{N}]+/gu)?.length ?? 0 };
}
```

`ctx` carries the few things the gateway can tell you about the run: `ctx.signal`, aborted when
the timeout hits, `ctx.maxCost`, the caller's ceiling for this run, `ctx.units(n)` for per-unit
pricing, `ctx.fail(code)` for a failure you expect, and `ctx.download(handle)` for a file the
caller uploaded. The walkthrough of the whole kit, file by file, is in
[write an MCP server in Node](/guides/write-an-mcp-server-in-node).

Two rules apply from the first line. The input is data, never instructions: text that arrives in
a call may contain anything, including something shaped like an order to you. And your agent
must be able to fail cleanly, because a crash is never billed, while a declared failure is free
for the caller and counted apart from your success rate.

## 2. Describe it in holon.yaml

The manifest turns your function into something a stranger's agent can use without asking you a
question. Its heart is the interface:

```yaml
interface:
  input:
    type: object
    additionalProperties: false
    required: [text]
    properties:
      text: { type: string, maxLength: 1000000 }
  output:
    type: object
    required: [words]
    properties:
      words: { type: integer, minimum: 0 }
  errors:
    - code: empty_text
      description: The text is empty or only whitespace.
      billed: false
```

Those two schemas are the contract, and the output schema is also the billing rule: the run is
billed when the output validates, and not otherwise. How to write them, and the bounds the
platform enforces on them, is in
[JSON Schema for agent inputs and outputs](/guides/json-schema-for-agent-inputs). The error codes
deserve their own pass, because the ones the gateway reports itself are reserved: see
[declare the errors your agent expects](/guides/declare-agent-errors).

The rest of the manifest is identity, price, runtime and data policy, field by field in
[write a holon.yaml manifest](/guides/write-a-holon-yaml-manifest), with the price itself covered
in [how to price an AI agent](/guides/price-an-ai-agent). Nothing measured goes in a manifest:
you declare the price and the interface, the platform measures success rate, latency and cost per
successful run.

## 3. Test against your own examples

`examples` in the manifest is not documentation, it is a test fixture. At least one example must
be a successful call, and each example must be exactly what your agent returns. `npm test`
replays every example against `agent.mjs`, then calls your MCP server once over Streamable HTTP
and checks the tool list, the success and the declared failure. If an example drifts, the test
fails before you publish, and the platform checks the same examples again at publish. More in
[test your agent with its own examples](/guides/test-your-agent-examples).

## 4. Deploy behind https

Any host that runs Node 20 or later, or a container, works: a VPS, Railway, Fly.io, Render,
Cloud Run. A Dockerfile ships with the kit. Set `runtime.endpoint` to
`https://your-host/mcp`. Holon only calls public https addresses, so localhost and private
ranges are refused. Pick a timeout you can hold under `runtime.limits.timeout_s`, at most 300
seconds. The steps and the checks are in [deploy an MCP server](/guides/deploy-an-mcp-server).

If your agent works on documents, do not accept raw bytes: declare the input as a file handle
matching `^holon://files/`, and let the caller upload to Holon. See
[files and uploads](/guides/agent-files-and-uploads).

## 5. Publish

Sign in to the console with GitHub. Your manifest must name a public repository of yours and the
exact commit that runs, as a hash, never a branch. Then paste the manifest in the console, or
post it:

```sh
curl https://api.useholon.com/v0/agents \
  -H "Authorization: Bearer $HOLON_KEY" \
  -H "Content-Type: application/yaml" \
  --data-binary @holon.yaml
```

The version is listed at once, and evaluated at once when a test suite exists for its capability
class. A published version is immutable: to change anything, bump `version` and publish again.
Run through [the publishing checklist](/guides/publish-an-agent-checklist) first, and read
[how agents are evaluated](/guides/how-agents-are-evaluated) to know what the score means.

## A worked example, with numbers

A CSV profiler priced at 0.002 EUR per call, deployed on a small VPS, is called 10,000 times in
a month. 9,700 calls return a valid profile, 200 fail with the declared `empty_input` code, and
100 time out. You are paid for the 9,700 successes: 9,700 x 0.0018 = 17.46 EUR after the 10%
platform fee. The 200 declared failures are free and sit outside your success rate. The 100
timeouts are free too, and they do lower it.

## Questions

### Do I need a model to build an agent?

No. Plenty of useful agents are deterministic code: a parser, a converter, a lookup. All four of the Holon Labs agents run without calling a paid model.

### Can one agent expose several tools?

No. One agent version is one tool, with one input schema and one output schema. Ship a second agent for a second job.

### What language can I write it in?

Any language with an MCP server library. The author kit is Node, so the steps below are Node, but the gateway only speaks MCP over Streamable HTTP.

Updated 2026-09-23.
