AI Agents (MCP)
Running It as a Shared HTTP Server
For teams and hosted agents · Estimated read time: 6 minutes
When to use it
Most people should let their client start the server over stdio (see the client guides). The HTTP mode is for when one long-running server is easier:
- a team that wants one shared, read-only Asentum server instead of a copy on every laptop;
- a hosted or cloud agent that cannot start local processes but can reach a URL;
- a self-hosted agent (see OpenClaw) where you want the MCP server as its own service.
Start it
Node.js 18.17 or newer, then:
npx -y @asentum/mcp --http 3333Output
asentum-mcp 0.1.0 on http://127.0.0.1:3333/mcp (read-only)The port defaults to 3333 (or PORT). It binds to 127.0.0.1, so only the same machine can reach it. That is deliberate: leave it that way and put a proxy with auth in front for anything else. The HOST variable changes the bind address; only set it to something else inside a private network (for example a container network where only the proxy can reach it).
Check it from the same machine:
curl http://127.0.0.1:3333/health
# {"ok":true}
curl -s -X POST http://127.0.0.1:3333/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'What it serves
| Path | Meaning |
|---|---|
| POST /mcp | MCP over streamable HTTP. Stateless: every request gets a fresh server, so there are no sessions to keep. |
| GET /mcp | 405, as the spec allows for a server without a standalone event stream. Clients carry on with POST. |
| GET /health | {"ok":true}, for load balancers and uptime checks. |
By default it serves the read, build and docs tools, the resources and the prompts. The tools that sign with a configured key (get_wallet, deploy_contract, call_contract, send_ase) are left out, even if a key is set. build_unsigned_tx and submit_signed_tx stay, because the signing happens somewhere else.
Run it as a service
Install a fixed version once, rather than letting npx fetch the latest on every start:
sudo npm install -g @asentum/mcp@0.1.0
which asentum-mcp # e.g. /usr/bin/asentum-mcp or /usr/local/bin/asentum-mcpA systemd unit for a Linux server, running as an unprivileged user with no key:
/etc/systemd/system/asentum-mcp.service
[Unit]
Description=Asentum MCP server (read-only, streamable HTTP)
After=network-online.target
Wants=network-online.target
[Service]
User=asentum-mcp
ExecStart=/usr/bin/asentum-mcp --http 3333
Environment=HOST=127.0.0.1
# Environment=ASENTUM_RPC_URL=http://127.0.0.1:8545
Restart=always
RestartSec=5
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
[Install]
WantedBy=multi-user.targetsudo useradd --system --no-create-home asentum-mcp
sudo systemctl daemon-reload
sudo systemctl enable --now asentum-mcp
journalctl -u asentum-mcp -fAdjust the ExecStart path to what which asentum-mcp printed.
Put it behind auth
The server has no login of its own. Anything that can reach the port can use it. So for anything beyond one machine, add auth in front, or keep it on a private network.
Option 1: a private network (simplest)
Keep it on 127.0.0.1 and reach it over a private network such as a VPN or Tailscale, or an SSH tunnel from your laptop:
ssh -N -L 3333:127.0.0.1:3333 you@your-server
# now http://127.0.0.1:3333/mcp on your laptop reaches the serverOption 2: HTTPS with a bearer token
Make a long random token (openssl rand -hex 32) and have the proxy reject any request without it. Caddy gets HTTPS certificates by itself:
Caddyfile
mcp.example.com {
@unauthorized not header Authorization "Bearer {$ASENTUM_MCP_TOKEN}"
respond @unauthorized 401
reverse_proxy 127.0.0.1:3333
}Set ASENTUM_MCP_TOKEN in Caddy's environment. The same with nginx (inside a server block that already has TLS set up):
nginx
location = /mcp {
if ($http_authorization != "Bearer <your long random token>") {
return 401;
}
proxy_pass http://127.0.0.1:3333/mcp;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_buffering off; # responses can stream as events
proxy_read_timeout 300s; # receipts can wait for finality
}- Always HTTPS. A token over plain HTTP can be read on the way.
- Give each person or agent its own token if your proxy supports it, so you can revoke one without the others.
- An SSO proxy (for example oauth2-proxy or your identity provider's gateway) works too, as long as your client can send the header it needs.
Connect clients
Claude Code
claude mcp add --transport http asentum https://mcp.example.com/mcp \
--header "Authorization: Bearer <token>"Cursor (~/.cursor/mcp.json)
{
"mcpServers": {
"asentum": {
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer <token>" }
}
}
}VS Code (mcp.json)
{
"servers": {
"asentum": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer <token>" }
}
}
}OpenClaw (~/.openclaw/openclaw.json)
{
mcp: {
servers: {
asentum: {
url: "https://mcp.example.com/mcp",
transport: "streamable-http",
headers: { Authorization: "Bearer <token>" },
},
},
},
}Other clients: use their streamable HTTP option, not SSE. See Other MCP Clients. Keep tokens in each client's user-level config, not in a committed project file.
Never expose write tools
ASENTUM_HTTP_ALLOW_WRITES=1 adds the signing tools back in HTTP mode, signing with the key in the server's environment. On a shared server that means anyone who can reach it can spend from that key, and every transaction comes from one wallet no matter who asked.
- Never set it on a server that more than one person or agent can reach, or that is reachable from the internet, with or without a proxy.
- The only reasonable use is a private, single-user server on
127.0.0.1, with a dedicated testnet key holding a few test ASE. - The server still refuses to sign for any chain other than testnet 1423 unless
ASENTUM_ALLOW_MAINNET=1. Do not set that on a shared server.
For a shared server where people still need to send transactions, let each person sign their own:
- The agent calls
build_unsigned_tx, which returns the transaction body and the exact digest to sign. - The person signs it with their own wallet or a local process (ML-DSA-65). The shared server never sees their key.
- The agent calls
submit_signed_txwith the result and follows it to its receipt.
Or give each person a local stdio server with their own key, and use the shared server for reads, validation and docs only.
Rate limits and upgrades
- One IP for everyone. All chain calls from a shared server come from its IP, and the public gateway allows about 25 requests a second per IP. For a team, point
ASENTUM_RPC_URLat your own node (Run Your Own RPC). - Timeouts.
ASENTUM_REQUEST_TIMEOUT_MS(default 30000) limits each chain request andASENTUM_VM_TIMEOUT_MS(default 10000) each local contract run. Keep your proxy timeout above the longest wait you expect. - Upgrades. Install the new version (
sudo npm install -g @asentum/mcp@<version>) and restart the service. The server is stateless, so nothing is lost.
Troubleshooting
- Connection refused from another machine. Expected: it listens on
127.0.0.1only. Go through the proxy or a tunnel. - A client says the server failed with 405. It is trying the SSE transport or a GET stream. Switch it to streamable HTTP.
- 401 from the proxy. The header must be exactly
Authorization: Bearer <token>, with the same token the proxy has. - The write tools are missing. That is the HTTP default. See above before you change it.
- Requests hang at the proxy. Turn off response buffering (
proxy_buffering offin nginx) so streamed responses get through.
Read next