Skip to main content
Glama
hestiax-official

HestiaX MCP

Official

HestiaX MCP: your own AI agent reads your XRPL lending

Check

WARNING

Disclaimer. This code is provided as is. It has not been audited, and it is for use on the XRPL Devnet only.

Nothing in this repository is an offer, solicitation or recommendation to invest or lend, or investment, legal, tax or financial advice. See Disclaimer.

HestiaX finances renewable-energy projects on the XRP Ledger. Lenders deposit into vault series (Single Asset Vault, XLS-65), the vaults lend to solar and wind projects through the Lending Protocol (XLS-66), and KYC is a Credential (XLS-70) checked by a Permissioned Domain (XLS-80). An Operator runs the vaults.

This repository is the MCP server behind https://demo.hestiax.org/api/mcp. Each user connects their own AI agent, such as Claude Code, with a personal read-only token. The agent then reads what that user sees in HestiaX, as that user: their positions, their loans, the vaults' state on the ledger. It can buy, sign or change nothing.

> Using the hestiax tools: who am I, what do I hold, and how are my loans?

● hestiax · getMe          Astrid Lund, Nordic Pension Fund, LENDER, KYC approved, scopes ["read"]
● hestiax · listPositions  19,500,000 Vault Shares in Construction Vault · USD · Series 39 · Solar and wind, Sweden,
                           worth 19,500,004.57 USD against 19,500,000 deposited; redemption 2027-04-23
● hestiax · listLoans      Skurup Solpark, 2,750,000 USD at 9.25%: all 20 installments paid, none late

Condensed from a Claude Code session against the live demo on 25 Oct 2026 (v5.9.0), through Cloudflare Access; the figures are its tools' answers. On the demo's clock the 20 installments fall a minute apart.

What to look at

Where

Read-only by construction. No tool writes. The scope model has "write" ready, but in the app every ledger write refuses a token, whatever tool asked: money moves only when the person confirms in HestiaX.

src/core/tool.ts, src/hestiax/catalog.ts

Least privilege per caller. A token sees only its role's tools. A tool outside its role or scope answers "Unknown tool", the same as one that does not exist, so nothing leaks what other roles can do.

sees() in src/core/policy.ts

One policy, two engines. A hand-rolled JSON-RPC engine and the official MCP SDK's sit behind the same checks. The same suite runs on both, and the official MCP client connects to both.

src/core/engines/, server.test.ts, interop.test.ts

Plug and play. The engine knows no app. The catalog, the public API, knows only the engine's contract. A test reads every import and fails if either reaches further.

src/boundary.test.ts

Hardened HTTP edge. A hosts list against DNS rebinding, 64 KB body cap, 60 calls a minute per token, refused tokens throttled per address and logged, an audit of every call made with a token by the SHA-256 of its arguments (never the arguments) and its address, and errors said in general terms.

src/core/policy.ts

Answers stripped of what hides. Text one user writes reaches another user's agent. Every string in an answer is normalized and stripped of the characters a screen does not show but a model reads: controls, zero-width, bidi overrides, Unicode Tags.

plain() in src/core/policy.ts

Related MCP server: Raily MCP

How it fits

flowchart LR
  A["User's AI agent<br/>(Claude Code)"] -- "Bearer hx_pat_…<br/>+ Access service token" --> CF["Cloudflare Access"]
  CF --> R["/api/mcp<br/>(HestiaX app)"]
  subgraph repo["this repository"]
    P["policy<br/>guard · visibility · rate limit · audit"] --> E["engine<br/>own | official SDK"]
    E --> T["tools = bind(catalog, backend)"]
  end
  R --> P
  T --> B["backend<br/>(the app's data access layer)"]
  B --> DB[("HestiaX records")]
  B --> X[("XRP Ledger<br/>vaults · loans · credentials")]

