Skip to content
naveed949Public

About

MCP server for Kaia accounts, tokens, NFTs, contracts, and calldata. OAuth-scoped HTTP and fail-closed bearer auth.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Kaia MCP Server

Production-ready Model Context Protocol (MCP) server for the Kaia 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.

Installation

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

Quick start

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

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.

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 jtis 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):

{
  "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):

{
  "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

Programmatic use

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:

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

Docker

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:

{
  "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:

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 jtis; 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.

License

MIT

About

MCP server for Kaia accounts, tokens, NFTs, contracts, and calldata. OAuth-scoped HTTP and fail-closed bearer auth.

Topics

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages