Skip to content

Architecture — how it works

  • Two entrypoints, one server. server.ts exposes the tools over Streamable HTTP — a single Express route, POST /mcp, in stateless mode (no session id), with a fresh McpServer built per request via buildServer() (GET/DELETE return 405). The HTTP boundary rejects JSON-RPC batch arrays; public endpoint safety comes from a read-only, no-ambient-authority deployment model rather than client Origin allow-lists. It also exposes cheap operational probes outside MCP: GET /healthz and GET /readyz. server-stdio.ts exposes the same tools over stdio for local desktop use. Both call buildServer(). HTTP deployments can set HOST and PORT through the configured BLURT_ENV_FILE; process-manager examples should expose only that env-file pointer, bind to 127.0.0.1 behind a reverse proxy, and avoid becoming a second runtime configuration surface.
  • Shared Blurt blockchain client. One @beblurt/dblurt Client is created on first use and reused so RPC connections stay warm (only the first call pays the cold-connection cost). It is configured with rpcTransport: "core" — the default "legacy" transport aborts each attempt after (tries+1)*500ms and then crashes (error.code.includes is not a function) on slow requests under the HTTP server context. The choice is guarded by test/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-status and get-vote-value read 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 become null rather than using a hardcoded infrastructure URL. Price is best-effort: if the feed is down, on-chain data is still returned and USD fields are null.
  • search / fetch. search maps a free-form query to blurt://… resource links without any network call; fetch resolves 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 semantic BLURT_WRITE_PROFILE capability profiles; the historical BLURT_WRITE_TOOLS_BANNED denylist still works as a subtractive compatibility control. BLURT_DRY_RUN_DEFAULT=true is a neutral preview-first execution-mode default: omitted dry_run parameters preview without broadcasting, but explicit dry_run: false still 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: true with 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)