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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 0.54.1

### Fixed — `blockrun_search` is one flat price, and the tool said otherwise

The gateway repriced `/v1/search` to a flat $0.08 per call on 2026-09-29
(blockrun `SEARCH_PRICE_PER_CALL_USD`: xAI never sees `max_results`, so the
count never drove cost). This server kept reserving and advertising
`$0.025 × max_results`: the default call reserved $0.2645 against a $0.081
charge (3×), a 50-source call $1.3145 (16×), and the description told every
agent search was "expensive by default" and to ask for fewer sources.

Live 402 quotes, 2026-10-06: Base `amount` 81000 and Solana 80000 at
`max_results` 1, 10 and 50 alike. The reserve is now `withTxFee($0.08)` =
$0.082 at any size, never below the charge; `test/search-cost.test.ts` pins
it flat. Tool description, `skills/search`, `skills/blockrun`, `skills/surf`
and the README price row now say $0.08 flat; the full/research schema-token
rows moved with the shorter description (14,018 / 2,727).

## 0.54.0

### Fixed — seedance-2.5 takes reference clips; the clip ceiling is per model
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,10 @@ Package managers have shown install size for decades. Almost no MCP server shows

| Profile | Tools | Context |
|---------|-------|---------|
| `full` *(default)* | 19 | 14,046 |
| `full` *(default)* | 19 | 14,018 |
| `trading` | 8 | 5,411 |
| `media` | 7 | 6,827 |
| `research` | 5 | 2,755 |
| `research` | 5 | 2,727 |
| `chat` | 3 | 2,082 |

