Hathor Forge MCP
Hathor Forge is a local blockchain development environment that runs a complete Hathor stack — fullnode, miner, wallet-headless service, block explorer, API explorer, and MCP server. The MCP server lets an AI assistant control the local environment directly: start services, create wallets, fund them from the faucet, send transactions, and inspect blocks.
New to Hathor Forge? Start with the Hathor Forge documentation.
Hathor Forge is local development only. Do not expose the MCP server publicly. Do not use Forge wallets, seeds, or funds on mainnet or testnet.
Prerequisites
- Nix installed with flakes enabled (the startup command uses
nix run) - Node.js 20+ if you plan to use the TypeScript starter
- Claude Code or another MCP-capable assistant
- Hathor Forge available locally
Connect in 3 steps
1. Start Hathor Forge
nix run github:HathorNetwork/hathor-forge -- --start
2. Register the MCP server
claude mcp add --transport http hathor-forge http://127.0.0.1:9876/mcp
Or add it to your project .mcp.json:
{
"mcpServers": {
"hathor-forge": {
"type": "http",
"url": "http://127.0.0.1:9876/mcp"
}
}
}
3. Verify the connection
curl http://127.0.0.1:9876/health
Then ask Claude Code:
Check the status of my local Hathor Forge environment.
The assistant should use Forge MCP tools to report on the node, miner, wallet service, and faucet status.
Claude Desktop setup
In the Hathor Forge desktop app: Settings → MCP Integration → Copy Config, then paste the generated config into Claude Desktop's MCP configuration file (~/Library/Application Support/Claude/claude_desktop_config.json on macOS).
Available tools
| Category | Example tools |
|---|---|
| Node | start_node, stop_node, get_node_status |
| Miner | start_miner, stop_miner, get_miner_status |
| Wallet Service | start_wallet_service, stop_wallet_service, get_wallet_service_status |
| Wallets | generate_seed, create_wallet, get_wallet_status, get_wallet_balance, get_wallet_addresses, send_from_wallet, close_wallet |
| Faucet | get_faucet_balance, send_from_faucet, fund_wallet |
| Blockchain | get_blocks, get_transaction |
| Quick actions | quick_start, quick_stop, get_full_status, reset_data |
What you can do with Forge MCP
Once Forge is running and connected, you can ask Claude to perform local development tasks directly — no code required. Claude uses the MCP tools to interact with the local environment for you.
Here are examples of what you can ask:
Start and inspect the environment
Start my local Hathor environment and tell me which services are running.
Check the full status of my local Hathor Forge environment.
Is the node running? Is the miner running? Does the faucet have funds?
Create wallets and send tokens
Create two local wallets named alice and bob.
Fund alice with 100 local HTR from the faucet.
Then send 20 HTR from alice to bob and show the final balances of both wallets.
Create a custom token
Using Hathor Forge MCP:
1. Make sure the local environment is running.
2. Create a wallet named token-creator and fund it from the faucet.
3. Create a custom token called MY_TOKEN with a supply of 1000 units.
4. Show the token UID and the wallet balance after creation.
Test a contract interaction
Using Hathor Forge MCP:
1. Create a wallet named contract-tester and fund it from the faucet.
2. Show me which nano contract blueprints are available locally.
3. Help me create a nano contract from the available blueprint and call one of its public methods.
Inspect blocks and transactions
Show me the last 5 blocks on my local Hathor chain.
Then find the transaction ID from my last wallet send and show its details.
Reset and start fresh
Reset my local Hathor Forge environment and start fresh.
After the reset, confirm that the node, miner, and wallet service are all running.
reset_data removes all local chain state, wallets, and transactions. Only run it when you are sure you no longer need the current local state.
TypeScript starter (optional)
If you want to test your application code against the local Hathor Forge environment, the starter project below shows how to connect to the local wallet-headless service from TypeScript.
This is only needed if you are building application code that talks directly to the Hathor Forge wallet-headless service. For most local development and exploration tasks, using Claude with the MCP tools directly (as shown above) is simpler and faster.
Step 1 — Prepare the environment with Forge MCP
Before running any application code, ask Claude to set up the local wallets:
Using Hathor Forge MCP:
1. Check the full local environment status and start any services not running.
2. Create a wallet named sender and a wallet named receiver.
3. Fund sender with 50 local HTR from the faucet.
4. Return the wallet IDs and confirm both are local-only.
Step 2 — Run the TypeScript starter
Project structure:
forge-local-wallet-flow/
├─ package.json
├─ .env.example
└─ src/
├─ config.ts
├─ forgeHealth.ts
├─ headlessWalletClient.ts
├─ amount.ts
└─ run-local-wallet-flow.ts
{
"name": "forge-local-wallet-flow",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": { "dev": "tsx src/run-local-wallet-flow.ts" },
"dependencies": { "dotenv": "^16.4.7" },
"devDependencies": {
"@types/node": "^22.10.2",
"tsx": "^4.19.2",
"typescript": "^5.7.2"
}
}
FORGE_HEALTH_URL=http://127.0.0.1:9876/health
HEADLESS_WALLET_URL=http://127.0.0.1:8001
HATHOR_NETWORK=localnet
SENDER_WALLET_ID=sender
RECEIVER_WALLET_ID=receiver
import 'dotenv/config';
export type LocalForgeConfig = {
forgeHealthUrl: string;
headlessWalletUrl: string;
network: 'localnet';
senderWalletId: string;
receiverWalletId: string;
};
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing required environment variable: ${name}`);
return value;
}
export function getLocalForgeConfig(): LocalForgeConfig {
const network = requireEnv('HATHOR_NETWORK');
if (network !== 'localnet') {
throw new Error(`This script is local-only. Expected HATHOR_NETWORK=localnet, got: ${network}`);
}
return {
forgeHealthUrl: requireEnv('FORGE_HEALTH_URL'),
headlessWalletUrl: requireEnv('HEADLESS_WALLET_URL').replace(/\/$/, ''),
network,
senderWalletId: requireEnv('SENDER_WALLET_ID'),
receiverWalletId: requireEnv('RECEIVER_WALLET_ID'),
};
}
export async function assertForgeIsRunning(healthUrl: string): Promise<void> {
const response = await fetch(healthUrl);
if (!response.ok) throw new Error(`Forge health check failed: ${response.status}`);
console.log('Forge health check: OK');
}
// Hathor amounts are integers where the last two digits are the decimal part.
// 10 HTR => 1000. Confirm in the official docs before production use.
const HTR_DECIMALS = 2;
const HTR_FACTOR = 10 ** HTR_DECIMALS;
export function htrToIntegerAmount(amount: number | string): number {
const normalized = String(amount).trim();
if (!/^\d+(\.\d+)?$/.test(normalized)) throw new Error('Amount must be a positive decimal value');
const [wholePart, decimalPart = ''] = normalized.split('.');
if (decimalPart.length > HTR_DECIMALS) throw new Error(`Max ${HTR_DECIMALS} decimal places`);
const result = Number(wholePart) * HTR_FACTOR + Number(decimalPart.padEnd(HTR_DECIMALS, '0'));
if (!Number.isSafeInteger(result) || result <= 0) throw new Error('Amount out of range');
return result;
}
The endpoint paths, request bodies, and response shapes in this client (/wallet/status, /wallet/address, /wallet/balance, /wallet/simple-send-tx) are illustrative. Verify all of them against the Headless Wallet API reference before using this code outside a local Forge experiment.
export class LocalHeadlessWalletClient {
private readonly baseUrl: string;
constructor(options: { baseUrl: string }) {
this.baseUrl = options.baseUrl.replace(/\/$/, '');
}
private headers(walletId: string): HeadersInit {
return {
'content-type': 'application/json',
// Confirm the exact wallet identifier header in the official API reference.
'x-wallet-id': walletId,
};
}
async getWalletStatus(walletId: string): Promise<unknown> {
// TODO: Confirm endpoint in the API reference.
const r = await fetch(`${this.baseUrl}/wallet/status`, { headers: this.headers(walletId) });
if (!r.ok) throw await this.toError(r, 'Failed to get wallet status');
return r.json();
}
async createOrOpenWallet(walletId: string): Promise<void> {
// TODO: Confirm the wallet creation/opening flow for your local setup.
console.log(`Prepare local wallet: ${walletId}`);
try {
await this.getWalletStatus(walletId);
console.log(`Wallet ${walletId} is reachable`);
} catch {
console.log(`Wallet ${walletId} not reachable — prepare it through Forge MCP first.`);
}
}
async getCurrentAddress(walletId: string): Promise<{ address: string }> {
// TODO: Confirm endpoint and response shape in the API reference.
const r = await fetch(`${this.baseUrl}/wallet/address`, { headers: this.headers(walletId) });
if (!r.ok) throw await this.toError(r, 'Failed to get address');
const data = await r.json();
return { address: data.address ?? data.base58 ?? data.result?.address };
}
async getBalance(walletId: string): Promise<{ available: unknown }> {
// TODO: Confirm endpoint and response shape in the API reference.
const r = await fetch(`${this.baseUrl}/wallet/balance`, { headers: this.headers(walletId) });
if (!r.ok) throw await this.toError(r, 'Failed to get balance');
const data = await r.json();
return { available: data.available ?? data.balance?.available ?? data };
}
async sendHtr(params: { fromWalletId: string; destinationAddress: string; amount: number }): Promise<{ txId: string }> {
// Confirm /wallet/simple-send-tx fields and response in the API reference.
const r = await fetch(`${this.baseUrl}/wallet/simple-send-tx`, {
method: 'POST',
headers: this.headers(params.fromWalletId),
body: JSON.stringify({ address: params.destinationAddress, value: params.amount }),
});
if (!r.ok) throw await this.toError(r, 'Failed to send HTR');
const data = await r.json();
if (typeof data.hash !== 'string') throw new Error('Missing transaction hash in response');
return { txId: data.hash };
}
private async toError(r: Response, msg: string): Promise<Error> {
let details: unknown;
try { details = await r.json(); } catch { details = await r.text(); }
return new Error(`${msg}. Status: ${r.status}. Details: ${JSON.stringify(details)}`);
}
}
import { htrToIntegerAmount } from './amount';
import { getLocalForgeConfig } from './config';
import { assertForgeIsRunning } from './forgeHealth';
import { LocalHeadlessWalletClient } from './headlessWalletClient';
async function main() {
const config = getLocalForgeConfig();
console.log('Network:', config.network);
await assertForgeIsRunning(config.forgeHealthUrl);
const client = new LocalHeadlessWalletClient({ baseUrl: config.headlessWalletUrl });
await client.createOrOpenWallet(config.senderWalletId);
await client.createOrOpenWallet(config.receiverWalletId);
const { address: receiverAddress } = await client.getCurrentAddress(config.receiverWalletId);
console.log('Receiver address:', receiverAddress);
const senderBefore = await client.getBalance(config.senderWalletId);
const receiverBefore = await client.getBalance(config.receiverWalletId);
console.log('Sender before:', senderBefore.available, '| Receiver before:', receiverBefore.available);
const tx = await client.sendHtr({
fromWalletId: config.senderWalletId,
destinationAddress: receiverAddress,
amount: htrToIntegerAmount(5),
});
console.log('Transaction:', tx.txId);
const senderAfter = await client.getBalance(config.senderWalletId);
const receiverAfter = await client.getBalance(config.receiverWalletId);
console.log('Sender after:', senderAfter.available, '| Receiver after:', receiverAfter.available);
}
main().catch((e) => {
console.error(e instanceof Error ? e.message : e);
process.exit(1);
});
Install and run:
npm install
cp .env.example .env
# Update .env with the wallet IDs returned by Forge MCP in Step 1
npm run dev
Expected output (after Forge MCP has prepared the wallets):
Network: localnet
Forge health check: OK
Receiver address: <local-address>
Sender before: <balance> | Receiver before: <balance>
Transaction: <local-transaction-id>
Sender after: <updated-balance> | Receiver after: <updated-balance>
Step 3 — Debug if MCP tools fail
curl http://127.0.0.1:9876/health
If the health check fails, restart Forge and re-run claude mcp add.
I am using Hathor Forge MCP and the assistant cannot reach the tools.
Check: whether Forge is running, whether the MCP endpoint is reachable at 127.0.0.1:9876/mcp,
whether Claude Code is configured with the server, and whether local ports are already in use.
Prompt patterns
Check environment status
Using Hathor Forge MCP, check full local environment status:
node running, miner running, wallet service running, faucet balance, recommended next step.
Start the full local stack
Using Hathor Forge MCP, start the local Hathor stack.
Confirm node, miner, and wallet service are ready for local wallet tests.
Create and fund a wallet
Using Hathor Forge MCP, create a wallet named demo and fund it with 100 local HTR.
Show the balance and address. Confirm this wallet is for local development only.
Send a local transaction
Using Hathor Forge MCP, send 5 local HTR from sender to receiver.
Confirm both wallets exist and sender has enough balance before sending.
Return the transaction ID and final balances.
Test wallet-headless locally
I want to test a wallet-headless integration against Hathor Forge.
Confirm the local wallet-headless service is running, identify its URL, create or fund a wallet if needed,
and explain which assumptions must change before moving to testnet.
Test a blueprint locally
I want to test a nano contract blueprint using Hathor Forge.
Plan: prepare the local environment, create/fund the required wallets, publish the blueprint locally,
execute the contract flow, inspect resulting transactions, and flag what to verify before leaving local development.
LLM guardrails
When using Hathor Forge MCP, the LLM should:
- Never confuse Forge with testnet or mainnet — all actions are local-only
- Never expose the MCP server publicly
- Never treat Forge wallets, seeds, or funds as real network assets
- Never reset local data without warning that existing state will be lost
- Never ask for seeds, private keys, or sensitive credentials
- Always verify the MCP connection is live before using tools
- Always flag which steps must change before moving to testnet or production
Next steps
- Headless Wallet Skill — build application code that talks to Headless Wallet
- Blueprint Skill — create, review, and debug nano contract blueprints
- Set up a localnet — understand the underlying localnet architecture
- Hathor Forge documentation — full Forge feature reference