Hedera MCP Server
Exposes the Hedera Agent Kit toolset so AI agents can work with the Hedera network on testnet or mainnet: query accounts, HBAR and token balances, tokens, topics, contracts, topic messages, pending airdrops, transaction records and exchange rates via the mirror node, and prepare transactions such as account creation/update, HBAR transfers and allowances, HTS fungible/NFT create, mint, airdrop, transfer, associate/dissociate and allowances, consensus topic create/submit/update/delete, ERC-20/ERC-721 operations, and scheduled transaction signing/deletion. Non-custodial: write tools return unsigned transaction bytes by default, or the server becomes an OAuth 2.1 authorization server that has the user's wallet sign and broadcast.
Used in the optional wallet-signing mode to send each prepared Hedera transaction to the user's own wallet (HIP-820) over WalletConnect for approval, so the wallet signs and broadcasts while the server never holds a private key; includes WalletConnect dApp metadata, session-key encryption at rest, and tools for wallet status and signing status.
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
Hedera MCP Server
A Model Context Protocol (MCP) server that lets AI agents work with the Hedera network. It speaks MCP over Streamable HTTP and exposes the Hedera Agent Kit toolset, so an LLM client (Claude, Cursor, VS Code, Codex, …) can query accounts, tokens and topics, and prepare transactions on testnet or mainnet.
The server is non-custodial: it never holds a Hedera private key. It runs in one of two modes:
Dev mode (default, no setup). Write tools return unsigned transaction bytes for the caller to sign and submit. The account is taken from an
x-hedera-account-idrequest header. No database, no OAuth.Wallet signing (optional). The server becomes an OAuth 2.1 authorization server whose login is the user's wallet. Each write tool sends the transaction to that wallet over WalletConnect (HIP-820) for the user to approve; the wallet signs and broadcasts. Needs Postgres and a WalletConnect project ID. See Wallet Signing.
Just want a plain local Hedera MCP server, without wallet sign-in? The Hedera Agent Kit
ships a runnable example with the same toolset:
examples/modelcontextprotocol.
Its HTTP server needs no keys; its stdio server takes your account's private key
(HEDERA_OPERATOR_KEY) and signs transactions itself, so use a testnet account. To build your
own server, use the library
@hashgraph/hedera-agent-kit-mcp.
What it does
Out of the box the server registers 44 Hedera Agent Kit tools:
Area | Examples |
Accounts & HBAR |
|
Hedera Token Service | create / mint / airdrop / transfer fungible and non-fungible tokens, associate / dissociate, token and NFT allowances |
Consensus Service |
|
EVM |
|
Scheduled transactions |
|
Queries (mirror node) | HBAR and token balances, account, token, topic and contract info, topic messages, pending airdrops, transaction records, exchange rate |
Use --tools to expose a subset. In wallet mode, three tools are added
(wallet_status, check_signing_status, sign_with_wallet).
Related MCP server: BananaCrystal MCP Server
Run it locally
Prerequisites
Node.js 24.22.1 or later 24.x (see
.nvmrc; earlier 24.x builds are vulnerable to CVE-2026-111497)A Hedera account ID to build transactions for. A free testnet account is available from the Hedera Portal.
Quick start (dev mode)
git clone https://github.com/blockydevs/hedera-mcp-server.git
cd hedera-mcp-server
nvm use # optional, picks up .nvmrc
npm install
npm run build
npm start # = node dist/main.js --agent-mode=returnBytes (testnet)The server listens on http://localhost:3001/mcp. Check it with
curl http://localhost:3001/healthz → {"status":"ok"}.
Connect an MCP client
Point any MCP client that supports Streamable HTTP at the server and pass your account ID in the
x-hedera-account-id header. For example, with Claude Code:
claude mcp add --transport http hedera http://localhost:3001/mcp \
--header "x-hedera-account-id: 0.0.YOUR_ACCOUNT_ID"or in a client's JSON config:
{
"mcpServers": {
"hedera": {
"url": "http://localhost:3001/mcp",
"headers": {
"x-hedera-account-id": "0.0.YOUR_ACCOUNT_ID"
}
}
}
}To poke at the tools without an LLM, run the
MCP Inspector
(npx @modelcontextprotocol/inspector), choose Streamable HTTP, enter the URL above and add
the header.
Run with Docker
docker build -t hedera-mcp .
docker run --rm -p 3001:3001 \
-e HEDERA_HTTP_HOST=0.0.0.0 -e ALLOW_INSECURE=true \
hedera-mcp # add --ledger-id=mainnet to override the testnet defaultInside a container the server must bind 0.0.0.0 to be reachable through the published port, and
an unauthenticated non-loopback bind is refused unless ALLOW_INSECURE=true. That is fine on your
own machine; never deploy it that way (see Security notes).
The image's entrypoint is node dist/main.js --agent-mode=returnBytes with a default
CMD ["--ledger-id=testnet"].
Quick start — wallet signing (non-custodial)
The full path to a running wallet-signing server, where every transaction is approved in the user's own wallet. See Wallet Signing below for how it works.
Configure. Copy the sample env and fill it in (
dist/main.jsauto-loads.env):cp .env.example .envSet at least these in
.env:WALLETCONNECT_PROJECT_ID="<your id from https://cloud.reown.com>" PUBLIC_URL="https://mcp.example.com" # public HTTPS origin of THIS server WC_ENCRYPTION_KEY="<output of: openssl rand -base64 32>" # encrypts WC session keys at rest DATABASE_URL="postgresql://hedera_mcp:hedera_mcp@localhost:5433/hedera_mcp" # matches docker compose.envvalues are not shell-expanded: runopenssl rand -base64 32and paste its output. Setting exactly one ofWALLETCONNECT_PROJECT_ID/PUBLIC_URLfails at startup — set both (wallet mode) or neither (dev mode).WC_ENCRYPTION_KEYis required in wallet mode (min 32 chars); without it the server refuses to start, unlessWC_ALLOW_PLAINTEXT=trueexplicitly accepts unencrypted session-key storage (non-production only).Start Postgres (local dev):
docker compose up -dInstall, build, and run migrations:
npm install npm run build npm run migration:runRun the server:
node dist/main.js --agent-mode=returnBytes --ledger-id=testnetTo expose it externally, set
HEDERA_HTTP_HOST=0.0.0.0and put it behind a TLS-terminating reverse proxy — a non-loopback bind without authentication is refused at startup.Connect a client. Point your MCP client at
PUBLIC_URL+/mcp. On the first call the server returns401, and the client runs the OAuth 2.1 + wallet sign-in flow automatically (browser-extension button or WalletConnect QR). Nox-hedera-account-idheader is used — the account comes from the wallet-verified token.
Health checks
Two unauthenticated probes for orchestrators (mounted ahead of rate-limiting and auth, so they are never gated):
GET /healthz— liveness; returns200 {"status":"ok"}whenever the process is up.GET /readyz— readiness; round-trips the database (200 {"status":"ready"}, or503 {"status":"unavailable"}if it can't). In dev mode (no database) it collapses to liveness.
Configuration
The server can be configured using environment variables:
HEDERA_HTTP_HOST(default:localhost)HEDERA_HTTP_PORT(default:3001)HEDERA_HTTP_ALLOWED_HOSTS(optional, comma-separated allowlist forHostheader)ORIGIN_AUTH_VERIFICATION_ENABLED(default:false, enables HMAC origin verification)ORIGIN_AUTH_SECRET_FILE_PATH(path to a file containing the shared HMAC secret)LOG_LEVEL(default:info;silentdisables logging) andLOG_PRETTY(default:false, human-readable instead of one-JSON-per-line)DAPP_NAME,DAPP_DESCRIPTION,DAPP_ICON_URL- WalletConnect dApp metadata shown to the walletTTL tuning knobs (ms):
SIGN_REQUEST_TTL_MS(~180s),SIGNIN_NONCE_TTL_MS,CEREMONY_TTL_MS,ACCESS_TOKEN_TTL_MS,REFRESH_TOKEN_TTL_MS
See .env.example for the complete, annotated list of environment variables
(wallet signing, encryption at rest, resource governance, database).
Optional Origin Verification (HMAC)
When ORIGIN_AUTH_VERIFICATION_ENABLED=true, the server verifies that each request was signed by a trusted edge (e.g. Cloudflare Worker) using HMAC-SHA256. Required headers:
X-Origin-Timestamp— millisecond timestamp (must be within 30 seconds of server time)X-Origin-Nonce— unique value per request (replay protection)X-Origin-Signature— hex-encoded HMAC-SHA256 of the canonical string:
{method}\n{path}\n{timestamp}\n{nonce}\n{sha256Hex(body)}The shared secret is loaded from the file at ORIGIN_AUTH_SECRET_FILE_PATH.
Missing or invalid signature returns
401 UnauthorizedWhen
ORIGIN_AUTH_VERIFICATION_ENABLED=false, requests are allowed without this check
The gate covers the whole request surface: /mcp, the OAuth endpoints (/authorize, /token,
/register, /.well-known/*), the /connect sign-in page, and any unmatched path. The only
exemptions are /healthz and /readyz, which orchestrators call against the container directly
rather than through the edge, so they cannot carry a signature.
In production, the edge proxy generates these headers. For local testing you must compute the HMAC yourself.
Example setup:
echo "my-super-secret" > /tmp/origin-auth-secret.txt
export ORIGIN_AUTH_VERIFICATION_ENABLED=true
export ORIGIN_AUTH_SECRET_FILE_PATH=/tmp/origin-auth-secret.txtYou can also override the port and ledger via CLI:
node dist/main.js --agent-mode=returnBytes --port=8080 --ledger-id=mainnetSupported Arguments
--agent-mode:returnBytesonly, and the default when the flag is omitted. This server is non-custodial and holds no operator key, so executing modes (the agent kit'sautonomous, and itscustomExecuteTx) are refused at startup rather than accepted and failing later.--ledger-id:testnet(default) ormainnet.--host: Interface to bind (default:localhost; also settable viaHEDERA_HTTP_HOST). A non-loopback host is refused at startup unless the server is authenticated orALLOW_INSECURE=true.--port: Port to listen on (default:3001).--tools: Comma-separated list of tools to enable (default: all).--account-id,--public-key: dev-mode account binding (ignored in wallet mode, where the account comes from the bearer token).
Agent Mode: RETURN_BYTES
In this mode, the MCP server returns the unsigned transaction bytes instead of executing them. This ensures the server remains stateless and does not require operator credentials.
Requesters must provide the account context by passing thex-hedera-account-id header in the HTTP request. This identifies which account the transactions are being built for.
Whenwallet signing is enabled, the account comes from the
verified access token instead and the x-hedera-account-id header is ignored — a client
must not be able to name an account it has not proven it controls.
Wallet Signing (non-custodial)
An optional mode in which the user approves and signs every transaction in their own wallet over WalletConnect v2 / HIP-820. The server holds no Hedera keys of any kind, never submits a transaction, and never sees a user signature — the wallet signs and broadcasts.
It is off unless both WALLETCONNECT_PROJECT_ID and PUBLIC_URL are set. Without them the
server behaves exactly as documented above.
In wallet mode, each write tool auto-delivers. The tool definitions are untouched, but a
wrapper runs the underlying Hedera tool to build the transaction and then delivers it to
the wallet for approval, returning a requestId — one call, no bytes handled by the model.
(In dev mode, with no wallet bound, the tools return unsigned bytes exactly as before.)
How it works
1. transfer_hbar_tool({ to, amount }) -> { requestId, summary, walletVerifiable, ... }
(built AND delivered to the wallet in one call;
also walletKind, approveUrl?, hint)
2. check_signing_status({ requestId }) -> awaiting_approval | confirming | executed
| rejected | failed | expired | needs_reauthThe write tool never blocks: it returns a requestId at once and the wallet prompt resolves
in the background (MCP clients cap tool calls near 60s, well under human approval time).
After the wallet broadcasts, the server confirms the transaction on the mirror node before
reporting executed — so a stale-window approval that never lands is reported honestly, not
as success.
Added tools
Tool | Args | Returns |
| none |
|
|
|
|
|
|
|
Status values: awaiting_approval → confirming (mirror-node check) → executed, or
rejected / failed (reached the network but failed on-chain) / expired / needs_reauth.
Sign-in
The server is an OAuth 2.1 authorization server whose login form is a wallet. MCP clients
start this flow on their own when /mcp returns 401:
Client runs OAuth 2.1 authorization-code + PKCE and opens
/authorize.That redirects to
/connect, which offers a browser-extension button (for detected HIP-820 wallet extensions) and a WalletConnect QR code for mobile wallets.The user pairs their wallet, then approves a sign-in message — a single-use nonce bound to this server's domain and an issued-at time. Signing it moves no funds.
The signature is verified against the account's key from the mirror node. Only then is an authorization code issued.
The token's subject is that proven Hedera account.
There are no pairing tools: an authenticated request always has a wallet behind it. If the
wallet session ends, tools report needs_reauth and the client re-runs the flow.
Security notes
The payer is never a parameter. It comes from the token, and
sign_with_walletre-checks the decoded transaction's payer against it before anything reaches a wallet. Those bytes travel through the model's context, so they are not trusted.walletVerifiable: falsemarks transaction types a wallet cannot fully decode (contract calls, file operations). The agent must relay that warning: for those types the wallet's display is not an independent check.Access tokens never outlive their wallet session, and a wallet-side disconnect purges the pairing and its grants immediately.
Run behind a TLS-terminating reverse proxy. The server speaks plaintext HTTP; bearer tokens and the
wc:pairing URI must never traverse an untrusted network in the clear. Binding a non-loopback host with no authentication is refused at startup (ALLOW_INSECURE=trueoverrides — never in production). SetTRUST_PROXY_HOPSto your proxy depth.Resource governance.
MAX_SESSIONS,RATE_LIMIT_PER_MINUTE, andMAX_CONCURRENT_APPROVALSbound session growth, request rate, and per-account wallet prompts.The Postgres store is secret-adjacent. Grants and auth codes are stored hashed. The
wc_kvtable holds WalletConnect session symmetric keys, envelope-encrypted at rest (AES-256-GCM) withWC_ENCRYPTION_KEY— required in wallet mode; the server refuses to start without it unlessWC_ALLOW_PLAINTEXT=trueopts into cleartext storage (non-production only). Even in the clear, a leaked database allows prompt initiation, never signing. Restrict database access; sourcing the key from a KMS is a planned follow-up.Single instance only. The database layer requires exactly one server process (see
docs/DATABASE.md); a second one against the same Postgres refuses to boot via an advisory lock. Do not autoscale horizontally, deploy stop-then-start (K8sstrategy: Recreate), and pointDATABASE_URLat a direct connection rather than a transaction-pooling proxy.
Not implemented yet
This mode has been exercised end-to-end ontestnet (sign-in, delivery, mirror-confirm).
WalletConnect sessions persist across a server restart (Postgres-backed store), and the
concurrent-approval cap and at-rest encryption are in place. Not built: spend policies, an
append-only audit log, and HA failover (the server runs as a single instance). See
docs/SPEC.md §11 for the known limitations.
Status of the hardening items:
Area | Status |
Prompt-spam quotas | Implemented — concurrent-approval cap ( |
Spend policies (per-tx / per-day caps, recipient allowlists) | Not implemented |
Append-only audit log | Not implemented |
HA / active-standby failover | Not implemented — single stateful instance |
Server-side re-issue on the ~180s validity window | Not implemented — an expired request reports |
Threshold / key-list accounts at sign-in | Not supported — single-key accounts only |
Mainnet | Untested — testnet only |
At-rest encryption of session keys | Implemented — AES-256-GCM envelope via |
Database migrations
The OAuth/session schema is owned by TypeORM migrations, not auto-sync (DB_SYNCHRONIZE
defaults off — a forgotten flag in production can't silently drop columns).
docker compose up -d # local Postgres
npm run migration:run # apply pending migrations (builds first)
npm run migration:generate --name=AddThing # after an entity change, generate the diff
npm run migration:revert # roll back the last migrationDeploy: run npm run migration:run as a discrete step (a k8s Job / initContainer), or set
DB_MIGRATIONS_RUN=true to apply pending migrations at startup on a single instance. Set
DB_SYNCHRONIZE=true only for throwaway local dev. Tests use in-memory sqlite and don't need
migrations.
Development
npm test # vitest (unit + integration; in-memory sqlite, no Postgres needed)
npm run typecheck # tsc over src and test
npm run lint # eslintThe tests cover the logic that does not need a live wallet: nonce lifecycle, payer verification,
the signing status machine, the session store, and the HTTP surface. The WalletConnect pairing
handshake and the hedera_signAndExecuteTransaction round trip need a real wallet and are tested
manually on testnet.
Further reading:
docs/SPEC.md— architecture, trust model, tool surface and known limitationsdocs/DATABASE.md— the Postgres schema and the single-instance requirementSECURITY.md— reporting vulnerabilities and deployment requirements
Contributing
Issues and pull requests are welcome. Before opening a PR, please make sure npm test,
npm run typecheck and npm run lint pass, and keep changes focused. Report security issues
privately as described in SECURITY.md, not in public issues.
License
Copyright 2026 Hedera Hashgraph, LLC. Licensed under the Apache License 2.0; see
NOTICE for attributions.
The sign-in page bundles the Montserrat typeface under the SIL Open Font License 1.1. The Hedera name and logo shown on the sign-in page are trademarks of Hedera Hashgraph, LLC; they are not covered by the Apache license.
Installing this server also installs WalletConnect and Reown packages, which are source-available rather than open source and are not covered by the Apache license. They are licensed under the WalletConnect Community License and the Reown Community License, and those terms apply to anyone who installs or runs the server. Portions © 2025 Reown, Inc. All Rights Reserved.
If you run it as a service, read those licences first. Among other things they require a commercial licence from Reown above their usage thresholds (§3), require connecting through the Reown relay network (§2b), and require the Reown copyright notice above in the service's notices or the about section of its website (§2a).
This server cannot be deployed
Maintenance
Related MCP Connectors
67 tools: agent blockpages, onboarding, messaging + hire loop on Hedera. Server never holds keys.
Provide AI agents and automation tools with contextual access to blockchain data including balance…
AI agent infrastructure for discovery, authorization, execution, identity, and signed receipts.
AI Reasoning Cache & Consensus Layer with 11 MCP tools via Streamable HTTP.
Related MCP Servers
- AlicenseAqualityBmaintenanceProduction MCP server giving AI agents metered access to live Hedera blockchain data. Query token prices, screen identities, monitor governance, write tamper-evident HCS compliance records, and analyze smart contracts — all paid in HBAR micropayments per call.2047 npmAcademic Free v1.1
- AlicenseAqualityBmaintenanceProvides AI agents with a wallet and autonomous payment capabilities using stablecoins, enabling seamless micropayments and financial operations with under 5-second settlement on Hedera.4038 npm4MIT
- AlicenseNot gradedqualityDmaintenanceProvides tools for AI assistants to interact with the Hedera blockchain, enabling account management, consensus messaging, and HBAR transfers. Supports testnet, mainnet, and previewnet.MIT
- AlicenseBqualityDmaintenanceComprehensive MCP server for Hedera (Hashgraph) covering 73 tools across all core services, building unsigned transactions for AI agents to propose. Never holds keys; reads via public Mirror Node.7369 npmMIT