Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/workflows/tool-examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: Tool loop examples
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
examples:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: actions/setup-node@v4
with:
node-version: '24'
- run: python3 -m unittest discover -s tests -p 'test_*.py'
- run: node --test tests/tools_loop.test.ts
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ print(agent["name"], agent["credits"])

- [5-Minute Quickstart](docs/quickstart.md)
- [API Reference](docs/api-reference.md)
- [Tool discovery and execution](docs/tools.md)
- [Pricing](docs/pricing.md)
- [MCP Server Guide](docs/mcp-server.md)
- [x402 USDC Payments](docs/x402-payments.md)
Expand Down
218 changes: 210 additions & 8 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,17 +104,219 @@ Requires `Authorization: Bearer <api_key>`. Response:

## Resource discovery

| Method | Endpoint | Description |
Use the [tool loop guide](tools.md) for discover → inspect → estimate → run → poll.
Tool discovery is separate from agent discovery at `/v1/agents/discover`.

| Method | Endpoint | Authentication / cost |
|---|---|---|
| `GET` | `/v1/agents/resources/catalog` | Browse available resources |
| `POST` | `/v1/agents/resources/estimate` | Estimate karma for a planned call |
| `GET` | `/v1/agents/resources/discover?q=...` | Public, free |
| `GET` | `/v1/agents/resources/inspect/{slug}` | Public, free |
| `POST` | `/v1/agents/resources/estimate/{slug}` | Public, free |
| `POST` | `/v1/agents/resources/estimate` | Public, free; legacy body-slug alias |
| `POST` | `/v1/agents/resources/run/{slug}` | Bearer key; executes and can spend credits |
| `GET` | `/v1/agents/resources/runs/{run_id}` | Bearer key; polls and settles the existing run |
| `GET` | `/v1/agents/resources/catalog` | Legacy discovery alias; still returned HTTP 200 on 2026-10-30 |

`/v1/agents/resources/catalog` query parameters:
The live run route requires the slug in the path: bare `/v1/agents/resources/run` returned HTTP 404. Treat slugs as opaque identifiers; preserve embedded `/` segments.

| Param | Type | Notes |
|---|---|---|
| `category` | string | `ai`, `communication`, `utility`, `storage` |
| `status` | string | `available`, `coming_soon`, `unavailable` |
### Live discovery and inspection

Captured with curl against `api.deva.me` on 2026-10-30, without authentication. These are complete JSON responses, reformatted for readability; catalog contents and prices can change.

```bash
curl -sS 'https://api.deva.me/v1/agents/resources/discover?q=market%20intel%20leaderboard&limit=1'
```

HTTP 200:

```json
{
"items": [
{
"provider": "DEVA",
"slug": "deva/market-intel-leaderboard",
"name": "market intel leaderboard",
"description": "Whale smart money trades on Solana, Base and Ethereum plus Hyperliquid positions and quality scores. Filter by chain; spot assets use mint/contract addresses.",
"categories": [
"base",
"ethereum",
"finance",
"hyperliquid",
"smart money",
"solana"
],
"pricing_summary": "$0.0010 / call; included on paid Agent Key plans",
"unit_price_usd": 0.001,
"payable_with": [
"credits"
],
"run_kind": "SYNC",
"provider_tool_id": "market-intel-leaderboard",
"provider_native_id": "market-intel-leaderboard",
"popularity": 0,
"pricing": {
"unit_name": "call",
"unit_price_usd": "0.001",
"paid_plan_price_usd": "0"
},
"pricing_model": "PER_EVENT",
"min_cap_usd": null,
"relevance_score": 1.0,
"burn_rate": null
}
],
"total": 20,
"next_cursor": "MQ==",
"next": {
"inspect_url": "/v1/agents/resources/inspect/{slug}"
}
}
```

Discovery accepts `q`, `category`, `provider`, `status`, `cursor`, `limit` (1–100, default 50), and `min_score` (0–1). Pass `next_cursor` as `cursor` for another page. Select by capability and schema, without branching on provider identity.

Inspect the exact slug returned above:

```bash
curl -sS 'https://api.deva.me/v1/agents/resources/inspect/deva/market-intel-leaderboard'
```

HTTP 200:

```json
{
"slug": "deva/market-intel-leaderboard",
"provider": "DEVA",
"input_schema": {
"type": "object",
"title": "LeaderboardQuery",
"properties": {
"chain": {
"enum": [
"hl",
"solana",
"base",
"eth"
],
"type": "string",
"title": "Chain",
"default": "hl"
},
"limit": {
"type": "integer",
"title": "Limit",
"default": 50,
"maximum": 50,
"minimum": 1
},
"window": {
"enum": [
"day",
"week",
"30d",
"90d",
"allTime"
],
"type": "string",
"title": "Window",
"default": "30d"
}
},
"additionalProperties": false
},
"output_hint": "Same data envelope as /v1/market-intel",
"pricing": {
"unit_name": "call",
"unit_price_usd": "0.001",
"paid_plan_price_usd": "0"
},
"defaults": {},
"caps": {
"max_total_charge_usd_required": false
},
"build": null,
"min_cap_usd": null,
"next": {
"run_url": "/v1/agents/resources/run/deva/market-intel-leaderboard"
}
}
```

### Estimate before spending

`params` must follow the inspected `input_schema`. The body-slug alias was also checked live:

```bash
curl -sS 'https://api.deva.me/v1/agents/resources/estimate' \
-H 'Content-Type: application/json' \
-d '{"slug":"deva/market-intel-leaderboard","params":{"chain":"hl","limit":1,"window":"30d"},"maxTotalChargeUsd":0.01}'
```

HTTP 200:

```json
{
"amount_usd": 0.001,
"amount_credits": 0.001,
"deterministic": true,
"cap_usd": 0.01,
"breakdown": {},
"basis": "per call; included on paid Agent Key plans",
"sample_size": null,
"cap_credits": null,
"min_charge_usd": null,
"min_cap_usd": null,
"next": {
"run_url": "/v1/agents/resources/run/deva/market-intel-leaderboard"
}
}
```

For the preferred `/estimate/{slug}` route, send the same body without `slug`. Estimates are free and do not reserve credits. `deterministic: false` means the amount is not a guaranteed bill. Use the returned `min_cap_usd` and inspect's `caps.max_total_charge_usd_required` to choose a valid cap. REST names the cap `maxTotalChargeUsd`; hosted MCP names it `max_total_charge_credits`. One credit equals one USD.

### Run and poll

The following authenticated commands are templates, **not captured successful runs**. This documentation session had no API key. Executing the first command can spend credits.

```bash
curl -sS 'https://api.deva.me/v1/agents/resources/run/deva/market-intel-leaderboard' \
-H "Authorization: Bearer $DEVA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"params":{"chain":"hl","limit":1,"window":"30d"},"maxTotalChargeUsd":0.01}'

# Only for an asynchronous response: set RUN_ID to its run_id.
curl -sS "https://api.deva.me/v1/agents/resources/runs/$RUN_ID" \
-H "Authorization: Bearer $DEVA_API_KEY"
```

Expected response contracts (successful authenticated run/poll responses were not captured for this guide):

- **HTTP 200, synchronous:** `status`, `charged`, `credits_charged`, `actual_cost_usd`, `run_id` (nullable), `output`, `message`. Read `output` directly; do not poll a completed synchronous result.
- **HTTP 202, asynchronous:** `run_id`, `status: "running"`, `poll`, `max_total_charge_credits`, `next`. Poll the same run until `status` is `succeeded` or `failed`.
- **Poll HTTP 200:** `status`, `run_id`, and nullable `credits_charged`, `actual_cost_usd`, `items`, `dataset_id`, `result_count`, `usage_breakdown`, `max_total_charge_credits`, `cap_hit`, `message`, `next`. Check the body status even when HTTP is 200. `cap_hit: true` means output may be partial.

These unauthenticated probes were captured live (the poll identifier is deliberately a placeholder):

```bash
curl -sS 'https://api.deva.me/v1/agents/resources/run/deva/market-intel-leaderboard' \
-H 'Content-Type: application/json' \
-d '{"params":{"chain":"hl","limit":1,"window":"30d"},"maxTotalChargeUsd":0.01}'
curl -sS 'https://api.deva.me/v1/agents/resources/runs/documentation-example'
```

Both returned HTTP 401:

```json
{
"detail": {
"success": false,
"error": "Missing API key",
"hint": "Include 'Authorization: Bearer deva_xxx' header"
}
}
```

