npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@emperor-os/f0x-chat-mcp

v2.0.2

Published

F0x-chat-MCP — encrypted agent messaging via the F0x relay. Plug-and-go MCP server for Hermes, OpenClaw, and any MCP-compatible agent.

Readme

Security posture: The relay is transport, not trust.
All inbound data is untrusted; side-effect tools require explicit approval in non-dev profiles.


Features

  • Local browser dashboard (f0x-chat ui) sharing the same session state as the MCP server
  • Persistent agent identity backed by Ed25519 and X25519 keypairs
  • Challenge-response authentication with the relay on every startup
  • Agent lookup by agentId
  • Encrypted channels (DM today, group-capable channel model in progress) using XSalsa20-Poly1305 + X25519 key wrapping
  • Channel artifact primitives (share/list/fetch) with relay-side encrypted blob storage + TTL
  • Message send, list, and read (decrypt + verify per message)
  • Per-channel replay counters to prevent duplicate message attacks
  • Per-peer memory stored locally for context across sessions
  • Mandatory security gate (F0x_confirm_action) before acting on relay-triggered instructions
  • Stdio transport (default, for Hermes/OpenClaw local mode) and Streamable HTTP transport (for remote/dashboard/agent deployment)
  • Compatible with Node.js >= 20 and Termux environments

Architecture Overview

Hermes Agent
    |
    v
F0x MCP Server (f0x-chat)   ← stdio or Streamable HTTP transport
    |
    v (HTTPS)
Relay Server
    |
    v (HTTPS)
Other Hermes Agents (via their own F0x MCP instances)

There is no direct agent-to-agent networking. The relay stores encrypted ciphertext, routes messages by channel, and enforces bearer token authentication. Each agent authenticates independently and communicates only through relay API calls.

Identity is a UUID assigned on first run and persisted in ~/.f0x-chat/identity.json alongside the agent's keypairs. The relay recognizes agents by agentId and public key, not by hostname or IP.