The app supplies three things: authenticate (a token to its user and role), the backend (each tool on the app's own reads, under the same role and owner checks as its pages) and audit. The rest is here.

The tools

All read-only, each listed only for its roles. Each answers plain JSON (money as { value, currency }) as structuredContent, with the same JSON as text. Lender tools carry a not-investment-advice notice.

Tool

Roles

Reads

getMe

every role

name, organization, role, KYC status, the token's scopes

getWallet

every role

the user's XRPL address, its explorer link and USD held (demo only)

listOffers

Lender

vault series taking deposits: coupon, minimum ticket, room left, subscription close, redemption

listPositions

Lender

Vault Shares held per series, valued at the withdrawal price, against what was paid in

listLoans, getLoan

Lender, Borrower, Operator

the loans the caller may see: principal, owed, installments left, next due, late / impaired / default, the ledger transactions

listProjects, getProject

Borrower, Operator

a borrower's own projects, or every borrower's for the Operator: figures, stage, terms, loan and due-diligence verdict

getFinancingOffer

Borrower

the offer on a project by the Credit agent's rules, or why none stands now

listVaults, getDecisionQueue

Operator

every series' phase, fund size, holdings, loss and Loan Broker book; the loans and projects waiting on a decision

Inputs are strict: an unknown key is refused, and ids are pattern-checked (a loan id is 64 hex characters), so nothing rides in one.

Try it

Requires Node.js 26.10 (.tool-versions) and pnpm 11. Node runs the TypeScript as it is, so there is no build.

pnpm install --frozen-lockfile
pnpm run check   # tsc, then 50 tests: the engine on both engines, the official MCP client, the import boundary
pnpm run try     # examples/try.ts: one tool, one token, five requests
MCP_ENGINE=sdk pnpm run try   # the same five on the official SDK's engine: the same answers
pnpm run demo    # examples/demo.ts: HestiaX's 11 tools on sample data, at http://127.0.0.1:3333/api/mcp

CI runs all four on every push, on each engine.

pnpm run try prints what an MCP client would get:

200 {"jsonrpc":"2.0","id":1,"result":{"tools":[]}}                                  no token: no tools
200 {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"getHello",…}]}}             the token's role's tools
200 {"jsonrpc":"2.0","id":1,"result":{…,"structuredContent":{"hello":"astrid"}}}    called as the token's user
200 {"jsonrpc":"2.0","id":1,"result":{…"Invalid arguments …"…,"isError":true}}    an unknown key: a result to correct by
401 The token is not valid: it may have expired or been revoked                    a token that does not hold

The demo on sample data serves the real catalog through the real engine and policy; only the backend is sample figures, in the shapes the app answers with. Three tokens: demo-lender, demo-borrower, demo-operator, each seeing its own role's tools. With the demo running, connect Claude Code and ask it, for example, "what do I hold, and how are my loans?":

claude mcp add --transport http hestiax-demo http://127.0.0.1:3333/api/mcp --header "Authorization: Bearer demo-lender"

Each call shows in the demo's log: its time, the token, the tool and how it ended.

The live demo is behind Cloudflare Access. To connect an agent, ask the HestiaX team for a demo token and the Access service token, then:

claude mcp add --scope user --transport http hestiax https://demo.hestiax.org/api/mcp \
  --header "Authorization: Bearer <hx_pat_ token>" \
  --header "CF-Access-Client-Id: <client id>" --header "CF-Access-Client-Secret: <client secret>"

Use it in your own app

createMcpServer returns (req: Request) => Promise<Response>: Streamable HTTP, one JSON answer per request, no session. Mount it as any Fetch-style handler, a Next.js route's POST for one. examples/try.ts is a complete example.

Option

What

engine

"own" (default) or "sdk"

info, instructions

What initialize answers; the instructions for a token's caller and for one without

tools

bind(catalog, backend): each spec with its implementation, which only sees input its schema parsed

authenticate

The Caller a bearer token names, or undefined (401). Absent: tokens are off, and any Authorization is a 401

as

Runs a call in its caller's context (the app's request-scoped principal)

ready

Why the backend cannot serve now, asked before each call

audit

Every call with a token: tool, the SHA-256 of its arguments, outcome, the client's address

explain

Words the caller may read for an error a tool threw; otherwise it is said in general terms

perMinute, maxBytes

Calls a minute per token, or per client address without one (60); body cap (64 KB, else 413)

hosts

The hosts it answers as: another Host, or a page of another origin, is a 403, so a rebound DNS name cannot pass. Absent: the request's own host