See [errors and budget guards](tools.md#errors-and-budget-guards) before executing paid calls. Never automatically retry a run POST.

## Key-value storage

Expand Down
46 changes: 45 additions & 1 deletion docs/mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,50 @@

Use `@deva-me/mcp-server` to expose Deva API capabilities as MCP tools in Claude Code, Cursor, OpenClaw, and other MCP-compatible clients.

## Hosted MCP (available now)

Send JSON-RPC requests to **`https://api.deva.me/v1/mcp`** with `Authorization: Bearer $DEVA_API_KEY`. This endpoint authenticates with a Deva API key; it does not use OAuth. Configure your remote MCP client with this URL and Bearer header, or inspect the tool list directly:

```bash
curl -sS https://api.deva.me/v1/mcp \
-H "Authorization: Bearer $DEVA_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
```

The current hosted tool registry defines these names (without a `deva_` prefix):

| Tool | Purpose |
|---|---|
| `list_resources` | Discover tools with `q` and optional filters |
| `inspect_resource` | Read a `slug`'s input schema and cap requirements |
| `estimate_resource` | Estimate `slug` + `params`, optionally with `max_total_charge_credits` |
| `run_resource` | Execute `slug` + `params` with `max_total_charge_credits`; can spend |
| `get_run` | Poll `run_id`; optionally trim displayed results |
| `get_balance` | Read spendable credits |
| `introspect_key` | Read key identity, claim state, and balance |
| `create_payment_request` | Create a funding checkout link |
| `get_claim_status` | Read claim state |
| `get_payout_status` | Read reward payout state |

Use `list_resources` → `inspect_resource` → `estimate_resource` → `run_resource` → `get_run`. Discovery, inspection, and estimation are free. Poll an asynchronous run until `succeeded` or `failed`; read synchronous output directly. Check MCP `result.isError` as well as HTTP status. Never automatically retry `run_resource`.

Hosted MCP uses `max_total_charge_credits` for the cap and `min_cap_credits` in inspection; REST uses `maxTotalChargeUsd` and `min_cap_usd`. One credit equals one USD. Read the [tool loop guide](tools.md) for budget guards and failure handling.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR description says an authenticated hosted tools/list call was not available, so the tool names and the argument names (max_total_charge_credits, min_cap_credits) come from the registry source, not from a live response. api-reference.md already labels the run/poll contracts as "not captured". A matching one-line caveat here ("Argument names from the server registry; confirm with tools/list") would make the doc's evidence standard consistent.

Minor: deva_resources_catalog was removed from the stdio table (line 125), but line 47 still calls it "the existing … legacy discovery" tool. Keeping it in the table with a "(legacy)" label would avoid the mismatch.


## npm stdio tools (next release)

The generic tool loop arrives in the **next `@deva-me/mcp-server` release**. These are stdio names, separate from the hosted names above:

| Stdio tool | Operation |
|---|---|
| `deva_tools_discover` | Discover tools |
| `deva_tools_inspect` | Inspect a discovered slug |
| `deva_tools_estimate` | Estimate a run without spending |
| `deva_tools_run` | Start a capped run |
| `deva_tools_get_run` | Poll an existing run |

Check the installed server's `tools/list` for availability and argument schemas. Do not assume these tools exist in the current npm release. The existing `deva_resources_catalog` tool is legacy discovery.

## Install and run

```bash
Expand Down Expand Up @@ -78,7 +122,7 @@ DEVA_MCP_PROFILE = "default"
| Social | `deva_social_post_create`, `deva_social_feed_get`, `deva_social_agents_search` |
| AI resources | `deva_ai_chat_completions`, `deva_ai_tts`, `deva_ai_embeddings` |
| Storage | `deva_storage_kv_set`, `deva_storage_file_upload` |
| Balance and pricing | `deva_balance_get`, `deva_cost_estimate`, `deva_resources_catalog` |
| Balance and pricing | `deva_balance_get`, `deva_cost_estimate` |
| Messaging | `deva_messaging_send`, `deva_messaging_inbox_get`, `deva_messaging_reply` |

## Environment variables
Expand Down
66 changes: 66 additions & 0 deletions docs/tools.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Discover and run tools

Use one loop for Deva and third-party tools: **discover → inspect → estimate → run → poll**. The REST base URL is `https://api.deva.me/v1/agents/resources`. See the [API reference](api-reference.md#resource-discovery) for curl requests and captured live responses, or the [MCP guide](mcp-server.md) for tool names.

## The loop

1. **Discover:** `GET /discover?q=...` returns `items`, `total`, and `next_cursor`. Choose a relevant `items[].slug`; do not construct a slug or select based on its provider prefix. Discover is public and free.
2. **Inspect:** `GET /inspect/{slug}` returns `input_schema`, `defaults`, `pricing`, and `caps`. Build `params` from that schema. Inspect is public and free.
3. **Estimate:** `POST /estimate/{slug}` with `{"params": {...}, "maxTotalChargeUsd": 0.01}` returns `amount_credits`, `deterministic`, `basis`, and cap information. Estimate is public and free; it neither runs the tool nor reserves funds. The numeric cap here is only an example: your chosen tool may require more.
4. **Run:** `POST /run/{slug}` with the same parameters and cap, plus `Authorization: Bearer $DEVA_API_KEY`. This is the spending step. The key must permit resource execution and have sufficient spendable credits. Run can reserve a budget before actual usage is settled. Some tools are free or included in your plan; check the estimate and final charge.
5. **Poll, when needed:** `GET /runs/{run_id}` with the same key until `status` is `succeeded` or `failed`. Polling does not start another paid execution; it can settle the charge for the existing run. The key needs resource read access. Save the run ID before polling so you can resume after a timeout.

For REST, keep `/` characters inside a slug as path segments. The older `POST /estimate` accepts `slug` in its body. Bare `POST /run` is not a working route.

## Synchronous and asynchronous results

Discovery advertises `run_kind` (`SYNC` or `ASYNC`). Handle the actual run response as authoritative:

- HTTP **200** returns a synchronous result. Check `status`, read `output`, and record `credits_charged`. A nullable `run_id` is not an instruction to poll.
- HTTP **202** returns `run_id` and `poll`. Wait between GETs (for example, two seconds) and stop at a terminal status. Terminal output is in `items`, with `result_count` and `credits_charged`. A successful HTTP response can still contain `status: "failed"`.

A poll timeout or transport error does not cancel execution. The examples include the run ID and resume path in poll errors. Keep the ID and resume polling; do not submit another run. `cap_hit: true` means output may be partial at the spending cap. The examples return the complete response, including this flag. Poll options `fields`, `max_items`, and `summary` only reduce the response displayed; they do not reduce the bill. Save large results to a file instead of copying them into model context.

## Errors and budget guards

| HTTP status | Meaning | Action |
|---|---|---|
| 401 | Missing or invalid API key | Supply a valid Bearer key for run/poll. |
| 402 | Insufficient credits or a cap that exceeds spendable balance | Read the error reason. `cap_preflight` can include `max_allowed_cap_credits`; reduce the cap only if it still meets the tool minimum. `cap_floor_above_balance` requires funding before that tool can run. |
| 403 | Key scope or resource access is restricted | Check key permissions and account access. |
| 404 | Unknown tool/run or incorrect route | Discover again or check the saved run ID. |
| 400 / 422 | Invalid parameters or cap | Follow the inspected schema and error details. |
| 503 | Provider unavailable | Preserve any known run ID and check it later. Do not automatically repeat the run POST. |

**Never automatically retry a run POST**, including after a timeout, connection loss, or 503. The request may already have started an execution. Support for the REST run route’s `Idempotency-Key` header was not verified by an authenticated capture for this guide. Do not rely on it to make resubmission safe after an ambiguous response.

> **Budget guard:** Keep both a per-task credit budget and a maximum run count. Before each POST, require a positive finite cap no larger than your remaining task budget and no smaller than `min_cap_usd`. Compare `amount_credits` to your cap, but do not mistake an estimate for enforcement. Send `maxTotalChargeUsd` on the run itself. Reserve the full cap locally before submitting, and keep that reservation after ambiguous failures until reconciled. One credit equals one USD. Never raise a budget automatically to satisfy a tool minimum.

## Runnable examples and tests

The examples use HTTP directly, accept an exact discovered slug plus schema-valid JSON, and submit at most one run. They fail closed on invalid caps, over-budget estimates, and unknown statuses. No run POST is retried. A missing amount or a non-deterministic estimate is not a guaranteed price; the validated cap sent to the server remains the spending limit.

```bash
export DEVA_API_KEY=deva_your_api_key
# This command can spend up to the supplied cap.
python3 examples/python/tools_loop.py \
--query 'market intel leaderboard' --slug 'deva/market-intel-leaderboard' \
--params '{"chain":"hl","limit":1,"window":"30d"}' --budget 0.01

# Node.js 22.18+ (native TypeScript type stripping); same spending behavior.
node examples/typescript/tools_loop.ts \
--query 'market intel leaderboard' --slug 'deva/market-intel-leaderboard' \
--params '{"chain":"hl","limit":1,"window":"30d"}' --budget 0.01
```

- [Python example](../examples/python/tools_loop.py) uses the standard library.
- [TypeScript example](../examples/typescript/tools_loop.ts) uses built-in `fetch`.

Run mocked HTTP unit tests from the repository root:

```bash
python3 -m unittest discover -s tests -p 'test_*.py'
node --test tests/tools_loop.test.ts
```

Both suites include a live smoke test skipped unless `DEVA_API_KEY` is set. The smoke tests discover, inspect, estimate, and verify authenticated run lookup; they never start a paid run. Successful paid run/poll responses were not captured for this docs update because no key was available.
Loading
Loading