> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eco.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-hosting over HTTP

> Run one Sauce MCP server over HTTP, for a team's agents to connect to by URL.

`sauce mcp --http <port>` serves the MCP server over Streamable HTTP, for agents that connect to it by URL. This server:

* builds transactions but never signs them;
* keeps no wallet or Pot store;
* authenticates no one, so it has to sit behind your own authentication.

## Prerequisites

* The `sauce` command on the host (`npm install -g @eco-incorp/sauce`, Node.js 24 or newer).
* A reverse proxy that checks a token before it forwards a request, and serves TLS.

## Run it

```bash theme={null}
sauce mcp --http 3000
```

```text theme={null}
sauce mcp: Streamable HTTP at http://127.0.0.1:3000/mcp (stateless, no authentication)
```

`--http 0` picks a free port. To check that the server answers, list its tools:

```bash theme={null}
curl -s -X POST http://127.0.0.1:3000/mcp \
  -H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

The answer is one JSON object whose `result.tools` lists 18 tools, from `sauce_chains` to `sauce_install_skills`. The HTTP server has no wallet tools, no `sauce_sign` and no Pot store tools ([MCP tools](https://docs.eco.com/sauce-sdk/mcp/tools.md#which-tools-a-server-lists)).

## How it answers

* **Stateless, at `/mcp`.** Each POST is answered as JSON, with no session, so any replica can answer any request.

* **Other methods and paths are refused.** A GET returns 405 with `this server is stateless: POST each JSON-RPC request to /mcp`. Another path returns 404 with `not found: the MCP endpoint is /mcp`.

* **Loopback by default.** Bound to `127.0.0.1`, the server answers only a loopback `Host`, and only a loopback `Origin` when one is sent. Anything else gets 403:

  ```text theme={null}
  {"jsonrpc":"2.0","error":{"code":-32000,"message":"this server does not answer for that Host"},"id":null}
  ```

* **Off loopback, `--allowed-host` is required.** `--host <address>` binds another interface. The server refuses to start beyond loopback until `--allowed-host <name>` names the host it is served as. The flag can be repeated, and `--allowed-host '*'` answers any `Host`, for a proxy that checks it itself:

  ```text theme={null}
  sauce mcp: --host 0.0.0.0 is beyond loopback: name the host the server is served as with --allowed-host <name> (or --allowed-host '*' behind a proxy that checks the Host itself)
  ```

* **Bounded bodies.** A request body over `--max-body` bytes (default 2 MiB) is refused with 413 before it is read. Each tool also bounds its own inputs: at most 256 KiB of program text, 32 modules (512 KiB in all), 64 entries in any list or map such as `args` or `defines`, and 1 MiB of hex.

* **No files.** The `path` input is refused, and a program's relative imports come only from the call's `modules`.

## Put your authentication in front of it

The server checks no token, so your reverse proxy has to. A typical setup:

* Keep the server on loopback, and name the public host it is served as:

  ```bash theme={null}
  sauce mcp --http 3000 --allowed-host mcp.example.com --config rpc.json
  ```

* In the proxy, check a bearer token, terminate TLS, rate-limit, pass the `Host` header on, and forward `/mcp` to `http://127.0.0.1:3000`.

* If the proxy runs on another machine, bind with `--host 0.0.0.0`, and make sure the server's port is reachable by the proxy alone.

Rate limiting matters because every chain read spends your endpoints' quota. The public endpoints' limits are shared by every caller.

Each person then adds the server to their agent as a remote (HTTP) MCP server at `https://<host>/mcp`, with the header your proxy checks.

## Guard what callers send

Over HTTP, a caller's `rpcUrl` is chosen by whoever calls. The server reaches it only through a guard:

* It takes `https:` only: `rpcUrl must be an https URL: this server reaches a caller's endpoint over TLS only`.
* It resolves the host and refuses any address that is not on the public internet: loopback, private (RFC 1918 and IPv6 ULA), link-local, CGNAT, unspecified, multicast, reserved, or a cloud metadata address.
* It resolves and checks the host again at every connection, so a name that changes to a private address after the first check is refused when it is used.
* It follows no redirect, times out (20 seconds a request), and bounds what it sends (2 MiB) and what it reads back (16 MiB).

```text theme={null}
rpcUrl's host resolves to an address that is loopback, private, CGNAT or otherwise not on the public internet, which this server does not reach. Pass a public endpoint, or run the server yourself (`sauce mcp`) to reach a local or private one.
```

`--allow-private-rpc` admits `http:` URLs and private addresses, for a deployment inside a private network. Link-local and metadata addresses stay refused:

```text theme={null}
rpcUrl's host resolves to an address that is link-local or a cloud metadata address, which this server does not reach. Pass a public endpoint, or run the server yourself (`sauce mcp`) to reach a local or private one.
```

A server over stdio has no guard. It is your own process, so an endpoint it reaches, a local node included, is one you can reach already.

Every `rpcUrl` passes through your server, so a key in a URL a caller passes is shared with you. The server never returns, logs or quotes a URL.

## Endpoints and environment

`--config <file>` gives the server your own RPC endpoints:

```json theme={null}
{ "rpcUrls": { "1": "https://…", "8453": "https://…", "1399811149": "https://…" } }
```

For a chain the file does not name, the server uses a built-in public endpoint. With `--offline` it uses none, and reads a chain only through a caller's `rpcUrl`. [How a tool reaches a chain](https://docs.eco.com/sauce-sdk/mcp/tools.md#how-a-tool-reaches-a-chain) gives the order.

The HTTP server reads no environment variable. It never shows a URL from the file, or the file's path.

It holds no key and keeps no store. The wallet, password and store flags are refused at start:

```text theme={null}
sauce mcp: --wallets is for stdio: the HTTP server never signs, and serves no wallet tools
sauce mcp: --pots is for stdio: the HTTP server keeps nothing
```

## Next steps

* [Sign with a wallet's MCP server](https://docs.eco.com/sauce-sdk/mcp/external-wallets.md): each person signs what the shared server builds with their own wallet.
* [MCP tools](https://docs.eco.com/sauce-sdk/mcp/tools.md): every tool and its inputs.
* [`sauce mcp`](https://docs.eco.com/sauce-sdk/cli/mcp): every flag.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.