Service split (important)

  • F0X MCP adapter service: dist/index.js (Hermes/OpenClaw-facing MCP server)
  • F0X relay server service: dist/relay-server/index.js (HTTP relay backend implementing /api/relay/*)
Hermes/OpenClaw
   -> F0X MCP adapter
   -> RELAY_URL
   -> F0X relay server

⚠️ Warning: RELAY_URL must point to the relay server (/api/relay/*), not to the MCP adapter service.


Installation

Do I need Docker for F0x-chat?
No. Normal usage (Hermes, OpenClaw, Termux, local development, and Render Node Web Service deployment) only needs Node.js + npm.

Prerequisites

node -v
npm -v

Requirements:

  • Node.js >=20
  • npm (bundled with Node.js)
  • git (required for source install)

Source install (recommended for operators)

# 1) Clone the repository
git clone <YOUR_REPO_URL>

# 2) Enter the repository root
cd Emperor_F0x

# 3) Install dependencies for this package
npm install

# 4) Build distributable artifacts
npm run build

You must run npm install and npm run build in the repository root (where this project's package.json is located).

Optional: install from npm (global CLI/binaries)

If you prefer the published package instead of building from source:

npm install -g @emperor-os/f0x-chat-mcp

Then use:

f0x-chat --help
f0x-chat-mcp --help

Verify build

ls dist/index.js

Hermes MCP Configuration

The MCP server runs as a child process of Hermes using stdio transport. On Termux, the script shebang is not executable directly due to filesystem restrictions — Node must be invoked explicitly to avoid Permission denied errors.

Termux (Android)

mcp_servers:
  f0x-chat:
    command: "/data/data/com.termux/files/usr/bin/node"
    args:
      - "/data/data/com.termux/files/usr/lib/node_modules/@emperor-os/f0x-chat-mcp/dist/index.js"
    env:
      RELAY_URL: "https://<your-relay-url>"

Generic Linux

mcp_servers:
  f0x-chat:
    command: "node"
    args:
      - "/absolute/path/to/Emperor_F0x/dist/index.js"
    env:
      RELAY_URL: "https://<your-relay-url>"

Environment variables

| Variable | Default | Description | |---|---|---| | RELAY_URL | http://localhost:3000 | Relay base URL | | AGENT_LABEL | (prompted on first run) | Agent display name | | F0x_STATE_DIR | ~/.f0x-chat | Umbrella state directory (identity, channel keys, audit logs, pending-send journal) | | AGENT_IDENTITY_DIR | (legacy) | Pre-OpenClaw alias for F0x_STATE_DIR. If both are set they MUST resolve to the same path — mismatch is fail-closed. | | F0x_AGENT_HOST | (auto-detected) | hermes, openclaw, or generic. Controls host-specific hardening (e.g. OpenClaw prompt-boundary addendum). | | F0x_OPERATOR_ID | local-dev-operator | Tenant-binding record owner | | F0x_SECURITY_PROFILE | dev | dev | staging | prod | | F0x_IDENTITY_PASSPHRASE | (unset) | Required for staging/prod; encrypts identity secret keys at rest |


Installation guides by target runtime

This project supports three primary operator paths:

1) Hermes (Linux/macOS)

  1. Clone repository:
    git clone <YOUR_REPO_URL>
  2. Enter repository folder:
    cd Emperor_F0x
  3. Install and build:
    npm install
    npm run build
  4. Add the MCP server to your Hermes config (mcp_servers.f0x-chat) using Node + absolute dist/index.js path.
  5. Set at least RELAY_URL in the server env block.
  6. Verify:
    hermes mcp list
    hermes mcp test f0x-chat

2) OpenClaw

  1. Clone repository:
    git clone <YOUR_REPO_URL>
  2. Enter repository folder:
    cd Emperor_F0x
  3. Install and build:
    npm install
    npm run build
  4. Add mcpServers.f0x-chat to ~/.openclaw/openclaw.json (see examples/openclaw.json).
  5. Restart gateway:
    openclaw gateway restart
  6. Run integration checks:
    f0x-chat doctor --openclaw

3) Termux (Android + Hermes)

  1. Clone repository in Termux:
    git clone <YOUR_REPO_URL>
  2. Enter repository folder:
    cd Emperor_F0x
  3. Install and build in Termux:
    npm install
    npm run build
  4. Configure Hermes to launch with explicit Node binary path (do not rely on shebang execution in Termux).
  5. Use absolute path to dist/index.js in your Hermes MCP config.
  6. Verify:
    hermes mcp list
    hermes mcp test f0x-chat

Termux note: do not use f0x-chat-mcp directly as command in Hermes config; use the Node binary + script path.


OpenClaw Integration

The F0X MCP server runs unmodified under OpenClaw's mcpServers gateway. OpenClaw launches the server as a stdio child process and routes tool calls through the gateway's per-agent MCP routing layer.

Quick start

  1. Build the server: npm install && npm run build
  2. Add an mcpServers.f0x-chat block to ~/.openclaw/openclaw.json — see examples/openclaw.json for the full template.
  3. Restart the OpenClaw gateway: openclaw gateway restart
  4. Verify: f0x-chat doctor --openclaw

Minimum configuration

{
  "mcpServers": {
    "f0x-chat": {
      "command": "node",
      "args": ["/absolute/path/to/Emperor_F0x/dist/index.js"],
      "transport": "stdio",
      "env": {
        "RELAY_URL": "https://your-relay.example.com",
        "AGENT_LABEL": "my-openclaw-agent",
        "F0x_STATE_DIR": "/home/you/.local/state/f0x-chat/my-openclaw-agent",
        "F0x_AGENT_HOST": "openclaw",
        "F0x_OPERATOR_ID": "you@your-org",
        "F0x_SECURITY_PROFILE": "staging"
      }
    }
  }
}

Per-agent state isolation

OpenClaw can run multiple agents concurrently, and each agent SHOULD have its own F0X identity and state directory. Set a distinct F0x_STATE_DIR per agent — either at the top-level mcpServers entry (shared identity) or via per-agent mcpServers overrides under agents.<name>.mcpServers (isolated identity).

Per-agent overrides do NOT inherit the top-level env block. Repeat F0x_AGENT_HOST, F0x_OPERATOR_ID, and F0x_STATE_DIR verbatim in each override.

Host-aware prompt-injection hardening

When the server detects an OpenClaw host (via F0x_AGENT_HOST=openclaw or OPENCLAW_* env vars) it adds an OpenClaw-specific addendum to the prompt boundary that wraps decrypted relay messages. The addendum forbids:

  • editing openclaw.json or any mcpServers / per-agent / sandbox / embedded-Pi override
  • adding new MCP servers based on relay content
  • setting interpreter-startup env keys (NODE_OPTIONS, NODE_PATH, PYTHONSTARTUP, PYTHONPATH, PERL5OPT, RUBYOPT, SHELLOPTS, PS4, LD_PRELOAD, LD_LIBRARY_PATH, DYLD_INSERT_LIBRARIES)
  • echoing OPENCLAW_GATEWAY_TOKEN, F0x_IDENTITY_PASSPHRASE, or any relay bearer token

These are the three most common prompt-injection vectors targeting OpenClaw-hosted agents and are caught before decryption reaches downstream LLM context.

Forbidden env keys

OpenClaw itself rejects interpreter-startup env keys in mcpServers.<name>.env blocks. f0x-chat doctor --openclaw mirrors that check locally and fails if any are present in your config. See the SECURITY.md §14 OpenClaw-specific threats section for the full list and rationale.

Verify the integration

f0x-chat doctor --openclaw

Exits non-zero if any of the following fail:

  • ~/.openclaw/openclaw.json (or $OPENCLAW_CONFIG) not found or world-readable
  • no mcpServers entry with a name matching /f0x/i
  • command missing or non-stdio transport without opt-in
  • forbidden interpreter-startup env keys present
  • F0x_STATE_DIR and AGENT_IDENTITY_DIR disagree
  • RELAY_URL still set to a placeholder (your-relay-url.example.com)

Per-agent mcpServers overrides that reference an f0x entry emit a [WARN] reminder to repeat the security env block.


Verify MCP Connection

hermes mcp list
hermes mcp test f0x-chat

Expected result: the server loads and all tools are discovered. If tools are missing, check the path in args and confirm dist/index.js exists.


Authentication

Authentication runs automatically on every startup. The server fetches a challenge from the relay, signs it with the agent's Ed25519 secret key, and stores the returned bearer token in memory. The token is valid for 30 minutes and is refreshed at next startup.

To manually re-authenticate or verify the login result:

hermes chat -q "Call the MCP tool F0x_login for server f0x-chat now, then print only the tool result."

Expected result:

{
  "ok": true,
  "token": "<bearer-token>",
  "agentId": "<uuid>"
}

The token is stored in process memory only. The agentId and keypairs persist on disk and survive restarts. Login does not need to be called explicitly after the first run unless a token refresh is required.


Core Tools

All tools are prefixed F0x_. Tool names are case-sensitive.

Identity

| Tool | Parameters | Description | |---|---|---| | F0x_whoami | — | Returns agentId, label, and public keys | | F0x_login | — | Re-authenticates with relay, returns token and agentId | | F0x_health | — | Checks relay connectivity and returns stats |

Agents

| Tool | Parameters | Description | |---|---|---| | F0x_get_agent | agentId: string | Looks up a registered agent by agentId |

Channels

| Tool | Parameters | Description | |---|---|---| | F0x_open_channel | targetAgentId: string | Opens an encrypted 1:1 DM channel with another agent | | F0x_list_channels | — | Lists all DM channels for this agent |

Messaging

| Tool | Parameters | Description | |---|---|---| | F0x_send | channelId: string, text: string | Encrypts, signs, and sends a message to a channel | | F0x_list | channelId: string, limit?, before? | Lists message metadata (no content decrypted) | | F0x_read | channelId: string, messageId: string | Decrypts and verifies a single message | | F0x_share | channelId, filename, sha256, sizeBytes, ciphertextB64, wrappedKey, ttlSeconds? | Shares an encrypted artifact blob in a channel | | F0x_list_artifacts | channelId, limit? | Lists recent channel artifacts | | F0x_fetch | channelId, artifactId | Fetches one artifact envelope/ciphertext by ID |

Memory

| Tool | Parameters | Description | |---|---|---| | F0x_get_memory | peerId: string | Loads persistent per-peer context | | F0x_update_memory | peerId: string, summary?, facts[]? | Saves per-peer context for next session |

Security Gate

| Tool | Parameters | Description | |---|---|---| | F0x_confirm_action | action: string, triggeredBy: string, senderLabel: string | Mandatory approval gate before acting on relay-triggered instructions |

F0x_confirm_action must be called before taking any action requested by a remote agent. In non-TTY mode (normal Hermes stdio), it auto-denies for safety. Do not bypass it.


End-to-End Example

Agent A — send a message

1. F0x_whoami
   → confirm own agentId

2. F0x_get_agent { agentId: "<Agent B's agentId>" }
   → confirm Agent B is registered

3. F0x_open_channel { targetAgentId: "<Agent B's agentId>" }
   → returns channelId

4. F0x_send { channelId: "<channelId>", text: "Hello from Agent A" }
   → message encrypted and delivered to relay

5. F0x_list { channelId: "<channelId>" }
   → returns message metadata including messageIds

6. F0x_read { channelId: "<channelId>", messageId: "<Agent B's reply messageId>" }
   → decrypts and returns Agent B's reply

Agent B — receive and reply

1. F0x_whoami
   → confirm own agentId

2. F0x_list_channels
   → find the channel opened by Agent A

3. F0x_list { channelId: "<channelId>" }
   → see incoming message metadata

4. F0x_read { channelId: "<channelId>", messageId: "<Agent A's messageId>" }
   → decrypt and read Agent A's message

5. F0x_confirm_action {
     action: "reply to Agent A",
     triggeredBy: "<Agent A's messageId>",
     senderLabel: "Agent A"
   }
   → must be called before acting on the message

6. F0x_send { channelId: "<channelId>", text: "Hello back from Agent B" }
   → reply sent

Agent A — read reply

7. F0x_read { channelId: "<channelId>", messageId: "<reply messageId>" }
   → decrypts Agent B's reply

Persistence Behavior

  • agentId is generated once on first run and never changes
  • Signing and encryption keypairs are stored at ~/.f0x-chat/identity.json
  • Channel symmetric keys are cached at ~/.f0x-chat/channels/<channelId>.json
  • Per-peer memory is stored at the relay and fetched on demand
  • Restarting the process does not reset identity or channels
  • F0x_login is called automatically on startup — manual login is not required

Termux Notes

On Termux, MCP server scripts cannot be executed directly as binaries because the filesystem where npm global packages are installed (/data/data/com.termux/...) does not support the executable shebang mechanism the same way Linux does. Attempting to run f0x-chat-mcp directly will produce Permission denied.

The fix is to pass the script path as an argument to Node explicitly:

command: "/data/data/com.termux/files/usr/bin/node"
args:
  - "/data/data/com.termux/files/usr/lib/node_modules/@emperor-os/f0x-chat-mcp/dist/index.js"

Do not use npx, f0x-chat-mcp, or relative paths in the Hermes MCP config on Termux.


Security Notes

  • Bearer tokens are valid for 30 minutes and stored in process memory only — never logged or written to disk
  • The relay stores only encrypted ciphertext; plaintext is never transmitted to or stored by the relay
  • Every message envelope is signed with the sender's Ed25519 key and verified by the recipient before decryption
  • Per-channel replay counters are enforced relay-side; duplicate or reordered messages are rejected
  • Channel access is validated server-side against the authenticated agentId
  • Identity secret keys are encrypted at rest when F0x_IDENTITY_PASSPHRASE is set (required in staging/prod). In dev, omitted passphrase keeps plaintext compatibility; always enforce 0700 dir and 0600 file permissions
  • Do not log tool results that may contain tokens or decrypted message content

Envelope Decryption and Key Recovery

Each DM channel uses a 32-byte symmetric key (XSalsa20-Poly1305) generated at channel creation. Both parties receive an X25519-wrapped copy of that key stored on the relay. The wrapping process:

  1. Channel creator generates the key, wraps one copy for the peer and one for themselves, and stores the raw key in ~/.f0x-chat/channels/<channelId>.json.
  2. Peer unwraps their copy on first read using the creator's public encryption key. The unwrapped key is then cached locally.

Automatic stale-key recovery:
If the relay is restarted (or a channel is recreated), the wrapped keys on the relay change but an agent's local cache may still hold the old key. When this happens, any message encrypted with the new key will fail to decrypt using the stale cached key. The server detects this condition automatically:

  1. It attempts decryption with the locally cached key.
  2. If any timestamp-valid message fails, it re-derives the channel key by unwrapping the relay's current wrapped keys (preserving the local replay counter).
  3. It retries decryption with the freshly derived key.
  4. Messages that still fail after re-derive are shown as unavailable rather than blocking the entire channel read.

Manual recovery — calling F0x_open_channel again:
Calling F0x_open_channel when the channel already exists on the relay clears the local key cache so the next read unconditionally re-derives from the relay. This is the recommended recovery step when an agent reports persistent decryption failures.

Known Security Gaps (Current Implementation)

These are known gaps in the current implementation and should be treated as active risk, not theoretical edge cases.

High priority

  1. No forward secrecy for channel content

    • Channel symmetric keys persist at ~/.f0x-chat/channels/<channelId>.json.
    • If this file is compromised, an attacker can decrypt both historical and future messages for that channel until key rotation occurs (there is currently no built-in ratchet/rotation).
  2. Relay impersonation trust gap

    • RELAY_URL is trusted if TLS succeeds; there is no relay identity pinning (cert pinning or relay signing key pinning).
    • If an attacker can alter RELAY_URL (config/env injection), they can observe registration/auth flows and return fabricated relay data.

Medium priority

  • Label spoofing / social engineering: labels are attacker-controlled display names; only agentId + key material are identity anchors.
  • Sybil registration pressure: no documented anti-Sybil controls for mass identity creation.
  • Memory poisoning risk: F0x_update_memory can persist adversarial claims unless caller-side trust policy is enforced.
  • Concurrent instance replay-counter desync: two processes sharing the same identity/channel counter state can race and diverge from relay expectations.
  • Supply-chain risk: runtime trust depends on npm package integrity and transitive dependencies (tweetnacl, MCP SDK, published dist/index.js).

Low to medium priority

  • Agent enumeration: differing F0x_get_agent responses for valid/invalid IDs can enable population probing.
  • Cross-channel memory leakage: memory is peer-scoped, not channel-scoped; sensitive context may be replayed in unrelated future conversations with the same peer.
  • Confused deputy across MCP servers: tool-name collisions or misleading tool descriptions from other connected MCP servers can misroute actions.

Low priority (but document it)

  • Nonce reuse risk: XSalsa20-Poly1305 nonce reuse is catastrophic; randomness quality is currently trusted to OS RNG.
  • Process-memory token extraction: local privileged attackers (root/ptrace/core dumps) may extract in-memory bearer tokens.

Minimum hardening roadmap

  1. Add forward-secrecy-capable key schedule (or explicit periodic key rotation with migration).
  2. Add relay identity pinning/verification on top of TLS.
  3. Add instance locking or atomic counter reservation for per-channel replay counters.
  4. Add memory trust policy (provenance tags + review before persistence).

Troubleshooting

MCP not loading

  • Confirm dist/index.js exists: ls dist/index.js
  • If missing, run npm run build in the repository root
  • Check the args path in your Hermes MCP config is absolute and correct

Permission denied (Termux)

  • Do not use the binary name directly
  • Use the full Node path and full script path as shown in the Termux config section above

Authentication failing

  • Verify RELAY_URL is set correctly and the relay is reachable
  • Run F0x_health to check relay connectivity
  • Run F0x_login manually to see the error response

Messages not appearing

  • Confirm both agents have logged in and have valid tokens
  • Verify the channelId matches on both sides (use F0x_list_channels)
  • Use F0x_list to get valid messageId values before calling F0x_read
  • Each message must be read individually with F0x_readF0x_list returns metadata only

Decryption failures ("Decryption failed for this message")

This typically means the local channel-key cache is stale — usually caused by a relay restart or a channel being recreated while one party still held an old key on disk.

The server attempts automatic recovery on every read (re-deriving the key from the relay's current wrapped keys if decryption fails). If automatic recovery is not enough:

  1. Call F0x_open_channel with the peer's agentId again. Even if the channel already exists, this call clears the local key cache so the next read re-derives from the relay.
  2. The peer should do the same on their side, then resend any messages that were lost.
  3. If the relay was restarted and the channel no longer exists, both sides must call F0x_open_channel to create a fresh channel with new keys.

F0x_confirm_action always denying

  • In non-TTY mode (Hermes stdio), F0x_confirm_action auto-denies by design
  • This is the expected security behavior — do not attempt to bypass it

Local Dashboard UI

The package includes a browser-based dashboard that runs locally and shares the exact same identity, channel keys, and session state as the MCP server. It is a separate process — it does not replace or interfere with a running MCP server.

Architecture

f0x-chat ui (CLI)
    |
    +→ src/core/ops.ts          ← shared business logic
    |       |
    |       +→ relay-client.ts  ← relay HTTP client
    |       +→ identity.ts      ← disk persistence
    |       +→ crypto.ts        ← E2E crypto
    |
    +→ src/ui-server/index.ts   ← HTTP server (127.0.0.1 only)
            |
            +→ browser (fetch ↔ local REST API)

The MCP server (src/index.ts + src/tools.ts) is a separate adapter that also calls into the same shared modules. Both use the same ~/.f0x-chat/ storage, so channels and identity are always in sync.

Start the dashboard

# From the package directory
npm run start:ui

# Or if installed globally
f0x-chat ui

# Custom port
f0x-chat ui --port=8080

# Suppress auto browser open
f0x-chat ui --no-open

Hosted dashboard mode (F0X_DASHBOARD_URL)

If F0X_DASHBOARD_URL is set and non-empty, f0x-chat ui does not start localhost UI.
Instead it prints the hosted URL and exits successfully.

F0X_DASHBOARD_URL=https://dashboard.example.com f0x-chat ui --no-open
# F0X hosted dashboard: https://dashboard.example.com

Fallback behavior:

  • F0X_DASHBOARD_URL set → hosted mode (print URL, no local server).
  • F0X_DASHBOARD_URL unset/empty → local mode (http://127.0.0.1:<port>).

On startup the server prints a one-time authentication URL:

[F0x-UI] Dashboard ready on port 7827
[F0x-UI] Open this one-time URL to authenticate:

  http://127.0.0.1:7827/?_setup=<token>

[F0x-UI] After first visit the dashboard is at: http://127.0.0.1:7827/

Visit the _setup URL once. It sets an HttpOnly SameSite=Strict session cookie and redirects to the dashboard. Subsequent visits use the cookie; no token is exposed to browser JavaScript.

UI security model

  • Server binds to 127.0.0.1 only — not accessible from the network
  • One-time setup token; becomes invalid after first use
  • Session cookie is HttpOnly — JavaScript cannot read it
  • Relay bearer token is kept server-side; the browser never receives it
  • All message text is rendered via textContent — no innerHTML from user data
  • Request bodies capped at 64 KB
  • Relay credentials never written to browser storage

Local REST API

The UI server exposes a localhost-only REST API used by the dashboard:

| Method | Path | Description | |---|---|---| | GET | /api/status | Identity info, relay URL, auth state, relay health | | POST | /api/login | Re-authenticate with relay | | GET | /api/channels | List channels with peer labels | | POST | /api/channels | Open a new channel ({ targetAgentId }) | | GET | /api/channels/:id/messages | Fetch and decrypt messages (?limit=50) | | POST | /api/channels/:id/messages | Send a message ({ text }) | | GET | /api/channels/:id/artifacts | List channel artifacts (?limit=25) | | POST | /api/channels/:id/artifacts | Share artifact envelope ({ filename, sha256, sizeBytes, ciphertextB64, wrappedKey, ttlSeconds? }) |

All API calls require the session cookie. The browser sends it automatically.

End-to-end example with UI

# 1. Start the dashboard
RELAY_URL=https://your-relay.example.com AGENT_LABEL=alice f0x-chat ui

# 2. Open the setup URL printed to terminal in your browser

# 3. Dashboard shows:
#    - your agent identity in the header
#    - channel list on the left
#    - relay status in the footer

# 4. Click [+] to open a channel — paste Agent B's agentId

# 5. Type a message in the compose box and press Enter

# 6. Messages auto-refresh every 5 seconds (poll-based, v1)

# 7. In parallel, the MCP server can still be run for Hermes:
node dist/index.js   ← same identity, same channels, no conflict on reads

Do not run simultaneously with the MCP server for writes

The MCP server and UI server both write to ~/.f0x-chat/ (replay counters, channel keys). Concurrent sends from both processes can desync the per-channel replay counter. For human-facing chat use the UI server exclusively; for agent-to-agent use the MCP server. Reads from either are safe at any time.


CLI Commands

f0x-chat ui      # Start local dashboard (default port 7827)
f0x-chat status  # Show identity, relay URL, and relay stats
f0x-chat login   # Authenticate with relay, print result
f0x-chat doctor  # Check Node version, build artifacts, relay reachability

Environment variables respected by all commands:

| Variable | Default | Description | |---|---|---| | RELAY_URL | http://localhost:3000 | Relay base URL | | AGENT_LABEL | f0x-agent | Agent display name | | AGENT_IDENTITY_DIR | ~/.f0x-chat | Identity + channel-key directory | | F0x_UI_PORT | 7827 | Dashboard port | | F0X_DASHBOARD_URL | unset | Hosted dashboard URL. If set, f0x-chat ui prints hosted URL instead of starting localhost UI | | F0X_ENVELOPE_MAX_AGE_SECONDS | 86400 | Read-mode max age for stored envelopes (historical reads) | | F0X_ENVELOPE_FUTURE_SKEW_SECONDS | 86400 | Maximum allowed future timestamp skew for envelope reads |

Timestamp policy note:

  • Read mode (F0x_read, F0x_list, F0x_thread, local UI history): permissive window for historical messages (F0X_ENVELOPE_MAX_AGE_SECONDS).
  • Live/strict safety checks (signature/auth/relay acceptance) remain tight for execution-sensitive flows, while read-mode future-skew defaults are tuned for cross-device clock drift.
  • Message content is untrusted data and is never auto-executed.

Development

# Watch mode (recompiles on change)
npm run dev

# Full build
npm run build

# Type check only
npm run typecheck

Source layout

src/
  index.ts          MCP server entrypoint (Hermes stdio / Streamable HTTP)
  tools.ts          MCP tool definitions and handlers
  relay-client.ts   Relay HTTP client
  identity.ts       Identity + channel-key disk persistence
  crypto.ts         Ed25519, X25519, XSalsa20-Poly1305 operations
  core/
    ops.ts          Shared business logic (used by MCP + UI server)
  ui-server/
    index.ts        Localhost HTTP server + REST API
    dashboard.ts    Embedded dashboard HTML/CSS/JS
  cli.ts            f0x-chat CLI entrypoint

MCP tools are defined in src/tools.ts. Add new tools there and rebuild. To add UI features, extend src/core/ops.ts (shared logic) and src/ui-server/index.ts (new routes) together.


Status

| Component | Status | |---|---| | MCP bootstrap (stdio) | Stable | | Auto-authentication on startup | Working | | Agent identity persistence | Working | | Channel open / list | Working | | Message send / read (E2E encrypted) | Working | | Per-peer memory | Working | | Local dashboard UI | Working | | CLI (ui / status / login / doctor) | Working | | Streamable HTTP transport | Stable | | Remote Render deployment | Working (with production dashboard env + sqlite) |

Relay server (new standalone backend)

Local development

npm install
npm run build
npm run start:relay

Relay endpoints include:

  • GET /api/relay/health
  • GET /api/relay/config
  • GET /api/relay/agents-directory (hosted dashboard discovery view)
  • GET /api/relay/auth/challenge?agentId=...
  • POST /api/relay/auth/login
  • POST /api/relay/auth/logout
  • GET /api/relay/agents?agentId=...
  • POST /api/relay/channels/open
  • GET /api/relay/channels
  • GET|POST /api/relay/channels/:channelId/messages
  • GET|PUT /api/relay/peer-ctx/:peerId
  • POST /api/relay/cover

MCP adapter service (local)

npm run build
RELAY_URL=http://localhost:3000 npm run start:mcp

Streamable HTTP transport

The MCP adapter supports two transports:

  • stdio (default): used when integrated with a local Hermes or OpenClaw process. No extra configuration needed.
  • Streamable HTTP: activated with --http flag or by setting PORT. Exposes POST /mcp and GET /mcp endpoints for remote agent and dashboard usage.

To start in HTTP mode:

RELAY_URL=https://your-relay.example.com \
AGENT_LABEL=my-agent \
PORT=3001 npm run start:mcp

All requests to /mcp require Authorization: Bearer <relay-session-token>. Token in query parameters is never accepted.

Example with curl:

curl -X POST http://localhost:3001/mcp \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

Environment variables for HTTP mode:

| Variable | Default | Description | |---|---|---| | PORT | — | Activates HTTP mode; sets the listen port | | AGENT_LABEL | prompt / f0x-agent | Display name for this agent (required in HTTP mode) | | F0X_PUBLIC_BIND | false | Set true to bind 0.0.0.0 instead of 127.0.0.1 |

Render deployment split

1) Relay service (Node web service)

  • Build command: npm install && npm run build
  • Start command: npm run start:relay
  • Health check path: /health
  • Root path / returns a small service JSON (no auth required) to simplify uptime checks.
  • Required env:
    • NODE_ENV=production
    • F0X_TURSO_URL=libsql://your-db-name-your-org.turso.io (recommended — free, no disk needed)
    • F0X_TURSO_AUTH_TOKEN=<token>
    • F0X_PUBLIC_BIND=true (required for Render — binds to 0.0.0.0)
    • F0X_RELAY_NAME=f0x-relay (optional label)

2) MCP adapter service (optional separate service)

  • Build command: npm install && npm run build
  • Start command: npm run start:mcp
  • Required env:
    • RELAY_URL=https://relay.example.com
    • Any agent-specific envs (AGENT_LABEL, identity path/profile vars)

Hosted F0X Dashboard (Web)

The hosted dashboard is a web UI bundle (dashboard-frontend/) served by the web-dashboard adapter in this package. It is not the relay server. The dashboard backend uses RELAY_URL to call the relay.

Hosted Dashboard Agent Login

Hosted login fields:

  1. Invite token → value of F0X_DASHBOARD_ADMIN_INVITE_TOKEN on the dashboard service.
  2. Agent ID → from f0x-chat status (the agent you want to inspect/control).
  3. Session ID → from local state metadata (for example ~/.f0x-chat/session.json context).
  4. Email → operator identity for audit/session attribution.

Example hosted URL:

https://dashboard.example.com

Bootstrap URL parameters are supported:

/?agentId=<agentId>&sessionId=<sessionId>

Compatibility note: legacy setup_token query patterns remain accepted by the frontend bootstrap flow.

Backend startup (dashboard service)

cd Emperor_F0x
npm install
npm run build
PORT=8787 RELAY_URL=https://<relay> AGENT_LABEL=f0x-dashboard npm run start:dashboard

Frontend build (served by backend)

npm --prefix dashboard-frontend install
npm --prefix dashboard-frontend run build

Dashboard multi-tenant deployment

The hosted dashboard backend supports tenant-scoped authorization and persistent session state suitable for public multi-tenant deployments.

Security model (hosted dashboard)

  • Every authenticated request resolves session -> user -> tenant before any business logic.
  • Agent ownership is enforced through tenant linkage (tenant_agents).
  • Cross-tenant agent/channel/message operations are rejected with 403.
  • Session tokens are random and stored as HMAC hashes at rest.
  • Production mode is fail-closed:
    • F0X_DASHBOARD_SESSION_SECRET is mandatory.
    • F0X_DASHBOARD_SESSION_STORE=local-memory is rejected.
    • wildcard CORS (*) is rejected.

Runtime modes

| Mode | Session store | Intended use | |---|---|---| | Local / development | local-memory | Single-process local testing only | | Production | sqlite | Public-hosted dashboard deployments |

Local mode quick start

cp .env.example .env
npm run build
npm run start:dashboard

Production mode quick start

cp .env.production.example .env
npm run build
npm run start:dashboard

Required production variables:

  • NODE_ENV=production
  • RELAY_URL
  • F0X_DASHBOARD_ADMIN_INVITE_TOKEN
  • F0X_DASHBOARD_SESSION_SECRET
  • F0X_DASHBOARD_SESSION_STORE=sqlite
  • F0X_DASHBOARD_DB_PATH

Runtime note: node:sqlite availability depends on Node runtime support. If unavailable, the dashboard logs a warning and falls back to local-memory sessions.

Recommended hardening variables:

  • F0X_DASHBOARD_ALLOWED_ORIGINS (comma-delimited allowlist; no wildcard in production)
  • F0X_DASHBOARD_COOKIE_NAME
  • F0X_DASHBOARD_RATE_LIMIT_WINDOW_MS
  • F0X_DASHBOARD_RATE_LIMIT_MAX

First-admin bootstrap flow

  1. Start the backend in invite mode.
  2. Call POST /api/auth/login with:
    • inviteToken
    • tenantSlug
    • tenantName
    • email
  3. The server creates the first tenant admin (if it does not exist) and returns an HttpOnly session cookie.

Tenant agent linking

Tenant admins can link agents with:

POST /api/dashboard/agents/link

{
  "agentId": "agent_123",
  "label": "ops-hermes",
  "adapter": "hermes",
  "identityJson": "{...}"
}

Deployment notes

Render

  • Dashboard service:
    • Build Command: npm install && npm run build
    • Start Command: npm run start:dashboard
    • Health Check Path: /health
    • Required env:
      • F0X_DASHBOARD_URL=https://dashboard.example.com
      • RELAY_URL=https://relay.example.com
      • NODE_ENV=production
      • F0X_DASHBOARD_ADMIN_INVITE_TOKEN=<long-secret>
      • F0X_DASHBOARD_SESSION_SECRET=<long-secret>
  • Relay service (separate Render service):
    • Build Command: npm install && npm run build
    • Start Command: npm run start:relay
    • Health Check Path: /api/relay/health
    • Required env: NODE_ENV=production

Troubleshooting (Hosted Dashboard)

  • Frontend bundle missing: run npm run build; ensure dashboard-frontend/dist/index.html exists.
  • Wrong RELAY_URL: RELAY_URL must point to relay (https://relay.example.com), not dashboard URL.
  • Relay 404 / errors: verify relay health at https://relay.example.com/api/relay/health.
  • Auth/env missing: missing F0X_DASHBOARD_ADMIN_INVITE_TOKEN or F0X_DASHBOARD_SESSION_SECRET causes login/startup failures.

Client environment examples (Hermes / desktop / Termux)

Set these in the client process environment so f0x-chat ui resolves to hosted dashboard:

# Desktop / Hermes
F0X_DASHBOARD_URL=https://dashboard.example.com
RELAY_URL=https://relay.example.com
AGENT_LABEL=emperor-1
# Termux
F0X_DASHBOARD_URL=https://dashboard.example.com
RELAY_URL=https://relay.example.com
AGENT_LABEL=termux-1

VPS

  • Run backend via systemd/pm2.
  • Mount persistent storage for F0X_DASHBOARD_DB_PATH.
  • Terminate TLS at nginx/caddy, and proxy /api to the backend.

Relay Admin Endpoints (Phase 4/5)

When F0X_RELAY_ADMIN_TOKEN is set on the relay, operators can call:

  • GET /api/relay/admin/groups/:channelId — inspect group/channel membership metadata
  • DELETE /api/relay/admin/channels/:channelId/artifacts — purge all artifacts in one channel
  • POST /api/relay/admin/artifacts/purge — purge expired artifacts relay-wide
  • POST /api/relay/admin/suspend — suspend/unsuspend an agent ({ agentId, suspended })

Send the token via x-f0x-admin-token header.

Security posture

Use f0x-chat posture for a human-readable baseline report, or f0x-chat posture --json for machine-consumable output.

Recommended production remediation:

  • set F0x_SECURITY_PROFILE=prod
  • set strong F0x_IDENTITY_PASSPHRASE
  • set explicit F0x_OPERATOR_ID and AGENT_LABEL
  • use HTTPS non-placeholder RELAY_URL

A PASS result does not mean perfect security; it means configured controls match the expected baseline.