MCP tools: search & execute
The server identifies as { name: "harbor", version: "0.1.0" } and exposes two tools.
search
Search Harbor operations, venues, and playbooks.
Input
{ query: string } // min length 1, no other keysOutput: a JSON text block with up to 8 results:
{
id: string
kind: "op" | "venue" | "merchant" | "playbook"
name: string
family: string
summary: string
tags: string[]
signature: string // the operation's argument shape
example: string // a ready-to-run harbor.* call
score: number
}Ranking. Full-text search (prefix and fuzzy matching) over name, family, tags and summary, boosted in that order. An exact operation name gets a large boost, then an exact family. So {"query":"ledger.fee"} always returns ledger.fee first.
What is indexed: every operation, the 23 venues, the AP4M demo merchants (risk-oracle, venue-yield-desk), and six local playbooks: handle-approval, link-account, provide-amm-liquidity, revoke, swap-safely, vault-deposit. Read a playbook with harbor.skill.read({ id }).
execute
Execute a sandboxed script against Harbor's typed operation namespaces.
Input
{ code: string } // the body of an async function; use `return`The harbor namespace mirrors operation names: ledger.account.info is harbor.ledger.account.info(args). Every call resolves to the envelope. Calls do not throw on operation failure, so check .ok.
const [fee, reserves] = await Promise.all([
harbor.ledger.fee({ network: "testnet" }),
harbor.server.reserves({ network: "testnet" })
]);
if (!fee.ok) return fee;
return { fee: fee.data, reserves: reserves.ok ? reserves.data : null };Sandbox limits
| Limit | Value |
|---|---|
| Wall-clock deadline | 10 seconds |
| Operation calls per script | 30 (OP_CALL_LIMIT) |
| Serialized output | 24 KiB, then truncated with … [output truncated] (becomes plain text) |
| Network | none: fetch throws Outbound network access is disabled |
| Memory (QuickJS path) | 64 MiB heap, 512 KiB stack |
The engine runs scripts in a Cloudflare Dynamic Worker with no outbound network when a loader binding exists, and falls back to QuickJS otherwise. The same guards apply to both.
When execute is unavailable
execute is only registered when HARBOR_MCP_EXECUTION_ENABLED is unset or "true". The free/QuickJS deployment profile sets it to "false": the server is search-only and tells the client so in its instructions.
Patterns
Return only what you need. Output is capped at 24 KiB, so map large results:
const r = await harbor.venues.list({});
if (!r.ok) return r;
return r.data.map((v) => v.id);Build and simulate before executing. For any write, call a builder (tx.swap, tx.offer, ...), inspect simulate.engine_result and preflight, then tx.execute. See Executing transactions.
Stop on needs_approval. Return the approval to the user. Do not try to work around it.
Never retry a block. A blocked result lists every failed rule. Explain them.