KeeperHub / KeeperHub/keeperhub

Action schemas drop the field label, so the unit of every protocol amount is invisible to an agent

Closed Beginner friendly
#2,466 1 comment 0 reactions 0 assignees View on GitHub
accepted bug confirmed
Dominant language
TypeScript
Stars
24
Forks
93
Avg merge
1d 8h
Merged PRs (30d)
266

Description

## Reason

`search_protocol_actions` describes `aave-v3/supply` like this:

```json
"requiredFields": {
"network": "string (chain ID)",
"asset": "string",
"amount": "string",
"onBehalfOf": "string"
}
```

and `web3/approve-token` like this:

```json
"amount": "string (supports {{@nodeId:Label.field}} templates) - 100.50 or \"max\" for unlimited"
```

The first `amount` is base units. The second is human units. Nothing in the
first schema says so, and an agent chaining the two - which is the normal
shape of an approve-then-supply - has no way to tell them apart. Sending `"1"`
to `aave-v3/supply` supplies 0.000001 USDC. Sending `"1000000"` to
`approve-token` approves a million.

**What told me to expect otherwise.** The platform knows the unit. The action's
own definition carries it: `protocols/aave-v3.ts` declares
`amount: { label: "Amount (wei)" }`, the editor shows that label, and
`docs/plugins/aave-v3.md` prints `Amount (wei)` in its parameter table. The unit
is stated everywhere a human looks and nowhere an agent looks.

**Where it is lost.** `transformPluginAction` in `lib/action-schemas/builder.ts`
builds the MCP-facing field description from the type and the placeholder
only:

```ts
const fieldDesc = `${mapFieldType(field)}${field.placeholder ? ` - ${field.placeholder}` : ""}`;
```

`field.label` is not consulted. Protocol action inputs carry their unit in the
label and have no placeholder, so they project to bare `"string"`.
`approve-token` happens to carry a placeholder, so its convention survives. The
difference between the two schemas above is not that one action documents its
unit and the other does not; it is which property the author put the
information in.

**How much is affected.** Across the fourteen protocol files I checked, 102
input labels carry a unit in parentheses - `(wei)`, `(ray)`, `(bps)`,
`(shares)`, `(seconds)`, `(18 decimals)`. All 102 reach an MCP client as
`"string"`. Aave, Morpho, Lido, Spark, Sky, Curve, Uniswap, Pendle, Yearn,
Rocket Pool, Ethena, Compound and the wrapped-token actions are all in that
set.

**What it costs someone who hits it.** We hit it building an Aave supply path
for the current hackathon. The capability spike guessed human units from the
`approve-token` precedent and would have supplied a millionth of the intended
amount. The correct answer was only recoverable by reading `protocols/aave-v3.ts`
in this repository, which is not a surface an agent has. An agent that guesses
the other way approves or supplies a million times too much, and the platform's
$100 stablecoin cap is the only thing between that and a loss.

## Scope

**In scope:** the field description string produced by `transformPluginAction`
for every plugin action, which feeds `search_protocol_actions`,
`list_action_schemas`, `/api/mcp/schemas` and the public `/api/action-schemas`.
They share the builder, so one change reaches all four.

**Checked and fine:** the labels themselves are correct and consistent with the
docs pages. `mapFieldType` is fine for the types it special-cases (`chain-select`,
`token-select`, the ABI fields, `select`). The Aave docs page is right. The
response shape - `Record` for `requiredFields` and
`optionalFields` - does not change under the plan below.

**Sibling risk:** any other consumer of `ActionConfigFieldBase` that projects a
field to text without reading `label`. I looked only at the action-schema
builder.

**Not in scope:** whether protocol amounts should accept human units, or
whether `approve-token` should accept base units. Both are consistent with
their own docs. The problem is that only one of them says so to an agent.

This is one issue: one function, one projection.

## Plan

Include the label in the description when the field has one. Something like:

```ts
const parts = [mapFieldType(field)];
if (field.label) parts.push(field.label);
if (field.placeholder) parts.push(`e.g. ${field.placeholder}`);
const fieldDesc = parts.join(" - ");
```

which produces `"string - Amount (wei)"` for the Aave input and
`"string (supports … templates) - Amount - e.g. 100.50 or \"max\" for unlimited"`
for the approval. Additive: every existing description keeps its type prefix,
and the shape is unchanged. One unit test on `transformPluginAction` covering a
label-only field, a placeholder-only field, and one with both.

If a label in the description is unwanted in the general case, the narrower
alternative is a `unit` property on `ActionConfigFieldBase` that the builder
emits when present, and a pass over the 102 labels to set it. That is strictly
more work for the same information, but it would let the label stay purely
presentational.

Happy to open the PR for the first shape as soon as this is accepted; the
change is small and I have the test written.

---

Found while building MIRSAD for the Agent Economy hackathon. Our own executor
sidesteps the ambiguity by calling the Pool ABI directly through
`execute_contract_call`, where the unit is unambiguous - but that means not
using the protocol action at all, which cannot be the intended answer.

Contributor guide

Open the contributing guide

Research direction

Start in lib/action-schemas/builder.ts at transformPluginAction and inspect how field type, label, and placeholder are projected into descriptions. Compare the examples in protocols/aave-v3.ts and the approve-token definition, then add the described transformPluginAction unit coverage for label-only, placeholder-only, and combined fields; done means the schema descriptions retain their type prefix and expose the relevant label text.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
api, backend-api-design, blockchain
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
84/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.