Skip to main content
Glama
README.md
# Kaia MCP Server

Production-ready [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server for the [Kaia](https://kaia.io) blockchain. Exposes 25 partner-safe tools (26 if `KAIA_ALLOW_UNSAFE_WALLET=1`), 5 resources, and 6 prompts for balance, transactions, blocks, tokens, NFTs, contracts, network info, and calldata encoding.

HTTP is a partner-style connector: OAuth 2.1 with PKCE (browser) or device flow (CLI), fail-closed bearer auth, and token scopes mapped to an allowed-tool registry. An in-process demo IdP runs with the HTTP server so tests and CI need no real credentials. See [docs/AUTH.md](docs/AUTH.md).

## Installation

```bash
npm install -g kaia-mcp
# or
npx kaia-mcp
```

## Quick start

**stdio (default)** — run and connect via stdin/stdout (e.g. Claude Desktop, Cursor):

```bash
npx kaia-mcp
# or
npx kaia-mcp --transport stdio
```

**HTTP** — Streamable HTTP on a port. Partner default (`KAIA_AUTH_MODE=required`) requires `Authorization: Bearer` on every MCP request. The same process serves the demo OAuth/OIDC endpoints.

```bash
npx kaia-mcp --transport http --port 3100
# GET http://127.0.0.1:3100/health
# GET http://127.0.0.1:3100/.well-known/openid-configuration
```

## Tools (25 partner-safe)

| Tool                       | Description                                                                                                                                    |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `get_kaia_balance`         | Get KAIA balance for an address                                                                                                                |
| `get_account_info`         | Get account summary: balance, tx count, nonce                                                                                                  |
| `get_account_tokens`       | List ERC-20 token holdings for an address                                                                                                      |
| `get_account_nfts`         | List NFT holdings for an address                                                                                                               |
| `get_transaction`          | Get transaction by hash                                                                                                                        |
| `get_transaction_receipt`  | Get transaction receipt and status                                                                                                             |
| `get_account_transactions` | List recent transactions for an address                                                                                                        |
| `estimate_gas`             | Estimate gas for a transaction                                                                                                                 |
| `get_block_number`         | Current block number                                                                                                                           |
| `get_block`                | Get block by number or tag                                                                                                                     |
| `get_block_rewards`        | Block rewards for a block                                                                                                                      |
| `get_token_info`           | Token metadata (name, symbol, supply)                                                                                                          |
| `get_token_holders`        | Top token holders                                                                                                                              |
| `get_token_transfers`      | Token transfer history                                                                                                                         |
| `get_token_allowance`      | ERC-20/KIP-7 allowance (owner/spender) for DeFi                                                                                                |
| `get_nft_info`             | NFT collection info                                                                                                                            |
| `get_nft_item`             | Single NFT item (owner, metadata)                                                                                                              |
| `get_nft_transfers`        | NFT transfer history                                                                                                                           |
| `read_contract`            | Read contract view/pure function                                                                                                               |
| `get_contract_abi`         | Verified contract ABI from KaiaScan                                                                                                            |
| `get_contract_source`      | Verified contract source from KaiaScan                                                                                                         |
| `get_gas_price`            | Current gas price                                                                                                                              |
| `get_kaia_price`           | KAIA price (USD, BTC, stats) from KaiaScan                                                                                                     |
| `get_chain_info`           | Chain id and name (mainnet/kairos)                                                                                                             |
| `encode_function_data`     | Encode contract call data from ABI and args (`kaia:encode`)                                                                                    |
| `generate_wallet`          | **Not in the default list.** Unsafe local-dev only (`KAIA_ALLOW_UNSAFE_WALLET=1` + `kaia:wallet`). Never returns private keys in partner mode. |

## Resources (5)

| URI                             | Description                                         |
| ------------------------------- | --------------------------------------------------- |
| `kaia://mainnet/status`         | Mainnet status: block height, gas price, KAIA price |
| `kaia://kairos/status`          | Kairos testnet status                               |
| `kaia://mainnet/tokens/popular` | Popular mainnet token addresses with name/symbol    |
| `kaia://mainnet/top-accounts`   | Top 100 KAIA holders                                |
| `kaia://docs/rpc-methods`       | Static reference of Kaia RPC methods                |

## Prompts (6)

| Name                      | Args                               | Description                                 |
| ------------------------- | ---------------------------------- | ------------------------------------------- |
| `analyze-wallet`          | address, network?                  | Analyze wallet: balance, txs, tokens        |
| `investigate-transaction` | txHash                             | Investigate tx: status, gas, transfers      |
| `token-research`          | contractAddress, network?          | Research token: supply, holders, activity   |
| `nft-lookup`              | contractAddress, tokenId, network? | Look up NFT: owner, metadata, history       |
| `gas-report`              | network?                           | Gas report: price, history, recommendations |
| `smart-contract-audit`    | contractAddress, network?          | Review contract: ABI, source, type          |

## Configuration

Environment variables (see `.env.example`):

| Variable                                | Description                                                                                                                                                                                                                                                                                              | Default                                                                            |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `KAIA_RPC_URL`                          | Kaia mainnet RPC endpoint                                                                                                                                                                                                                                                                                | `https://public-en.node.kaia.io`                                                   |
| `KAIA_KAIROS_RPC_URL`                   | Kairos testnet RPC                                                                                                                                                                                                                                                                                       | `https://public-en-kairos.node.kaia.io`                                            |
| `KAIASCAN_API_KEY`                      | KaiaScan API key (optional)                                                                                                                                                                                                                                                                              | —                                                                                  |
| `KAIA_DEFAULT_NETWORK`                  | mainnet or kairos                                                                                                                                                                                                                                                                                        | mainnet                                                                            |
| `LOG_LEVEL`                             | debug, info, warn, error                                                                                                                                                                                                                                                                                 | info                                                                               |
| `RATE_LIMIT_RPC`                        | Max RPC requests per second                                                                                                                                                                                                                                                                              | 10                                                                                 |
| `RATE_LIMIT_KAIASCAN`                   | Max KaiaScan requests per second                                                                                                                                                                                                                                                                         | 5                                                                                  |
| `RPC_TIMEOUT_MS`                        | RPC request timeout (ms)                                                                                                                                                                                                                                                                                 | 30000                                                                              |
| `KAIASCAN_TIMEOUT_MS`                   | KaiaScan request timeout (ms)                                                                                                                                                                                                                                                                            | 15000                                                                              |
| `KAIA_AUTH_MODE`                        | HTTP auth: `required` or `off`                                                                                                                                                                                                                                                                           | `required`                                                                         |
| `KAIA_OAUTH_CLIENT_ID`                  | Demo public client id                                                                                                                                                                                                                                                                                    | `kaia-mcp-demo`                                                                    |
| `KAIA_ACCESS_TOKEN_TTL_SECONDS`         | Demo access-token TTL                                                                                                                                                                                                                                                                                    | 900                                                                                |
| `KAIA_PUBLIC_URL`                       | Public origin (`https://host[:port]`, no path): OAuth issuer, canonical resource URI and token `aud`; also the default allowed browser Origin                                                                                                                                                            | `http://127.0.0.1:<port>`                                                          |
| `KAIA_OAUTH_LEGACY_AUDIENCE`            | Extra `aud` minted next to the canonical URI for gateways that pin a non-URI audience (never accepted alone). `KAIA_OAUTH_AUDIENCE` is a deprecated alias                                                                                                                                                | unset                                                                              |
| `KAIA_OAUTH_REQUIRE_RESOURCE`           | Reject authorize/device/token requests without an RFC 8707 `resource`                                                                                                                                                                                                                                    | off (missing = this server)                                                        |
| `KAIA_ALLOWED_ORIGINS`                  | Extra browser origins allowed on the MCP/OAuth endpoints (comma-separated); a foreign `Origin` gets 403, no `Origin` passes                                                                                                                                                                              | only the public origin                                                             |
| `KAIA_OAUTH_PREVIOUS_SIGNING_KEY_FILES` | Retired signing keys (comma-separated PEMs) still published in the JWKS and accepted for verification                                                                                                                                                                                                    | unset                                                                              |
| `KAIA_OAUTH_SIGNING_KEY_FILE`           | Dev RS256 key path, created 0600 if missing; keep it gitignored (`.kaia-dev/`)                                                                                                                                                                                                                           | unset (in-memory key per process)                                                  |
| `KAIA_OAUTH_REVOCATION_FILE`            | Where revoked access-token `jti`s are persisted (atomic, durable writes, 0600). A corrupt, unreadable or group/world-writable file refuses startup. One file per server process: a file another running instance holds is refused at startup, so instances sharing a signing key must each set their own | `revoked-jti.json` next to the signing key file; in memory when no key file is set |
| `KAIA_INTROSPECTION_CLIENT_ID`          | Gateway client id for `/oauth/introspect`                                                                                                                                                                                                                                                                | `kaia-mcp-gateway`                                                                 |
| `KAIA_INTROSPECTION_CLIENT_SECRET`      | Gateway secret (HTTP Basic). Unset: introspection is not offered                                                                                                                                                                                                                                         | unset                                                                              |
| `KAIA_ALLOW_UNSAFE_WALLET`              | Enable `generate_wallet` private keys (local only)                                                                                                                                                                                                                                                       | off                                                                                |

## MCP client setup

**Cursor** — add to Cursor MCP settings (e.g. `~/.cursor/mcp.json` or project MCP config):

```json
{
  "mcpServers": {
    "kaia": {
      "command": "npx",
      "args": ["kaia-mcp"],
      "env": {}
    }
  }
}
```

**Claude Desktop** — in Claude Desktop config (e.g. `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "mcpServers": {
    "kaia": {
      "command": "npx",
      "args": ["kaia-mcp"],
      "env": {}
    }
  }
}
```

Override env as needed, e.g. `"env": { "KAIA_RPC_URL": "https://your-rpc.io", "KAIASCAN_API_KEY": "your-key" }`.

## Links

- [Kaia docs](https://docs.kaia.io)
- [KaiaScan API](https://docs.kaiascan.io)

## Programmatic use

```ts
import { createKaiaMcpServer, runKaiaMcpServer } from "kaia-mcp";

// Get the MCP Server instance (e.g. for custom transport)
const server = createKaiaMcpServer();

// Run over stdio (CLI default)
await runKaiaMcpServer();
```

For HTTP:

```ts
import { runKaiaMcpServerHttp } from "kaia-mcp";
await runKaiaMcpServerHttp(3100);
```

## Docker

```bash
docker build -t kaia-mcp .
docker run -p 3100:3100 -e KAIA_RPC_URL=https://public-en.node.kaia.io kaia-mcp
```

Server listens on port 3100 (Streamable HTTP transport). Override env as needed.

### Connecting to the Docker server

The container runs the **HTTP** transport (not stdio), so clients must connect by **URL**.

**Cursor** — in MCP settings (e.g. `~/.cursor/mcp.json` or project MCP config), add a server entry with `url`:

```json
{
  "mcpServers": {
    "kaia": {
      "url": "http://localhost:3100"
    }
  }
}
```

If your client expects a path, use `http://localhost:3100/mcp`. Restart Cursor after changing MCP config.

**Claude Desktop** — if your Claude Desktop build supports remote MCP URLs, use the same `url` in its MCP config.

**Test from the host** (with the container running). Health is unauthenticated; MCP is not:

```bash
curl -s http://localhost:3100/health
# Obtain a demo token (see docs/AUTH.md), then:
curl -s -X POST http://localhost:3100 \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq
```

You should see a list of tools filtered by the token’s scopes. Replace `localhost` with your machine’s IP or hostname when connecting from another device.

## Partner integration

This server is an MCP connector, not a hosted product partnership. Integrate over HTTP with OAuth; do not treat stdio as a partner path.

### Integration checklist

1. Discover `/.well-known/oauth-protected-resource` and `/.well-known/openid-configuration` (demo) or your authorization server’s metadata. Access tokens are RS256 JWTs; fetch keys from the discovery `jwks_uri` and pin `iss` and `aud` (both the server's canonical URI, `KAIA_PUBLIC_URL`) instead of trusting whatever the token says. Send `resource=<canonical URI>` on authorize and token requests (RFC 8707) and check the `iss` on the authorization response (RFC 9207).
2. Register a **public** client. Require PKCE `S256` for browser agents; use device flow for CLIs that cannot host a redirect.
3. Allowlist redirect URIs. The demo IdP accepts only `http://127.0.0.1/callback`, `http://localhost/callback`, and `/cb` variants.
4. Request least privilege: `kaia:read` for chain reads, add `kaia:encode` only if the agent must build calldata. Never request `kaia:wallet` in production.
5. Send `Authorization: Bearer <access_token>` on **every** MCP HTTP request. The transport is stateless (MCP 2026-07-28 via SDK v2 `createMcpHandler`): no `Mcp-Session-Id`, modern clients may use `server/discover` instead of `initialize`, `GET`/`DELETE` answer 405. Match `Mcp-Method`/`Mcp-Name`/`MCP-Protocol-Version` to the body on modern requests (`-32020` on mismatch). `tools/list` is `cacheScope: private`.
6. Handle fail-closed errors literally: `-32040` unauthorized, `-32041` token_expired, `-32043` invalid_token (HTTP 401 with a `WWW-Authenticate` challenge naming `resource_metadata`), and `-32042` insufficient_scope (HTTP 403, `error="insufficient_scope"` with the required `scope`). Retry only after a new token; do not retry a denied tool.
7. On disconnect or user logout, `POST /oauth/revoke` with the access or refresh token. Revocation is by `jti` and only the issuer knows about it: a gateway that verifies JWTs offline must also call `/oauth/introspect` (client-authenticated) if it needs to see revocations before `exp`.
8. Keep tokens out of logs, crash dumps, eval fixtures, and git. Prefer a token fingerprint if you must correlate requests.
9. Leave `KAIA_ALLOW_UNSAFE_WALLET` unset. `generate_wallet` must not appear in partner tool lists and must not return private keys.
10. Point production at your own OIDC issuer; the in-process demo IdP is for tests/CI/local bring-up only. No real user secrets or signing keys belong in this repo.
11. If you keep your own copy of the tool → scope map (e.g. in a policy gateway), compare it against `GET /.well-known/kaia-mcp/tool-scopes` at startup and refuse to run on drift.

### Threat notes

- **Scopes.** A `kaia:read` token must not call `encode_function_data`. Scope checks run before handlers; a deny has no chain or wallet side effect.
- **Secret handling.** Access tokens are signed JWTs and are not stored; refresh tokens are hashed at rest. The logger redacts bearer values and bare JWTs. The signing key is generated at startup (or read from a gitignored dev path) and only the public JWK is published. Wallet private keys are not emitted unless the explicit unsafe dev flag is on.
- **Offline verification vs revocation.** A JWT stays cryptographically valid until `exp` even after `/oauth/revoke`. kaia-mcp itself rejects revoked `jti`s; external verifiers need introspection or a short TTL.
- **Algorithm confusion.** Only `RS256` with the published `kid` is accepted; `alg=none`, `HS*`, and foreign keys fail as `invalid_token`.
- **Sessions and DNS rebinding.** There are no MCP sessions to steal; the bearer token is checked on every request. A browser page on a foreign origin is refused with 403 before auth (allow-list with `KAIA_ALLOWED_ORIGINS`).
- **PKCE downgrade.** `code_challenge_method=plain` is rejected.
- **Open redirect.** Unregistered `redirect_uri` values are rejected.
- **Fallbacks.** `KAIA_AUTH_MODE=off` and stdio skip OAuth. Those paths are local-only. Partners must not ship them as a “fallback” for failed authentication; failed auth is a deny.
- **Unsafe wallet flag.** If an operator enables `KAIA_ALLOW_UNSAFE_WALLET=1`, private keys can leave the process. Treat that as a break-glass local tool, not a partner feature.

Full protocol detail: [docs/AUTH.md](docs/AUTH.md).

## License

MIT

TDQS

B3.3/5.0

Scored across 25 tools

Disambiguation4/5

Most tools target distinct resources and actions, especially blocks, transactions, contracts, and NFTs. A few overlaps exist, such as get_kaia_balance vs. get_account_info (which also returns balance) and get_account_tokens vs. token metadata/holder tools, but descriptions usually clarify the boundaries.

Naming Consistency4/5

The set mostly follows a snake_case prefix_verb/noun convention, especially with get_* tools. Minor deviations like read_contract, estimate_gas, and encode_function_data are semantically appropriate rather than confusing.

Tool Count3/5

25 tools is on the heavy side for an MCP server, even though the Kaia blockchain domain is broad. The surface is usable but could likely be consolidated, especially around account, token, and contract queries.

Completeness4/5

The server covers a wide read-only surface: balances, accounts, tokens, NFTs, transactions, blocks, gas, contract ABI/source, and contract reads. Notable gaps include no send/broadcast transaction tool and no explicit event-log query, though these may be outside the intended read-only scope.

Maintenance

ActivityMaintained
ResponsivenessResponsive