Skip to main content

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.

tip

New to Hathor Forge? Start with the Hathor Forge documentation.

warning

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

CategoryExample tools
Nodestart_node, stop_node, get_node_status
Minerstart_miner, stop_miner, get_miner_status
Wallet Servicestart_wallet_service, stop_wallet_service, get_wallet_service_status
Walletsgenerate_seed, create_wallet, get_wallet_status, get_wallet_balance, get_wallet_addresses, send_from_wallet, close_wallet
Faucetget_faucet_balance, send_from_faucet, fund_wallet
Blockchainget_blocks, get_transaction
Quick actionsquick_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.
warning

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.

note

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
package.json
{
"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"
}
}
.env.example
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
src/config.ts
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'),
};
}
src/forgeHealth.ts
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');
}
src/amount.ts
// 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;
}
warning

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.

src/headlessWalletClient.ts
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)}`);
}
}
src/run-local-wallet-flow.ts
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