Running `--profile trading` instead of the default costs **61% less context** for the same trading
Expand Down Expand Up @@ -380,7 +380,7 @@ npx -y @blockrun/mcp@latest skills install --to ~/.codex/skills
| `blockrun_polymarket_read` | Read-only Polymarket positions/open orders plus executable live order previews, separated for MCP clients that enforce tool safety annotations | free |
| `blockrun_polymarket` | **Trade on Polymarket** (CLOB V2): place/cancel real bets, positions, redeem winnings — signed locally, settled in pUSD from a gasless deposit wallet. Confirm-gated, $25/order default cap. [Details ↓](#-polymarket-trading) | free tool; bets are your funds |
| `blockrun_exa` | Neural web search (Exa) — research, competitors, papers, URL content | $0.01 + fee/query |
| `blockrun_search` | Grok Live Search — web + news with citations | $0.025 × max_results |
| `blockrun_search` | Grok Live Search — web + news with citations | $0.08 flat + network fee |
| `blockrun_dex` | Live DEX prices via DexScreener | free |
| `blockrun_rpc` | Raw JSON-RPC on <!-- br:chains.rpc -->40<!-- /br:chains.rpc --> chains (Ethereum, Base, Solana, Bitcoin, Sui, NEAR, …) via Tatum | $0.002 + fee/call |
| `blockrun_defi` | DefiLlama — protocol TVL, chain TVL, yield pools (APY), token prices | $0.001–0.005 + fee/call |
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.54.0
0.54.1
4 changes: 2 additions & 2 deletions docs/mcp-schema-overhead.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ Written 2026-09-01, verified against `@modelcontextprotocol/sdk` 1.29.0. Numbers

| Profile | Tools | Context |
|---------|-------|---------|
| `full` *(default)* | 19 | 14,046 |
| `full` *(default)* | 19 | 14,018 |
| `trading` | 8 | 5,411 |
| `media` | 7 | 6,827 |
| `research` | 5 | 2,755 |
| `research` | 5 | 2,727 |
| `chat` | 3 | 2,082 |

Descriptions are ~54% of it, input schemas ~42%. `--profile trading` costs 61% less than the
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@blockrun/mcp",
"version": "0.54.0",
"version": "0.54.1",
"mcpName": "io.github.BlockRunAI/blockrun-mcp",
"description": "BlockRun MCP Server - Give your AI agent web search, deep research, prediction markets, and crypto data. Pay per call from a USDC wallet (Solana or Base) or a BlockRun API key.",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion skills/blockrun/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Deeper wallet, budget and x402 mechanics — including calling the HTTP API dire
| Video of one *specific* real person | `blockrun_realface` | Enroll a face, then pass `real_face_asset_id` to `blockrun_video` |
| Make music | `blockrun_music` | Full-length tracks |
| Speak text aloud | `blockrun_speech` | Also sound effects |
| "What just happened" — news, live web | `blockrun_search` | Freshest; priced per source |
| "What just happened" — news, live web | `blockrun_search` | Freshest; flat $0.08 per call |
| Research, papers, competitors, page contents | `blockrun_exa` | Semantic, not keyword |
| Event odds, betting markets | `blockrun_markets` | Reading only |
| Polymarket positions, open orders, order preview | `blockrun_polymarket_read` | Reads and previews only — structurally cannot sign |
Expand Down
12 changes: 6 additions & 6 deletions skills/search/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ triggers:

# Live Search (Grok)

Real-time web + news search with AI-summarized results and citations. **PRICED PER SOURCE and expensive by default: $0.025 × `max_results`, +5% +$0.001 → default 10 settles $0.2635 per call.** Best for *fresh* queries; for semantic / neural research use `blockrun_exa` instead.
Real-time web + news search with AI-summarized results and citations. **Flat $0.081 per call on Base ($0.08 + $0.001 network fee; $0.08 on Solana), at any `max_results`** — verified against live 402 quotes 2026-10-06. Best for *fresh* queries; for semantic / neural research use `blockrun_exa` instead.

## How to Call from MCP

Expand All @@ -33,7 +33,7 @@ blockrun_search({ body: {
|---|---|---|---|
| `query` | yes | string | Natural-language search query |
| `sources` | no | string[] | Subset of `["web","news"]`. Default: `["web"]` — pass both for news coverage. There is no X/Twitter source (removed upstream 2026-07-05; asking for `"x"` is refused before payment). Does NOT multiply price. |
| `max_results` | no | number | 1–50, default 10. **Drives the price** — ~$0.0263 charged per source. Pass a small number to cap spend; the gateway prices the raw value and does not floor fractions. |
| `max_results` | no | number | 1–50, default 10. Does **not** change the price — ask for as many sources as the question needs. |
| `from_date` | no | string | `YYYY-MM-DD` lower bound on result date |
| `to_date` | no | string | `YYYY-MM-DD` upper bound |

Expand All @@ -53,14 +53,14 @@ blockrun_search({ body: {
```ts
blockrun_search({ body: { query: "Ethereum ETF approval SEC", sources: ["news","web"], max_results: 8 } })
```
**Cost: ~$0.2110** (8 sources: $0.025 × 8 × 1.05 + $0.001).
**Cost: $0.081.**

### 2. "What happened in Solana's latest outage?"

```ts
blockrun_search({ body: { query: "Solana outage today", sources: ["news","web"], max_results: 15 } })
```
**Cost: ~$0.3948** (15 sources: $0.025 × 15 × 1.05 + $0.001). There is no
**Cost: $0.081.** There is no
X/Twitter source: `sources: ["x"]` is refused before payment with the live
list, so do not promise "what X is saying" — say what the news and web say.

Expand All @@ -73,7 +73,7 @@ blockrun_search({ body: {
from_date: "2026-02-17"
}})
```
**Cost: $0.2635** (10 sources, the default — verified live).
**Cost: $0.081** (the default 10 sources — same price as any other count).

## search vs exa — Pick the Right Tool

Expand All @@ -87,7 +87,7 @@ blockrun_search({ body: {
## Notes

- Returns AI-summarized text + a list of sources with URLs. The summary is one paragraph; sources let you drill in.
- **Price is per result, not per source.** `max_results: 20` with one source or three sources both charge **$0.526** ($0.025 × 20 × 1.05 + $0.001 — verified live). Pass a smaller `max_results` to cap spend.
- **One flat price per call.** Neither `sources` nor `max_results` changes it: the upstream decides how many searches to run, so the count never drove the cost (repriced 2026-09-29).
- Date filters are strict — results outside the window are dropped, not down-ranked.

## Reference
Expand Down
2 changes: 1 addition & 1 deletion skills/surf/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ not; route the question with the table below.
| Polymarket / Kalshi markets, prices, positions | `prediction-market/*`, `search/polymarket`, `search/kalshi` | `blockrun_markets` — see [`skills/prediction-markets/SKILL.md`](../prediction-markets/SKILL.md) | $0.0075 + fee |
| Who is this Polymarket wallet, and which wallets are theirs | `wallet/detail`, `wallet/labels/batch` (for Polymarket traders only) | `blockrun_markets` `polymarket/wallet/identity/{wallet}` + `polymarket/wallet/{address}/cluster` | $0.0075 + fee |
| Gas price, a transaction, a balance, a contract read | `onchain/gas-price`, `onchain/tx` | `blockrun_rpc` (`eth_gasPrice`, `eth_getTransactionByHash`, `eth_getBalance`, `eth_call`) — see [`skills/rpc/SKILL.md`](../rpc/SKILL.md) | $0.002 + fee |
| Web / news search | `search/web`, `search/news`, `news/feed` | `blockrun_exa` (neural) or `blockrun_search` (Grok Live Search, web + X + news) | $0.01 + fee / $0.025 × results |
| Web / news search | `search/web`, `search/news`, `news/feed` | `blockrun_exa` (neural) or `blockrun_search` (Grok Live Search, web + X + news) | $0.01 + fee / $0.08 + fee |

"fee" is the gateway's flat network fee — $0.001 per call on Base today; the
Solana gateway quotes the base alone; the account rail charges no fee.
Expand Down
56 changes: 13 additions & 43 deletions src/tools/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,53 +14,23 @@ import { asStructuredContent, coerceBody } from "../utils/body.js";
import { buildClient } from "../utils/wallet.js";
import { ledgerFallback, rawPost, type RawClient } from "../utils/raw-call.js";
import { formatError } from "../utils/errors.js";
import { withTxFee } from "../utils/tx-fee.js";
import { pathToolFailure } from "../utils/path-tool-catch.js";
import { hasPathTraversal } from "../utils/path-safety.js";
import type { BudgetState } from "../types.js";


// Pricing scales with max_results (capped 1–50, default 10 upstream) at
// $0.025 per returned source. Mirrors getSearchPrice() in
// blockrun/src/app/api/v1/search/route.ts.
const SEARCH_DEFAULT_MAX_RESULTS = 10;
// READ THE `payment-required` HEADER, NOT THE JSON BODY.
//
// A 402's JSON `price` field is the BASE. What x402 actually charges is
// `maxAmountRequired` inside the base64 `payment-required` header, and it is
// base + a $0.002 flat transaction fee. The two differ on every route:
//
// max_results body price header (CHARGED)
// 1 $0.0263 $0.0283
// 10 (default) $0.2625 $0.2645
// 50 $1.3125 $1.3145
//
// Keying the reserve off the body — which is what 0.30.10 did — leaves the gate
// $0.002 short on every search. Same trap as $0.0075 vs $0.0095 elsewhere: the
// base is not the price.
const SEARCH_MICRO_PER_SOURCE = 26_250; // $0.025 x 1.05 base, exact in micro-dollars
const SEARCH_MICRO_QUANTUM = 100; // the gateway rounds the base UP to 4dp = 100 micro
const SEARCH_MICRO_TX_FEE = 2_000; // $0.002 flat, added on top of the rounded base
//
// Integer micro-dollars throughout, because the float form is not exact:
// 0.025 * 1.05 is 0.026250000000000002, which rounds to $0.026251 — under the
// base itself, let alone the charge. A reserve must never be short.
// One flat price per call: $0.08 base + the transaction fee, whatever
// max_results is. Mirrors SEARCH_PRICE_PER_CALL_USD in blockrun/src/lib/models.ts
// (repriced 2026-09-29 — xAI never sees the count, so the count never drove
// cost). Live 402 quotes 2026-10-06, `amount` in the payment requirement:
// Base 81000 (= $0.081) and Solana 80000 at max_results 1, 10 and 50 alike.
// The old $0.025 x max_results reserve over-reserved the default call 3x and
// a 50-source call 16x, blocking searches a budget could afford.
const SEARCH_BASE_USD = 0.08;

export function estimateSearchCost(body: unknown): number {
const reserve = (max: number) => {
const base = Math.ceil((SEARCH_MICRO_PER_SOURCE * max) / SEARCH_MICRO_QUANTUM) * SEARCH_MICRO_QUANTUM;
return (base + SEARCH_MICRO_TX_FEE) / 1e6;
};
if (!body || typeof body !== "object") return reserve(SEARCH_DEFAULT_MAX_RESULTS);
const raw = (body as { max_results?: unknown }).max_results;
// Do NOT floor. The gateway prices the RAW value: max_results 2.7 quotes a base
// of $0.0709 while 2 quotes $0.0525 (2.7 x 0.025 x 1.05 exactly). Flooring to 2
// reserved $0.0525+fee against a $0.0709+fee charge. Cap at 50 to match the
// upstream ceiling; anything non-finite falls back to the default.
const max =
typeof raw === "number" && Number.isFinite(raw) && raw > 0
? Math.min(50, raw)
: SEARCH_DEFAULT_MAX_RESULTS;
return reserve(max);
export function estimateSearchCost(_body?: unknown): number {
return withTxFee(SEARCH_BASE_USD);
}

// The gateway's `sources` enum. X/Twitter was dropped upstream on 2026-07-05
Expand Down Expand Up @@ -94,12 +64,12 @@ export function registerSearchTool(server: McpServer, budget: BudgetState): void
server.registerTool(
"blockrun_search",
{
description: `Grok Live Search — real-time web + news with AI-summarized results and citations. PRICED PER SOURCE and expensive by default: $0.025 × max_results, +5% gateway buffer — default max_results=10 settles ~$0.26 (max_results=50 → ~$1.31). Pass a smaller max_results to cap spend; for a plain fact, 3 sources (~$0.08) is usually enough.
description: `Grok Live Search — real-time web + news with AI-summarized results and citations. Flat $0.08 per call plus the gateway's network fee ($0.001 on Base today, none on Solana; we reserve $0.002) — the same price at any max_results, so ask for as many sources as the question needs.

Common shape:
- body: { query: "...", sources: ["web","news"], max_results: 10, from_date: "YYYY-MM-DD", to_date: "YYYY-MM-DD" }

\`sources\` accepts any subset of ["web","news"] (default ["web"] — pass both for news coverage). There is no X/Twitter source (removed upstream 2026-07-05; asking for it is refused before payment). \`max_results\` is 1–50 (default 10) and drives the price — pass a smaller value if you want to cap spend.
\`sources\` accepts any subset of ["web","news"] (default ["web"] — pass both for news coverage). There is no X/Twitter source (removed upstream 2026-07-05; asking for it is refused before payment). \`max_results\` is 1–50 (default 10); it does not change the price.

Full request shape + worked examples in the \`search\` skill (\`skills/search/SKILL.md\`).`,
annotations: TOOL_ANNOTATIONS.readOnlyOpenWorld,
Expand Down
72 changes: 24 additions & 48 deletions test/search-cost.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,71 +4,47 @@ import assert from "node:assert/strict";
import { estimateSearchCost } from "../src/tools/search.js";
import { estimateExaCost } from "../src/tools/exa.js";

// blockrun_search is the most expensive tool per call in the server — priced per
// SOURCE, so a default call settles ~$0.26 while most tools cost $0.001. The gate
// can only stop a looping agent if the reserve is >= what the gateway settles.
// blockrun_search is one flat price per call. These are what x402 ACTUALLY
// charges — the `amount` in a live 402's payment requirement (free to request:
// send any call with no payment header). Captured 2026-10-06 on Base:
// 81000 micro-USDC at max_results 1, 10 and 50 alike; Solana quoted 80000.
//
// These are what x402 ACTUALLY charges — `maxAmountRequired` decoded from the
// base64 `payment-required` header on a live 402 (free to request: send any call
// with no payment header). Captured 2026-07-15.
//
// They are NOT the 402's JSON `price` field. That field is the BASE, and the
// charge is base + a $0.002 flat transaction fee:
//
// max_results body price header (CHARGED) <- pin the RIGHT column
// 1 $0.0263 $0.0283
// 5 $0.1313 $0.1333
// 10 $0.2625 $0.2645
// 20 $0.5250 $0.5270
// 50 $1.3125 $1.3145
//
// 0.30.10 pinned the body column and shipped a gate that was $0.002 short on
// every search. If the fee or the buffer moves, these fail — which is the point.
const CHARGED: Array<[number, number]> = [
[1, 0.0283],
[5, 0.1333],
[10, 0.2645],
[20, 0.527],
[50, 1.3145],
];
// The gate can only stop a looping agent if the reserve is >= the charge, and
// it must not over-reserve by much or it blocks calls a budget can afford —
// the old $0.025 x max_results reserve did exactly that, 16x at 50 sources.
const CHARGED_BASE_USD = 0.081;
const SIZES = [1, 5, 10, 20, 50];

test("estimateSearchCost reserves at least what x402 actually charges (header, not body)", () => {
for (const [max, quoted] of CHARGED) {
test("estimateSearchCost reserves at least what x402 actually charges, at every size", () => {
for (const max of SIZES) {
const reserved = estimateSearchCost({ query: "x", max_results: max });
assert.ok(
reserved >= quoted - 1e-6,
`max_results=${max}: reserved $${reserved} < charged $${quoted} — the gate would under-reserve`,
reserved >= CHARGED_BASE_USD - 1e-6,
`max_results=${max}: reserved $${reserved} < charged $${CHARGED_BASE_USD} — the gate would under-reserve`,
);
}
});

test("estimateSearchCost does not over-reserve by more than a cent", () => {
// Reserving wildly high would block calls a budget could actually afford.
for (const [max, quoted] of CHARGED) {
for (const max of SIZES) {
const reserved = estimateSearchCost({ query: "x", max_results: max });
assert.ok(reserved - quoted < 0.01, `max_results=${max}: reserved $${reserved} vs gateway $${quoted}`);
assert.ok(reserved - CHARGED_BASE_USD < 0.01, `max_results=${max}: reserved $${reserved} vs gateway $${CHARGED_BASE_USD}`);
}
});

test("estimateSearchCost defaults to the 10-source price when max_results is absent", () => {
// Upstream defaults to 10. Reserving less than that on a bare { query } — the
// most common call shape — would let every default search skip the gate.
assert.ok(estimateSearchCost({ query: "x" }) >= 0.2645 - 1e-6);
assert.ok(estimateSearchCost(undefined) >= 0.2645 - 1e-6);
assert.ok(estimateSearchCost("not an object") >= 0.2645 - 1e-6);
});

test("estimateSearchCost caps at 50 sources, matching the upstream ceiling", () => {
assert.equal(estimateSearchCost({ max_results: 999 }), estimateSearchCost({ max_results: 50 }));
test("estimateSearchCost is flat: max_results never changes the reserve", () => {
const flat = estimateSearchCost({ query: "x" });
for (const body of [undefined, "not an object", { max_results: 1 }, { max_results: 50 }, { max_results: 999 }]) {
assert.equal(estimateSearchCost(body), flat);
}
});

test("estimateSearchCost ignores garbage max_results instead of reserving $0", () => {
// A $0 reserve is a gate bypass: it would authorize a ~$0.26 call against an
// exhausted budget.
test("estimateSearchCost never reserves $0 on garbage max_results", () => {
// A $0 reserve is a gate bypass.
for (const bad of [0, -5, "10", null, NaN, {}]) {
assert.ok(
estimateSearchCost({ max_results: bad }) >= 0.2645 - 1e-6,
`max_results=${JSON.stringify(bad)} fell back below the default reserve`,
estimateSearchCost({ max_results: bad }) >= CHARGED_BASE_USD - 1e-6,
`max_results=${JSON.stringify(bad)} fell below the charge`,
);
}
});
Expand Down
Loading