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