Skip to main content
Glama
PharosNetwork

Pharos MCP Server

Official
README.md
# 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

A3.7/5.0

Scored across 14 tools

Disambiguation5/5

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.

Naming Consistency5/5

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.

Tool Count5/5

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.

Completeness5/5

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.

Maintenance

ActivitySlowing
ResponsivenessNo issues