Pharos MCP Server
Official# Pharos MCP Server
An [MCP](https://modelcontextprotocol.io) server that brings Pharos chain data into
MCP-compatible IDEs and agent workflows — Claude Desktop, Cursor, Windsurf, and custom
agent runners.
- **Network:** Pharos (EVM-compatible, 100% Ethereum-compatible)
- **Chain ID:** `1672` (`0x688`)
- **RPC:** `https://rpc.pharos.xyz`
- **Native token:** PROS
- **Hosted endpoint:** `https://mcp.pharos.xyz/mcp`
## Quick start — use the hosted server
No API key required. Add this to your MCP client configuration:
```json
{
"mcpServers": {
"pharos": {
"type": "http",
"url": "https://mcp.pharos.xyz/mcp"
}
}
}
```
The hosted endpoint speaks **Streamable HTTP**, so your client must support remote MCP
servers. Clients that only support stdio can bridge with
[`mcp-remote`](https://www.npmjs.com/package/mcp-remote):
```json
{
"mcpServers": {
"pharos": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.pharos.xyz/mcp"]
}
}
}
```
Once connected, ask your agent things like *"what's the latest Pharos block?"* or
*"check the PROS balance of 0x…"*.
**Rate limit:** 120 requests per minute per IP. Exceeding it returns HTTP `429` with a
`Retry-After` header.
## Tools
13 read-only tools are available today.
| Tool | Underlying JSON-RPC | Description | Parameters |
|---|---|---|---|
| `get_chain_info` | `eth_chainId`, `eth_blockNumber`, `web3_clientVersion` | Chain ID, client version, latest block | — |
| `get_block_number` | `eth_blockNumber` | Latest block height | — |
| `get_balance` | `eth_getBalance` | Native PROS balance of an address | `address`, `block?` |
| `get_transaction_count` | `eth_getTransactionCount` | Address nonce | `address`, `block?` |
| `get_gas_price` | `eth_gasPrice`, `eth_maxPriorityFeePerGas` | Current gas price and priority fee | — |
| `get_block` | `eth_getBlockByNumber` | Block by tag or number | `block?`, `includeTransactions?` |
| `get_transaction` | `eth_getTransactionByHash` | Transaction by hash | `hash` |
| `get_transaction_receipt` | `eth_getTransactionReceipt` | Transaction receipt | `hash` |
| `get_code` | `eth_getCode` | Contract bytecode | `address`, `block?` |
| `get_storage_at` | `eth_getStorageAt` | Read a contract storage slot | `address`, `position`, `block?` |
| `eth_call` | `eth_call` | Read-only contract call | `transaction`, `block?` |
| `estimate_gas` | `eth_estimateGas` | Estimate gas without sending | `transaction` |
| `get_logs` | `eth_getLogs` | Event logs over a block range (max 1000 blocks per call) | `address?`, `topics?`, `fromBlock?`, `toBlock?` |
`block` accepts a named tag (`latest`, `earliest`, `pending`, `safe`, `finalized`), a
decimal number, or a `0x` hex quantity.
### Transaction broadcasting
A `send_raw_transaction` tool exists but is **disabled on the hosted endpoint**
(`ENABLE_SEND_RAW_TRANSACTION=false`), which is therefore **read-only**. Calling it there
returns an error.
The design is **non-custodial**: the server never receives or stores a private key. It only
relays a transaction you already signed locally. If you self-host and enable it, put
authentication in front of the endpoint first — an open, unauthenticated broadcast endpoint
lets anyone push transactions through your node.
## Self-hosting
Requires Node.js 20+.
```bash
npm install
npm run build
```
### stdio transport
```bash
npm start
```
```json
{
"mcpServers": {
"pharos": {
"command": "node",
"args": ["/absolute/path/to/pharos-mcp/dist/src/index.js"],
"env": {
"PHAROS_RPC_URL": "https://rpc.pharos.xyz",
"PHAROS_CHAIN_ID": "1672",
"PHAROS_NATIVE_SYMBOL": "PROS"
}
}
}
}
```
### HTTP transport
```bash
MCP_TRANSPORT=http \
MCP_HOST=127.0.0.1 \
MCP_PORT=3001 \
MCP_AUTH_TOKEN="$(openssl rand -hex 32)" \
npm start
```
- MCP endpoint: `http://127.0.0.1:3001/mcp` (POST only)
- Health check: `http://127.0.0.1:3001/health`
With `MCP_AUTH_TOKEN` set, clients must send `Authorization: Bearer <token>`.
> **Scaling note.** In HTTP mode each MCP session is held in the server process's memory.
> Running multiple instances behind a load balancer therefore needs real session affinity —
> and cookie-based stickiness is not enough, because MCP clients generally do not store or
> resend cookies. Requests that land on an instance which does not own the session fail with
> `Unknown MCP session`. Run a single instance, or move session state into a shared store
> before scaling out.
### Docker
```bash
docker build -t pharos-mcp-server .
docker run -p 3001:3001 -e MCP_TRANSPORT=http -e MCP_HOST=0.0.0.0 pharos-mcp-server
```
## Configuration
| Variable | Default | Description |
|---|---|---|
| `PHAROS_RPC_URL` | `https://rpc.pharos.xyz` | Upstream JSON-RPC endpoint |
| `PHAROS_CHAIN_ID` | `1672` | Expected chain ID |
| `PHAROS_NATIVE_SYMBOL` | `PROS` | Native token symbol |
| `MCP_TRANSPORT` | `stdio` | `stdio` or `http` |
| `MCP_HOST` | `127.0.0.1` | Bind address (use `0.0.0.0` in containers) |
| `MCP_PORT` | `3001` | HTTP port |
| `MCP_AUTH_TOKEN` | — | Bearer token; auth is off when unset |
| `ENABLE_SEND_RAW_TRANSACTION` | `false` | Enable transaction broadcasting |
| `RATE_LIMIT_PER_MINUTE` | `120` | Per-IP request cap; `0` disables |
| `TRUST_PROXY` | `false` | Set `true` when running behind exactly one trusted proxy (e.g. an ALB) so rate limiting uses the real client IP. Leave `false` if the server is directly reachable — otherwise clients can spoof `X-Forwarded-For` and evade the limiter |
| `MAX_SESSIONS` | `500` | Maximum concurrent MCP sessions; new sessions past the cap get `503`. `0` disables the cap |
| `SESSION_TIMEOUT_MINUTES` | `30` | Idle sessions are evicted after this long |
## Development
```bash
npm test # unit tests + a live chain ID check
npm run smoke # end-to-end MCP test over stdio against the real RPC
npm run smoke:http # end-to-end test against a running HTTP server
```
`npm run smoke` exercises the write path by signing a zero-value transaction with a freshly
generated empty wallet. The RPC rejects it for insufficient funds, so nothing is broadcast
on-chain.
## Security notes
- Never pass a private key or mnemonic to this server; it has no use for one.
- `send_raw_transaction` is disabled by default.
- HTTP binds to `127.0.0.1` by default.
- For a public deployment, terminate TLS in front of the server and keep rate limiting on.
- `admin_*`, `debug_*`, `personal_*` and `txpool_*` methods are not exposed.
## License
MIT
TDQS
Scored across 14 tools
Every tool targets a distinct resource or action: code, storage, call, gas, logs, transaction, receipt, balance, nonce, gas price, block, chain info. No two tools have overlapping purposes; even similar ones like get_transaction and get_transaction_receipt are clearly separated by what they return.
All tools follow a snake_case pattern, predominantly verb_get_* for reads and send_raw_transaction for the write operation. eth_call and estimate_gas are also snake_case and align with common EVM naming, preserving overall consistency.
14 tools is well within the ideal 3-15 range for an EVM blockchain server. Each tool corresponds to a standard JSON-RPC method, and none are redundant or superfluous given the server's purpose.
The tool set covers the full spectrum of EVM node interactions: chain info, blocks, transactions, receipts, balances, storage, code, calls, gas estimation, logs, nonce, gas price, and raw transaction broadcast. This is a complete surface for typical dApp or chain interaction needs.