diff --git a/CHANGELOG.md b/CHANGELOG.md index b321f87..7c649b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to BlockRun MCP will be documented in this file. +## 0.45.1 + +**The context number gets its own card instead of being the second of nineteen +badges.** 0.45.0 published the figure but buried it โ€” a shields badge in a row +with `MIT License`, `TypeScript 5` and `Node โ‰ฅ20.19`, which are table stakes, +next to the one number nothing else in the ecosystem publishes. It now sits +directly under the install command as a card: the figure large, a reference +unit that makes it mean something (6% of a 200K context window), the +`--profile trading` alternative at โˆ’57%, and the line that matters โ€” *every +turn, whether or not you call a tool*. + +The card is generated by `npm run brand:card`, never drawn by hand, so it +cannot outlive the number it states; `test/schema-tokens.test.ts` checks both +the light and dark variants against a live measurement and fails the build if +either drifts. Light and dark ship as separate SVGs behind ``, because +an SVG cannot restyle itself from the host page. + ## 0.45.0 **Every tool schema got smaller, and the README now tells you by how much.** diff --git a/README.md b/README.md index d9088ec..3020757 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,6 @@ Agents can only sign transactions.


20 tools  -12.9K context tokens  Agent native  No API keys  Read and trade Polymarket  @@ -41,6 +40,15 @@ claude mcp add blockrun -s user -- npx -y @blockrun/mcp@latest
Wallet auto-created on first run. Fund with $5 USDC. Ask Claude anything.
+
+ + + Context cost: 12.9K tokens, 6% of a 200K context window, charged every turn whether or not you call a tool. 5.6K with --profile trading, 57% less. + +
+ +
Every MCP server costs you this. Almost none of them tell you. How we measure it โ†’
+ --- > **BlockRun MCP** is an open-source [Model Context Protocol](https://modelcontextprotocol.io) server that gives Claude โ€” and any MCP-compatible agent โ€” 20 tools for real-time data and real actions: 71 LLMs, image & video generation, prediction-market data, live web/X search, on-chain queries across 40 chains, and **the ability to place real, USDC-settled bets on Polymarket**. Authentication is a wallet signature (no API keys); you pay per call in USDC via the [x402](https://x402.org) protocol (no credit cards, no subscriptions). One self-custody wallet on Base or Solana. MIT licensed. diff --git a/assets/context-cost-dark.svg b/assets/context-cost-dark.svg new file mode 100644 index 0000000..00f1b3f --- /dev/null +++ b/assets/context-cost-dark.svg @@ -0,0 +1,10 @@ + + + + + CONTEXT COST + 12.9K tokens + 6% of a 200K context window ยท every turn, whether or not you call a tool + 5.6K with --profile trading โ€” 57% less + measured, not estimated + diff --git a/assets/context-cost.svg b/assets/context-cost.svg new file mode 100644 index 0000000..baa95f3 --- /dev/null +++ b/assets/context-cost.svg @@ -0,0 +1,10 @@ + + + + + CONTEXT COST + 12.9K tokens + 6% of a 200K context window ยท every turn, whether or not you call a tool + 5.6K with --profile trading โ€” 57% less + measured, not estimated + diff --git a/package.json b/package.json index 61365d4..3d03399 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockrun/mcp", - "version": "0.45.0", + "version": "0.45.1", "mcpName": "io.github.BlockRunAI/blockrun-mcp", "description": "BlockRun MCP Server - Give your AI agent web search, deep research, prediction markets, and crypto data. Paid via x402 micropayments.", "type": "module", @@ -24,6 +24,7 @@ "prepublishOnly": "npm run build", "verify:prices": "tsx scripts/verify-prices.ts", "measure:schema": "node scripts/measure-tool-schema.mjs", + "brand:card": "node scripts/measure-tool-schema.mjs --svg", "e2e:polymarket:readonly": "tsx scripts/polymarket-e2e-readonly.ts", "e2e:polymarket:approvals": "tsx scripts/polymarket-e2e-verify-approvals.ts", "e2e:polymarket:approve": "tsx scripts/polymarket-e2e-approve.ts", diff --git a/scripts/measure-tool-schema.mjs b/scripts/measure-tool-schema.mjs index 9a4bdd3..1669f85 100755 --- a/scripts/measure-tool-schema.mjs +++ b/scripts/measure-tool-schema.mjs @@ -34,6 +34,7 @@ * percent higher on JSON, so every number here is a slight UNDER-count. */ import { spawn } from "node:child_process"; +import { writeFileSync } from "node:fs"; import { fileURLToPath, pathToFileURL } from "node:url"; import { encode } from "gpt-tokenizer/encoding/o200k_base"; @@ -129,6 +130,41 @@ export function measure(tools, prefix = "") { /** 12900 -> "12.9K". The form the README badge publishes. */ export const asK = (n) => `${(Math.round(n / 100) / 10).toFixed(1)}K`; +/* โ”€โ”€ the context-cost card โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + * + * Generated, never hand-drawn: the figures come from the measurement above, so + * the card cannot say something the server stopped doing. `--svg` rewrites + * both variants; test/schema-tokens.test.ts fails if they drift. + * + * Two files because GitHub picks between them with ; an SVG + * cannot restyle itself from the host page's colour scheme. Text is left at a + * system font stack rather than converted to paths so it stays selectable and + * legible at any zoom โ€” the tradeoff is that positions are hand-tuned with + * enough slack that a wider glyph set cannot collide. + */ +const REFERENCE_WINDOW = 200_000; + +export function renderCard({ total, tradingTotal, cut, dark }) { + const c = dark + ? { bg: "#0B0A0F", stroke: "#26242E", label: "#8A8797", value: "#FFFFFF", accent: "#5B9BF6", muted: "#6E6B7B" } + : { bg: "#FFFFFF", stroke: "#E4E2E8", label: "#6E6B7B", value: "#0B0A0F", accent: "#2563EB", muted: "#8A8797" }; + const pct = Math.round((total / REFERENCE_WINDOW) * 100); + const sans = "-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif"; + const mono = "ui-monospace,SFMono-Regular,Menlo,Consolas,monospace"; + + return ` + + + + CONTEXT COST + ${asK(total)} tokens + ${pct}% of a 200K context window ยท every turn, whether or not you call a tool + ${asK(tradingTotal)} with --profile trading โ€” ${cut}% less + measured, not estimated + +`; +} + // Importable: test/schema-tokens.test.ts reuses measure()/asK() to pin the // published number, so the CLI half must not run on import. const isCli = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href; @@ -143,6 +179,17 @@ for (const profile of PROFILES) { results[profile ?? userCmd.join(" ")] = measure(await listTools(cmd), PREFIX); } +if (flags.has("--svg")) { + const full = results.full, trading = results.trading; + if (!full || !trading) throw new Error("--svg needs the full and trading profiles (drop `-- `)"); + const cut = Math.round((1 - trading.total / full.total) * 100); + for (const dark of [false, true]) { + const file = new URL(`../assets/context-cost${dark ? "-dark" : ""}.svg`, import.meta.url); + writeFileSync(file, renderCard({ total: full.total, tradingTotal: trading.total, cut, dark })); + console.log(`wrote ${fileURLToPath(file).split("/").slice(-2).join("/")}`); + } +} + if (asJson) { console.log(JSON.stringify(results, null, 2)); } else { diff --git a/test/schema-tokens.test.ts b/test/schema-tokens.test.ts index 1ea48ce..4f036f0 100644 --- a/test/schema-tokens.test.ts +++ b/test/schema-tokens.test.ts @@ -44,14 +44,28 @@ async function measureProfile(profile: string) { }; } -test("the README badge states the measured context cost", async () => { +test("the context-cost card states the measured numbers", async () => { + // The card is generated by `npm run brand:card`, never hand-drawn, so the + // only way it can lie is if someone edits the SVG or forgets to regenerate. + // Both variants are checked: a dark-mode reader sees a different file. const full = await measureProfile("full"); - const shown = asK(full.total) as string; - assert.ok( - README.includes(`badge/๐Ÿงฎ_${shown}_Context_Tokens`), - `badge URL should show ${shown} (measured ${full.total} tokens)`, - ); - assert.ok(README.includes(`alt="${shown} context tokens"`), "badge alt text"); + const trading = await measureProfile("trading"); + const cut = Math.round((1 - trading.total / full.total) * 100); + const pct = Math.round((full.total / 200_000) * 100); + + for (const variant of ["context-cost.svg", "context-cost-dark.svg"]) { + const svg = readFileSync(new URL(`../assets/${variant}`, import.meta.url), "utf8"); + assert.ok(svg.includes(`${asK(full.total)} tokens`), `${variant}: headline figure`); + assert.ok(svg.includes(`${pct}% of a 200K context window`), `${variant}: reference unit`); + assert.ok(svg.includes(`${asK(trading.total)} with`), `${variant}: trading figure`); + assert.ok(svg.includes(`${cut}% less`), `${variant}: saving`); + } +}); + +test("the README shows both card variants", () => { + // Without the , dark-mode readers get the light card on a dark page. + assert.ok(README.includes('srcset="assets/context-cost-dark.svg"'), "dark source"); + assert.ok(README.includes('src="assets/context-cost.svg"'), "light fallback"); }); test("the profile table states each profile's measured cost", async () => {