Architecture — how it works
- Two entrypoints, one server.
server.tsexposes the tools over Streamable HTTP — a single Express route,POST /mcp, in stateless mode (no session id), with a freshMcpServerbuilt per request viabuildServer()(GET/DELETEreturn405). The HTTP boundary rejects JSON-RPC batch arrays; public endpoint safety comes from a read-only, no-ambient-authority deployment model rather than clientOriginallow-lists. It also exposes cheap operational probes outside MCP:GET /healthzandGET /readyz.server-stdio.tsexposes the same tools over stdio for local desktop use. Both callbuildServer(). HTTP deployments can setHOSTandPORTthrough the configuredBLURT_ENV_FILE; process-manager examples should expose only that env-file pointer, bind to127.0.0.1behind a reverse proxy, and avoid becoming a second runtime configuration surface. - Shared Blurt blockchain client. One
@beblurt/dblurtClientis created on first use and reused so RPC connections stay warm (only the first call pays the cold-connection cost). It is configured withrpcTransport: "core"— the default "legacy" transport aborts each attempt after(tries+1)*500msand then crashes (error.code.includes is not a function) on slow requests under the HTTP server context. The choice is guarded bytest/resilience.test.ts. - RPC node selection. A background checker (
@beblurt/blurt-nodes-checker) continuously scores the configured nodes (latency, chain lag, version, reliability) and repoints the shared client at the healthy ones, best-first — so a slow, stale, forked or offline node is avoided rather than failing a request (src/utils/rpc.ts). dblurt's own failover then covers transient blips within that healthy set. - Two data layers. Account, wallet, history and raw votes come from the condenser API (Layer 1); profiles, ranked posts, account posts, discussions and communities come from the Nexus / Bridge API (Layer 2).
- Market price.
get-blurt-price,get-chain-statusandget-vote-valueread the external price feed (BLURT_PRICE_URL) through a shared helper. The feed URL, optional cache TTL and optional timeout are deployment configuration; if no feed URL is configured, price-dependent fields fail or becomenullrather than using a hardcoded infrastructure URL. Price is best-effort: if the feed is down, on-chain data is still returned and USD fields arenull. search/fetch.searchmaps a free-form query toblurt://…resource links without any network call;fetchresolves such a URI (or a shorthand id) to the raw JSON. This mirrors the search/fetch connector convention used by ChatGPT-style clients.- Write path (opt-in). When a posting key is present, write tools are registered through a guarded write context (
src/utils/signer.ts); the key is validated as the account's posting authority, used only for local signing, and never logged or sent to the network. New deployments should use semanticBLURT_WRITE_PROFILEcapability profiles; the historicalBLURT_WRITE_TOOLS_BANNEDdenylist still works as a subtractive compatibility control.BLURT_DRY_RUN_DEFAULT=trueis a neutral preview-first execution-mode default: omitteddry_runparameters preview without broadcasting, but explicitdry_run: falsestill executes. HTTP stays read-only by default and exposes signing only behind the explicit unsafe trusted-deployment override. The server does not enforce content/account/ community policy files; see ADR 0001, write operations, and security guide. - Errors. Tool failures return
isError: truewith a short message rather than throwing, so the model can react instead of seeing a transport error. - Documentation site UX. VitePress keeps Markdown as the source of truth. The site uses a minimal custom theme (
docs/.vitepress/theme/custom.css) for global readability: wider desktop content, stronger table headers and more legible wide tables. Large tables remain Markdown because they are mostly reference material and generated content; Vue components should be reserved for truly interactive or reused semantic views.
Project layout
src/
server.ts # HTTP entrypoint: builds the app and listens
server-stdio.ts # stdio entrypoint: same tools as a local child process
app.ts # Builds the Express app + /mcp route (no side effects, testable)
buildServer.ts # Registers all tools/resources on an McpServer; BLURT_CLIENT_OPTIONS
tools/ # One file per MCP tool
resources/ # blurt:// resource templates
utils/
logger.ts # Leveled logger (human / JSON), writes to stderr
loadEnv.ts # Loads BLURT_ENV_FILE / project .env regardless of cwd
rpc.ts # Shared client + RPC node selection (blurt-nodes-checker)
signer.ts # Write context: posting-key validation, optional local rate limits, signing
price.ts # Cached BLURT price feed helper
blurtUri.ts # blurt:// URI parsing / shorthand mapping (used by search/fetch)
test/
uri / search / resilience / write # offline (no external network)
live # live, in-memory MCP client
http # live, real Streamable HTTP transport (E2E)