# Algorand MCP Server
[![npm version](https://img.shields.io/npm/v/@goplausible/algorand-mcp.svg)](https://www.npmjs.com/package/@goplausible/algorand-mcp)
[![npm downloads](https://img.shields.io/npm/dm/@goplausible/algorand-mcp.svg)](https://www.npmjs.com/package/@goplausible/algorand-mcp)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A comprehensive [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that gives AI agents and LLMs full access to the Algorand blockchain. Built by [GoPlausible](https://goplausible.com).

Algorand is a carbon-negative, pure proof-of-stake Layer 1 blockchain with instant finality, low fees, and built-in support for smart contracts (AVM), standard assets (ASAs), and atomic transactions.

## What is MCP?

[Model Context Protocol](https://modelcontextprotocol.io/) is an open standard that lets AI applications connect to external tools and data sources. This server exposes Algorand blockchain operations as MCP tools that any compatible AI client can use — Claude Desktop, Claude Code, Cursor, Windsurf, and others.

## Features

- Secure wallet management via OS keychain — private keys never exposed to agents or LLMs
- Wallet accounts nicknames, allowances, and daily limits for safe spending control
- Account creation, key management, and rekeying
- Transaction building, signing, and submission (payments, assets, applications, key registration)
- Atomic transaction groups
- TEAL compilation and disassembly
- Full Algod and Indexer API access
- NFDomains (NFD) name service integration
- x402 and AP2 toolins for Algorand
- Tinyman AMM integration (pools, swaps, liquidity)
- Haystack Router DEX aggregation (best-price swaps across Tinyman, Pact, Folks)
- Alpha Arcade prediction market trading (browse markets, orderbooks, limit/market orders, positions, claims)
- ARC-26 URI and QR code generation
- Algorand knowledge base with full developer documentation taxonomy
- Per-tool-call network selection (mainnet, testnet, localnet) and pagination

## Requirements

- Node.js v20 or later
- npm, pnpm, or yarn

## Installation

### From npm

```bash
npm install -g @goplausible/algorand-mcp
```

### From source

```bash
git clone https://github.com/GoPlausible/algorand-mcp.git
cd algorand-mcp
npm install
npm run build
```

## MCP Configuration

The server runs over **stdio**. There are three ways to invoke it — pick whichever suits your setup:

| Method | Command | When to use |
|---|---|---|
| **npx** (recommended) | `npx @goplausible/algorand-mcp` | No install needed, always latest version |
| **Global install** | `algorand-mcp` | After `npm install -g @goplausible/algorand-mcp` |
| **Absolute path** | `node /path/to/dist/index.js` | Built from source or local clone |

**No environment variables are required** for standard use. Network selection, pagination, and node URLs are all handled dynamically per tool call.

---

### OpenClaw

No manual configuration needed — install the [`@goplausible/openclaw-algorand-plugin`](https://www.npmjs.com/package/@goplausible/openclaw-algorand-plugin) npm package and the Algorand MCP server is configured automatically:

```bash
npm install -g @goplausible/openclaw-algorand-plugin
```

---

### Claude Desktop

Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

**Using npx:**
```json
{
  "mcpServers": {
    "algorand-mcp": {
      "command": "npx",
      "args": ["@goplausible/algorand-mcp"]
    }
  }
}
```

**Using global install:**
```json
{
  "mcpServers": {
    "algorand-mcp": {
      "command": "algorand-mcp"
    }
  }
}
```

**Using absolute path:**
```json
{
  "mcpServers": {
    "algorand-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/algorand-mcp/dist/index.js"]
    }
  }
}
```

---

### Claude Code

Create `.mcp.json` in your project root (project scope) or `~/.claude.json` (user scope):

```json
{
  "mcpServers": {
    "algorand-mcp": {
      "type": "stdio",
      "command": "npx",
      "args": ["@goplausible/algorand-mcp"]
    }
  }
}
```

Or add interactively:
```bash
claude mcp add algorand-mcp -- npx @goplausible/algorand-mcp
```

---

### Cursor

Add via **Settings > MCP Servers**, or edit `.cursor/mcp.json` in your project root:

```json
{
  "mcpServers": {
    "algorand-mcp": {
      "command": "npx",
      "args": ["@goplausible/algorand-mcp"]
    }
  }
}
```

---

### Windsurf

Add via **Settings > MCP**, or edit `~/.codeium/windsurf/mcp_config.json`:

```json
{
  "mcpServers": {
    "algorand-mcp": {
      "command": "npx",
      "args": ["@goplausible/algorand-mcp"]
    }
  }
}
```

---

### VS Code / GitHub Copilot

Edit `.vscode/mcp.json` in your workspace root, or open **Settings > MCP Servers**:

```json
{
  "servers": {
    "algorand-mcp": {
      "type": "stdio",
      "command": "npx",
      "args": ["@goplausible/algorand-mcp"]
    }
  }
}
```

---

### Cline

Add via the **MCP Servers** panel in the Cline sidebar, or edit `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` (macOS):

```json
{
  "mcpServers": {
    "algorand-mcp": {
      "command": "npx",
      "args": ["@goplausible/algorand-mcp"],
      "disabled": false
    }
  }
}
```

---

### OpenAI Codex CLI

Create `.codex/mcp.json` in your project root or `~/.codex/mcp.json` for global scope:

```json
{
  "mcpServers": {
    "algorand-mcp": {
      "command": "npx",
      "args": ["@goplausible/algorand-mcp"]
    }
  }
}
```

---

### Open Code

Edit `~/.config/opencode/config.json`:

```json
{
  "mcp": {
    "algorand-mcp": {
      "type": "stdio",
      "command": "npx",
      "args": ["@goplausible/algorand-mcp"]
    }
  }
}
```

---

### Any MCP-compatible client

The server speaks the standard MCP stdio protocol. For any client not listed above, configure it with:

- **Command:** `npx` (or `algorand-mcp` if globally installed, or `node /path/to/dist/index.js`)
- **Args:** `["@goplausible/algorand-mcp"]` (for npx)
- **Transport:** `stdio`

## Network Selection

Every tool accepts an optional `network` parameter: `"mainnet"` (default), `"testnet"`, or `"localnet"`. Algod and Indexer URLs are built-in for mainnet and testnet via [AlgoNode](https://algonode.io/).

Example tool call:
```json
{ "name": "api_algod_get_account_info", "arguments": { "address": "ABC...", "network": "testnet" } }
```

If no `network` is provided, tools default to **mainnet**.

## Pagination

API responses are automatically paginated. Every tool accepts an optional `itemsPerPage` parameter (default: 10). Pass the `pageToken` from a previous response to fetch the next page.

## Secure Wallet

### Architecture

The wallet system has two layers of storage, each with a distinct security role:

| Layer | What it stores | Where | Encryption |
|---|---|---|---|
| **OS Keychain** | Mnemonics (secret keys) | macOS Keychain / Linux libsecret / Windows Credential Manager | OS-managed, hardware-backed where available |
| **Embedded SQLite** | Account metadata (nicknames, allowances, spend tracking) | `~/.algorand-mcp/wallet.db` | Plaintext (no secrets) |

**Private key material never appears in tool responses, MCP config files, environment variables, or logs.** The agent only sees addresses, public keys, and signed transaction blobs.

### How it works

```
  Agent (LLM)                    MCP Server                     Storage
  ──────────                     ──────────                     ───────
       │                              │                              │
       │  wallet_add_account          │                              │
       │  { nickname: "main" }        │                              │
       │ ──────────────────────────►  │  generate keypair            │
       │                              │  store mnemonic ──────────►  │  OS Keychain (encrypted)
       │                              │  store metadata ──────────►  │  SQLite (nickname, limits)
       │  ◄─ { address, publicKey }   │                              │
       │                              │                              │
       │  wallet_sign_transaction     │                              │
       │  { transaction: {...} }      │                              │
       │ ──────────────────────────►  │  check spending limits       │
       │                              │  retrieve mnemonic ◄──────  │  OS Keychain
       │                              │  sign in memory              │
       │  ◄─ { txID, blob }          │  (key discarded)             │
       │                              │                              │
```

1. **Account creation** (`wallet_add_account`) — Generates a keypair, stores the mnemonic in the OS keychain, and stores metadata (nickname, spending limits) in SQLite. Returns **only** address and public key.
2. **Active account** — One account is active at a time. `wallet_switch_account` changes it by nickname or index. All signing and query tools operate on the active account.
3. **Transaction signing** (`wallet_sign_transaction`) — Checks per-transaction and daily spending limits, retrieves the key from the keychain, signs in memory, discards the key. Returns only the signed blob.
4. **Data signing** (`wallet_sign_data`) — Signs arbitrary hex data using raw Ed25519 via the [`@noble/curves`](https://github.com/paulmillr/noble-curves) library (no Algorand SDK prefix). Useful for off-chain authentication.
5. **Asset opt-in** (`wallet_optin_asset`) — Creates, signs, and submits an opt-in transaction for the active account in one step.

### Spending limits

Each account has two configurable limits (in microAlgos, 0 = unlimited):

- **`allowance`** — Maximum amount per single transaction. Rejects any transaction exceeding this.
- **`dailyAllowance`** — Maximum total spend per calendar day across all transactions. Automatically resets at midnight. Tracked in SQLite.

### Platform keychain support

The keychain backend is provided by [`@napi-rs/keyring`](https://github.com/nicolo-ribaudo/napi-keyring) (Rust-based, prebuilt binaries):

| Platform | Backend |
|---|---|
| macOS | Keychain Services (the system Keychain app) |
| Linux | libsecret (GNOME Keyring) or KWallet |
| Windows | Windows Credential Manager |

All credentials are stored under the service name `algorand-mcp`. You can inspect them with your OS keychain app (e.g. Keychain Access on macOS).

## Optional Environment Variables

Environment variables are only needed for special setups. Pass them via the `env` block in your MCP config.

| Variable | Description | Default | When needed |
|---|---|---|---|
| `ALGORAND_TOKEN` | API token for private/authenticated nodes | `""` | Connecting to a private Algod/Indexer node |
| `ALGORAND_LOCALNET_URL` | Localnet base URL | `""` | Using `network: "localnet"` (e.g. `http://localhost:4001`) |
| `ALPHA_API_KEY` | Alpha Arcade API key | `""` | Accessing reward market data |


### Example: localnet (AlgoKit)

```json
{
  "mcpServers": {
    "algorand-mcp": {
      "command": "node",
      "args": ["/path/to/algorand-mcp/dist/index.js"],
      "env": {
        "ALGORAND_LOCALNET_URL": "http://localhost:4001",
        "ALGORAND_TOKEN": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
      }
    }
  }
}
```

Then use `"network": "localnet"` in your tool calls.

## Available Tools

### Wallet Tools (10 tools)

See [Secure Wallet](#secure-wallet) for full architecture details.

| Tool | Description |
|---|---|
| `wallet_add_account` | Create a new Algorand account with nickname and spending limits (returns address + public key only) |
| `wallet_remove_account` | Remove an account from the wallet by nickname or index |
| `wallet_list_accounts` | List all accounts with nicknames, addresses, and limits |
| `wallet_switch_account` | Switch the active account by nickname or index |
| `wallet_get_info` | Get active account info: address, public key, balance, and spending limits |
| `wallet_get_assets` | Get all asset holdings for the active account |
| `wallet_sign_transaction` | Sign a single transaction with the active account (enforces spending limits) |
| `wallet_sign_transaction_group` | Sign a group of transactions with the active account (auto-assigns group ID) |
| `wallet_sign_data` | Sign arbitrary hex data with raw Ed25519 (noble, no SDK prefix) |
| `wallet_optin_asset` | Opt the active account into an asset (creates, signs, and submits) |

### Account Management (8 tools)

| Tool | Description |
|---|---|
| `create_account` | Create a new Algorand account (returns address + mnemonic in the clear) |
| `rekey_account` | Rekey an account to a new address |
| `mnemonic_to_mdk` | Convert mnemonic to master derivation key |
| `mdk_to_mnemonic` | Convert master derivation key to mnemonic |
| `secret_key_to_mnemonic` | Convert secret key to mnemonic |
| `mnemonic_to_secret_key` | Convert mnemonic to secret key |
| `seed_from_mnemonic` | Generate seed from mnemonic |
| `mnemonic_from_seed` | Generate mnemonic from seed |

### Utility Tools (13 tools)

| Tool | Description |
|---|---|
| `ping` | Server connectivity check and info |
| `validate_address` | Check if an Algorand address is valid |
| `encode_address` | Encode a public key to an Algorand address |
| `decode_address` | Decode an Algorand address to a public key |
| `get_application_address` | Get address for a given application ID |
| `bytes_to_bigint` | Convert bytes to BigInt |
| `bigint_to_bytes` | Convert BigInt to bytes |
| `encode_uint64` | Encode uint64 to bytes |
| `decode_uint64` | Decode bytes to uint64 |
| `verify_bytes` | Verify signature against bytes |
| `sign_bytes` | Sign bytes with a secret key |
| `encode_obj` | Encode object to msgpack |
| `decode_obj` | Decode msgpack to object |

### Transaction Tools (18 tools)

| Tool | Description |
|---|---|
| `make_payment_txn` | Create a payment transaction |
| `make_keyreg_txn` | Create a key registration transaction |
| `make_asset_create_txn` | Create an asset creation transaction |
| `make_asset_config_txn` | Create an asset configuration transaction |
| `make_asset_destroy_txn` | Create an asset destroy transaction |
| `make_asset_freeze_txn` | Create an asset freeze transaction |
| `make_asset_transfer_txn` | Create an asset transfer transaction |
| `make_app_create_txn` | Create an application creation transaction |
| `make_app_update_txn` | Create an application update transaction |
| `make_app_delete_txn` | Create an application delete transaction |
| `make_app_optin_txn` | Create an application opt-in transaction |
| `make_app_closeout_txn` | Create an application close-out transaction |
| `make_app_clear_txn` | Create an application clear state transaction |
| `make_app_call_txn` | Create an application call transaction |
| `assign_group_id` | Assign group ID for atomic transactions |
| `sign_transaction` | Sign a transaction with a secret key |
| `encode_unsigned_transaction` | Encode an unsigned transaction to base64 msgpack bytes |
| `decode_signed_transaction` | Decode a signed transaction blob back to JSON with signature details |

### Algod Tools (5 tools)

| Tool | Description |
|---|---|
| `compile_teal` | Compile TEAL source code |
| `disassemble_teal` | Disassemble TEAL bytecode to source |
| `send_raw_transaction` | Submit signed transactions to the network |
| `simulate_raw_transactions` | Simulate raw transactions |
| `simulate_transactions` | Simulate transactions with detailed config |

### Algod API Tools (13 tools)

| Tool | Description |
|---|---|
| `api_algod_get_account_info` | Get account balance, assets, and auth address |
| `api_algod_get_account_application_info` | Get account-specific application info |
| `api_algod_get_account_asset_info` | Get account-specific asset info |
| `api_algod_get_application_by_id` | Get application information |
| `api_algod_get_application_box` | Get application box by name |
| `api_algod_get_application_boxes` | Get all application boxes |
| `api_algod_get_asset_by_id` | Get asset information |
| `api_algod_get_pending_transaction` | Get pending transaction info |
| `api_algod_get_pending_transactions_by_address` | Get pending transactions for an address |
| `api_algod_get_pending_transactions` | Get all pending transactions |
| `api_algod_get_transaction_params` | Get suggested transaction parameters |
| `api_algod_get_node_status` | Get current node status |
| `api_algod_get_node_status_after_block` | Get node status after a specific round |

### Indexer API Tools (17 tools)

| Tool | Description |
|---|---|
| `api_indexer_lookup_account_by_id` | Get account information |
| `api_indexer_lookup_account_assets` | Get account assets |
| `api_indexer_lookup_account_app_local_states` | Get account app local states |
| `api_indexer_lookup_account_created_applications` | Get apps created by account |
| `api_indexer_search_for_accounts` | Search accounts with filters |
| `api_indexer_lookup_applications` | Get application information |
| `api_indexer_lookup_application_logs` | Get application log messages |
| `api_indexer_search_for_applications` | Search applications |
| `api_indexer_lookup_application_box` | Get application box by name |
| `api_indexer_lookup_application_boxes` | Get all application boxes |
| `api_indexer_lookup_asset_by_id` | Get asset info and configuration |
| `api_indexer_lookup_asset_balances` | Get asset holders and balances |
| `api_indexer_lookup_asset_transactions` | Get transactions for an asset |
| `api_indexer_search_for_assets` | Search assets |
| `api_indexer_lookup_transaction_by_id` | Get transaction by ID |
| `api_indexer_lookup_account_transactions` | Get account transaction history |
| `api_indexer_search_for_transactions` | Search transactions |

### NFDomains Tools (6 tools)

| Tool | Description |
|---|---|
| `api_nfd_get_nfd` | Get NFD by name or application ID |
| `api_nfd_get_nfds_for_addresses` | Get NFDs for specific addresses |
| `api_nfd_get_nfd_activity` | Get activity/changes for NFDs |
| `api_nfd_get_nfd_analytics` | Get NFD analytics data |
| `api_nfd_browse_nfds` | Browse NFDs with filters |
| `api_nfd_search_nfds` | Search NFDs |

### Tinyman AMM Tools (9 tools)

| Tool | Description |
|---|---|
| `api_tinyman_get_pool` | Get pool info by asset pair |
| `api_tinyman_get_pool_analytics` | Get pool analytics |
| `api_tinyman_get_pool_creation_quote` | Get quote for creating a pool |
| `api_tinyman_get_liquidity_quote` | Get quote for adding liquidity |
| `api_tinyman_get_remove_liquidity_quote` | Get quote for removing liquidity |
| `api_tinyman_get_swap_quote` | Get quote for swapping assets |
| `api_tinyman_get_asset_optin_quote` | Get quote for asset opt-in |
| `api_tinyman_get_validator_optin_quote` | Get quote for validator opt-in |
| `api_tinyman_get_validator_optout_quote` | Get quote for validator opt-out |

### Haystack Router Tools (3 tools)

| Tool | Description |
|---|---|
| `api_haystack_get_swap_quote` | Get optimized swap quote with routing across Tinyman V2, Pact, Folks, and LST protocols |
| `api_haystack_execute_swap` | All-in-one swap: quote → sign (via wallet) → submit → confirm |
| `api_haystack_needs_optin` | Check if address needs asset opt-in before swapping |

### Pera Wallet Tools (3 tools)

| Tool | Description |
|---|---|
| `api_pera_asset_verification_status` | Get verification status of a mainnet asset (verified, trusted, suspicious, unknown) |
| `api_pera_verified_asset_details` | Get detailed asset info from Pera (name, unit, logo, decimals, verification) |
| `api_pera_verified_asset_search` | Search Pera verified assets by name, unit name, or keyword |

> Pera Wallet tools are **mainnet only** — the Pera public API does not support testnet or localnet.

### Alpha Arcade Tools (14 tools)

Trade on-chain prediction markets (YES/NO outcomes) denominated in USDC. All prices and quantities use microunits (1,000,000 = $1.00 or 1 share). Read-only tools work without a wallet; trading tools require an active wallet account.

| Tool | Description |
|---|---|
| `alpha_get_live_markets` | Fetch all live prediction markets with prices, volume, and categories |
| `alpha_get_reward_markets` | Fetch markets with liquidity rewards (requires `ALPHA_API_KEY` env var) |
| `alpha_get_market` | Fetch full details for a single market by app ID |
| `alpha_get_orderbook` | Unified YES-perspective orderbook with spread calculation |
| `alpha_get_open_orders` | Open orders for a wallet on a specific market |
| `alpha_get_positions` | YES/NO token positions across all markets |
| `alpha_create_limit_order` | Place a limit order at a specific price (locks ~0.957 ALGO collateral) |
| `alpha_create_market_order` | Place a market order with auto-matching and slippage tolerance |
| `alpha_cancel_order` | Cancel an open order (refunds USDC/tokens and ALGO collateral) |
| `alpha_amend_order` | Edit an existing unfilled order in-place (price, quantity, slippage) |
| `alpha_propose_match` | Propose a match between an existing maker order and your wallet |
| `alpha_split_shares` | Split USDC into equal YES + NO outcome tokens |
| `alpha_merge_shares` | Merge equal YES + NO tokens back into USDC |
| `alpha_claim` | Claim USDC from a resolved market by redeeming winning tokens |

> Optional env var: `ALPHA_API_KEY` — needed for reward market data. `ALPHA_API_BASE_URL` — custom API endpoint (default: `https://platform.alphaarcade.com/api`).

### ARC-26 URI Tools (1 tool)

| Tool | Description |
|---|---|
| `generate_algorand_qrcode` | Generate Algorand URI and QR code per ARC-26 spec |

### Knowledge Tools (1 tool)

| Tool | Description |
|---|---|
| `get_knowledge_doc` | Get markdown content for Algorand knowledge documents |


## Resources

The server exposes MCP resources for direct data access. Wallet resources are described in the [Secure Wallet](#secure-wallet) section above.

### Knowledge Resources

| URI | Description |
|---|---|
| `algorand://knowledge/taxonomy` | Full Algorand knowledge taxonomy |
| `algorand://knowledge/taxonomy/arcs` | Algorand Request for Comments |
| `algorand://knowledge/taxonomy/sdks` | SDK documentation |
| `algorand://knowledge/taxonomy/algokit` | AlgoKit documentation |
| `algorand://knowledge/taxonomy/algokit-utils` | AlgoKit Utils documentation |
| `algorand://knowledge/taxonomy/tealscript` | TEALScript documentation |
| `algorand://knowledge/taxonomy/puya` | Puya documentation |
| `algorand://knowledge/taxonomy/liquid-auth` | Liquid Auth documentation |
| `algorand://knowledge/taxonomy/python` | Python SDK documentation |
| `algorand://knowledge/taxonomy/developers` | Developer documentation |
| `algorand://knowledge/taxonomy/clis` | CLI tools documentation |
| `algorand://knowledge/taxonomy/nodes` | Node management documentation |
| `algorand://knowledge/taxonomy/details` | Technical details documentation |

## Project Structure

```
algorand-mcp/
├── src/                         # TypeScript source
│   ├── index.ts                 # Server entry point
│   ├── networkConfig.ts         # Hardcoded network URLs and client factories
│   ├── algorand-client.ts       # Re-exports from networkConfig
│   ├── env.ts                   # Legacy env shim (unused)
│   ├── types.ts                 # Shared types (Zod schemas)
│   ├── resources/               # MCP Resources
│   │   ├── knowledge/           # Documentation taxonomy
│   │   └── wallet/              # Wallet resources
│   ├── tools/                   # MCP Tools
│   │   ├── commonParams.ts      # Network + pagination schema fragments
│   │   ├── walletManager.ts     # Secure wallet (keychain + SQLite)
│   │   ├── accountManager.ts    # Account operations
│   │   ├── utilityManager.ts    # Utility functions
│   │   ├── algodManager.ts      # TEAL compile, simulate, submit
│   │   ├── arc26Manager.ts      # ARC-26 URI generation
│   │   ├── knowledgeManager.ts  # Knowledge document access
│   │   ├── transactionManager/  # Transaction building
│   │   │   ├── accountTransactions.ts
│   │   │   ├── assetTransactions.ts
│   │   │   ├── appTransactions/
│   │   │   └── generalTransaction.ts
│   │   └── apiManager/          # API integrations
│   │       ├── algod/           # Algod API
│   │       ├── indexer/         # Indexer API
│   │       ├── nfd/             # NFDomains
│   │       ├── tinyman/         # Tinyman AMM
│   │       ├── hayrouter/       # Haystack Router DEX aggregator
│   │       ├── pera/            # Pera Wallet verified assets
│   │       └── alpha/           # Alpha Arcade prediction markets
│   └── utils/
│       └── responseProcessor.ts # Pagination and formatting
├── tests/                       # Test suite
│   ├── helpers/                 # Shared test utilities
│   │   ├── mockFactories.ts     # Mock algod/indexer/keychain factories
│   │   ├── testConfig.ts        # Category enable/disable logic
│   │   ├── e2eSetup.ts          # E2E account provisioning + invokeTool()
│   │   └── testConstants.ts     # Well-known testnet addresses and asset IDs
│   ├── unit/                    # 11 unit test suites (mocked, fast)
│   ├── e2e/                     # 11 E2E test suites (live testnet)
│   │   ├── globalSetup.ts       # Account provisioning + fund-check
│   │   └── globalTeardown.ts    # Cleanup
│   └── jest.config.e2e.js       # E2E-specific Jest config
├── dist/                        # Compiled output
├── jest.config.js               # Unit test Jest config
├── tsconfig.json                # Production TypeScript config
├── tsconfig.test.json           # Test TypeScript config
└── package.json
```

## Response Format

All tool responses follow the MCP content format. API responses include automatic pagination when datasets exceed `itemsPerPage` (default 10):

```json
{
  "data": { ... },
  "metadata": {
    "totalItems": 100,
    "itemsPerPage": 10,
    "currentPage": 1,
    "totalPages": 10,
    "hasNextPage": true,
    "pageToken": "eyJ..."
  }
}
```

Pass `pageToken` from a previous response to fetch the next page. Set `itemsPerPage` on any tool call to control page size.

## Development

```bash
# Install dependencies
npm install

# Type-check
npm run typecheck

# Build
npm run build

# Clean build output
npm run clean
```

## Testing

The project has a comprehensive dual-layer test suite: fast **unit tests** (mocked, no network) and real **E2E tests** (live testnet). Both use [Jest 29](https://jestjs.io/) with [ts-jest](https://kulshekhar.github.io/ts-jest/) and ESM support.

### Quick start

```bash
npm test                    # Unit tests (fast, no network)
npm run test:e2e            # E2E tests (testnet, generates account + fund link)
npm run test:all            # Both
```

### Unit tests

Unit tests cover all 11 tool categories with fully mocked network dependencies. They run in parallel and finish in ~5 seconds. No environment variables or funded accounts are needed.

```bash
npm test
```

**Coverage:** 11 suites, 75+ tests covering success paths, error handling, and edge cases for every tool category.

| Suite | What it tests |
|---|---|
| `accountManager` | Account creation, mnemonic round-trips, rekey parameter validation |
| `utilityManager` | Ping, address validation, encode/decode, sign/verify bytes, encode/decode objects |
| `walletManager` | Full lifecycle: add → list → switch → get info → sign data → remove (mocked keychain + SQLite) |
| `transactionManager` | Payment, asset, app transaction building; sign_transaction; assign_group_id |
| `algodManager` | TEAL compile/disassemble, send raw, simulate |
| `apiAlgod` | All 13 algod API tools with correct mock routing |
| `apiIndexer` | All 17 indexer API tools with fluent builder mocks |
| `apiNfd` | NFD get/search/browse with mocked fetch |
| `apiTinyman` | Tinyman pool/swap with error handling |
| `arc26Manager` | ARC-26 URI generation and QR code SVG output |
| `knowledgeManager` | Knowledge document retrieval and missing-doc error handling |

#### How mocking works

Unit tests use `jest.unstable_mockModule()` (ESM-compatible) to intercept imports before they load. The shared [tests/helpers/mockFactories.ts](tests/helpers/mockFactories.ts) provides:

- **`setupNetworkMocks()`** — Replaces `algorand-client.ts` with mock algod/indexer clients that return deterministic responses without any network calls.
- **`createKeychainMock()`** — Replaces `@napi-rs/keyring` with an in-memory `Map`, so wallet tests work without an OS keychain.
- **Fluent Proxy mocks** — Algorand's Indexer SDK uses a builder pattern (`.searchForAssets().limit(5).do()`). The mock factory uses ES `Proxy` objects that return themselves for any chained method and resolve when `.do()` is called.

### E2E tests

E2E tests call tool handlers directly against **Algorand testnet** (via [AlgoNode](https://algonode.io/) public nodes). They run serially to avoid rate-limiting.

```bash
npm run test:e2e
```

On first run (no mnemonic provided), the test setup:
1. Generates a new Algorand account
2. Prints the address and mnemonic
3. Prints a fund link: https://lora.algokit.io/testnet/fund
4. Runs all tests (unfunded tests still pass)

To run with a funded account:

```bash
E2E_MNEMONIC="word1 word2 ... word25" npm run test:e2e
```

**Coverage:** 11 suites, 35+ tests covering real network interactions.

| Suite | What it tests |
|---|---|
| `account` | Account creation, mnemonic-to-key round-trip chain |
| `utility` | Ping, address validation, encode/decode, sign/verify bytes, encode/decode objects |
| `wallet` | Full wallet lifecycle: add → list → switch → get info → get assets → sign data → remove |
| `transaction` | Build payment → sign → verify txID; build asset opt-in; build group with assign_group_id |
| `algod` | Compile + disassemble TEAL round-trip |
| `algodApi` | Account info, suggested params, node status, asset info via algod |
| `indexerApi` | Account lookup, asset/transaction/account search via indexer |
| `nfd` | Look up "algo.algo", search NFDs, browse NFDs |
| `tinyman` | Get ALGO/USDC pool |
| `arc26` | Generate ARC-26 URI, verify format + QR SVG |
| `knowledge` | Retrieve known knowledge doc content |

### Category activation

E2E tests can be selectively enabled by category or individual tool via environment variables. **By default all categories are enabled.**

#### Enable specific categories

```bash
E2E_WALLET=1 npm run test:e2e            # Only wallet tests
E2E_ALGOD=1 E2E_UTILITY=1 npm run test:e2e  # Algod + utility tests
```

#### Available category flags

| Env var | Category |
|---|---|
| `E2E_ALL=1` | All categories (explicit) |
| `E2E_WALLET=1` | Wallet tools |
| `E2E_ACCOUNT=1` | Account tools |
| `E2E_UTILITY=1` | Utility tools |
| `E2E_TRANSACTION=1` | Transaction tools |
| `E2E_ALGOD=1` | Algod tools |
| `E2E_ALGOD_API=1` | Algod API tools |
| `E2E_INDEXER_API=1` | Indexer API tools |
| `E2E_NFD=1` | NFDomains tools |
| `E2E_TINYMAN=1` | Tinyman tools |
| `E2E_ARC26=1` | ARC-26 tools |
| `E2E_KNOWLEDGE=1` | Knowledge tools |

**Important:** Setting any individual flag (e.g. `E2E_WALLET=1`) disables all other categories unless `E2E_ALL=1` is also set.

#### Enable specific tools

```bash
E2E_TOOLS=ping,validate_address npm run test:e2e
```

The `E2E_TOOLS` variable accepts a comma-separated list of tool names. Only tests for those specific tools will run.

### Test file structure

```
tests/
├── helpers/
│   ├── mockFactories.ts       # Mock algod/indexer/keychain factories
│   ├── testConfig.ts          # Category enable/disable logic
│   ├── e2eSetup.ts            # E2E account provisioning + invokeTool()
│   └── testConstants.ts       # Well-known testnet addresses and asset IDs
├── unit/                      # 11 unit test files (*.test.ts)
│   ├── accountManager.test.ts
│   ├── utilityManager.test.ts
│   ├── walletManager.test.ts
│   ├── transactionManager.test.ts
│   ├── algodManager.test.ts
│   ├── apiAlgod.test.ts
│   ├── apiIndexer.test.ts
│   ├── apiNfd.test.ts
│   ├── apiTinyman.test.ts
│   ├── arc26Manager.test.ts
│   └── knowledgeManager.test.ts
├── e2e/                       # 11 E2E test files (*.e2e.test.ts)
│   ├── globalSetup.ts         # Account provisioning + fund-check
│   ├── globalTeardown.ts      # Cleanup
│   ├── account.e2e.test.ts
│   ├── utility.e2e.test.ts
│   ├── wallet.e2e.test.ts
│   ├── transaction.e2e.test.ts
│   ├── algod.e2e.test.ts
│   ├── algodApi.e2e.test.ts
│   ├── indexerApi.e2e.test.ts
│   ├── nfd.e2e.test.ts
│   ├── tinyman.e2e.test.ts
│   ├── arc26.e2e.test.ts
│   └── knowledge.e2e.test.ts
└── jest.config.e2e.js         # E2E-specific Jest config
```

### Jest configuration

| Config | Purpose | Key settings |
|---|---|---|
| `jest.config.js` (root) | Unit tests | `testTimeout: 10s`, parallel workers, `testMatch: tests/unit/**` |
| `tests/jest.config.e2e.js` | E2E tests | `testTimeout: 60s`, `maxWorkers: 1` (serial), `globalSetup/globalTeardown` |
| `tsconfig.test.json` | TypeScript for tests | `rootDir: "."`, includes both `src/` and `tests/` |

### Writing new tests

**Unit test template:**

```typescript
import { jest } from '@jest/globals';
import { setupNetworkMocks } from '../helpers/mockFactories.js';

jest.unstable_mockModule('../../src/algorand-client.js', () => setupNetworkMocks());

const { YourManager } = await import('../../src/tools/yourManager.js');

describe('YourManager', () => {
  it('does something', async () => {
    const result = await YourManager.handleTool('tool_name', { arg: 'value' });
    const data = JSON.parse(result.content[0].text);
    expect(data.field).toBeDefined();
  });
});
```

**E2E test template:**

```typescript
import { describeIf, testConfig } from '../helpers/testConfig.js';
import { invokeTool, parseToolResponse } from '../helpers/e2eSetup.js';

describeIf(testConfig.isCategoryEnabled('your-category'))('Your Tools (E2E)', () => {
  it('does something on testnet', async () => {
    const data = parseToolResponse(
      await invokeTool('tool_name', { arg: 'value', network: 'testnet' }),
    );
    expect(data.field).toBeDefined();
  });
});
```

## Dependencies

- [algosdk](https://github.com/algorand/js-algorand-sdk) v3 — Algorand JavaScript SDK
- [@modelcontextprotocol/sdk](https://github.com/modelcontextprotocol/typescript-sdk) — MCP TypeScript SDK
- [@napi-rs/keyring](https://github.com/nicolo-ribaudo/napi-keyring) — Native OS keychain access (macOS Keychain, Linux libsecret, Windows Credential Manager)
- [sql.js](https://github.com/sql-js/sql.js) — Embedded SQLite (WASM) for wallet metadata persistence
- [@noble/curves](https://github.com/paulmillr/noble-curves) — Pure JS Ed25519 for raw data signing (`wallet_sign_data`)
- [@tinymanorg/tinyman-js-sdk](https://github.com/tinymanorg/tinyman-js-sdk) — Tinyman AMM SDK
- [@alpha-arcade/sdk](https://github.com/pfrfrfr/alpha-js-sdk) — Alpha Arcade prediction market SDK
- [zod](https://github.com/colinhacks/zod) — Runtime type validation
- [qrcode](https://github.com/soldair/node-qrcode) — QR code generation for ARC-26

## License

MIT