A tool throws Refusal for words the caller reads in full (not found, not allowed). Bad arguments are a result with isError (SEP-1303), so the agent can correct them; an unknown tool, or one the caller may not see, is a JSON-RPC -32602. A request must be Content-Type: application/json with an Accept naming application/json and text/event-stream, on both engines (else 415 or 406). A spec with access: "public" is listed without a token; "read" or "write" needs a token holding that scope ("write" holds "read"), and roles, when given, its caller's role.

The two engines

own (default)

sdk

Built on

hand-rolled JSON-RPC

@modelcontextprotocol/server 2.3.1, the official SDK

Protocol versions

2025-06-18

2026-07-28, plus 2024-10-07 to 2025-11-25

Batches

refused

served under 2025-03-26 only

Auth, visibility, results, refusals, limits and audit are the policy's, so both behave the same there. The SDK engine uses the SDK's low-level Server, not McpServer: McpServer validates a call before the handler runs, so unknown tools and bad arguments would skip the rate limit and the audit.

Layout

src/core/                the engine; knows zod, node, the official MCP SDK and itself
  tool.ts                the contract: Spec, Catalog, Backend, Caller, Scope, Refusal, bind()
  policy.ts              the checks both engines share
  engines/own.ts         hand-rolled JSON-RPC
  engines/sdk.ts         the official MCP SDK
  server.ts              createMcpServer(): one policy in front of one engine
src/hestiax/catalog.ts   HestiaX's public API: tool names, descriptions, scopes, roles, input schemas
examples/try.ts          a runnable server with one tool
examples/demo.ts         HestiaX's catalog on sample data, over HTTP

The HestiaX app, which holds the backend and the token issuing, is a separate, private repository. It takes this repository whole, as a git subtree at src/mcp/xrpl-lending-mcp, so this is the one source of the engine and the catalog. A change lands here first, by pull request, and the app pulls it:

git subtree pull --prefix=src/mcp/xrpl-lending-mcp git@github.com:hestiax-official/xrpl-lending-mcp.git main --squash

Next

  • Writes as propose-then-confirm. An agent drafts a deposit or a repayment; the person confirms it in HestiaX, where their own signature moves the money. A "yes" typed into the agent is never the confirmation.

  • OAuth 2.1 in place of personal tokens, so a client connects without a copied token or custom headers.

  • Public tools for partners, served without a token from a catalog of their own.

Disclaimer

This code is provided as is. It has not been audited, and it is for use on the XRPL Devnet only.

HestiaX Ltd is a software technology provider and is not authorised or regulated by the Financial Conduct Authority. HestiaX does not provide investment advice, commit capital, hold client money or assets, act as a broker, underwriter or custodian, operate an exchange, trading venue or marketplace, or itself undertake regulated arranging, placement or distribution activities; any regulated activities are required to be carried out by appropriately authorised third-party firms. This repository is for general information only and does not constitute an offer, solicitation, recommendation, invitation or inducement to invest or lend. HestiaX does not guarantee that any financing will be arranged or completed, and any potential transaction is subject to due diligence, approvals, definitive documentation, investor demand and market conditions. Nothing in this repository constitutes investment, legal, tax, accounting, regulatory or financial advice or a personal recommendation; you should conduct your own assessment and due diligence and obtain independent professional advice.

The names, institutions, projects and figures in this README and in the examples are illustrative, from a demo that runs on the XRP Ledger in test money.

License

Apache License 2.0. Copyright 2026 HestiaX Ltd; see NOTICE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables read-only access to the Raily personal agent's data, including agent status, brief, matches, intros, memory, analysis status, and billing status through browser-based OAuth.
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI assistants to access Saxo Bank portfolio data, account balances, positions, orders, transactions, and market data such as instruments, quotes, and charts. It is read-only by default, with trading tools available only when explicitly enabled.
    10
    MIT
  • A
    license
    Not graded
    quality
    A
    maintenance
    Enables AI assistants to read a verified household financial record across linked accounts, including holdings, transactions, fees, performance, cash flow, advisors, diagnostics, and score findings. It can also refresh connections and manage manually tracked assets or liabilities inside the service, but cannot move money or place trades at financial institutions.
    MIT