diff --git a/CHANGELOG.md b/CHANGELOG.md index f4ead41..13cd790 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 5cc1d49..fc77e96 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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 40 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 | diff --git a/VERSION b/VERSION index 524456c..1942d77 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.54.0 +0.54.1 diff --git a/docs/mcp-schema-overhead.md b/docs/mcp-schema-overhead.md index 30f4836..c98185c 100644 --- a/docs/mcp-schema-overhead.md +++ b/docs/mcp-schema-overhead.md @@ -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 diff --git a/package-lock.json b/package-lock.json index e93e518..e996a06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@blockrun/mcp", - "version": "0.52.1", + "version": "0.54.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@blockrun/mcp", - "version": "0.52.1", + "version": "0.54.1", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "^0.123.0", diff --git a/package.json b/package.json index b9f0515..8dc795a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/skills/blockrun/SKILL.md b/skills/blockrun/SKILL.md index 3d71a31..7bf9abf 100644 --- a/skills/blockrun/SKILL.md +++ b/skills/blockrun/SKILL.md @@ -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 | diff --git a/skills/search/SKILL.md b/skills/search/SKILL.md index dad6e37..48e7cd7 100644 --- a/skills/search/SKILL.md +++ b/skills/search/SKILL.md @@ -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 @@ -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 | @@ -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. @@ -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 @@ -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 diff --git a/skills/surf/SKILL.md b/skills/surf/SKILL.md index 285de5e..c9a32f8 100644 --- a/skills/surf/SKILL.md +++ b/skills/surf/SKILL.md @@ -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. diff --git a/src/tools/search.ts b/src/tools/search.ts index 3e63638..88623f1 100644 --- a/src/tools/search.ts +++ b/src/tools/search.ts @@ -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 @@ -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, diff --git a/test/search-cost.test.ts b/test/search-cost.test.ts index 44f12e7..ae29a81 100644 --- a/test/search-cost.test.ts +++ b/test/search-cost.test.ts @@ -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`, ); } });