An implementation-gap memo on the headless deployment path the official docs don't describe.
Why this memo exists
I'm building an AI agent for the Post Fiat Task Node — a conversational interface for validator operational intelligence, distinct from existing agents in the public directory. The official package @postfiatorg/pft-chatbot-mcp is the canonical toolkit for this. Its README is the only deployment reference anyone gets.
That README is written for a developer on a laptop with Cursor or Claude Desktop installed, an MCP-compatible client to orchestrate tool calls, and a wallet seed living in an mcp.json configuration file. That's a fine starting point if your goal is to experiment locally.
It is structurally incompatible with what an always-on agent actually requires.
The Post Fiat agent directory enforces a 15-minute liveness ping. Agents that fail to ping for 20 minutes get hidden from search. A laptop that sleeps, loses wifi, closes its lid, or simply gets shut down at night cannot meet that requirement. Whatever infrastructure the currently-visible agents run on, it has to survive operator absence — closed lids, lost connections, overnight idle time. The implication is a deployment shape that is fundamentally not a personal developer machine.
That deployment path is unwritten.
This memo documents what I learned filling that gap, working from the official MCP docs and a hardened VPS instead of a developer machine. The friction I hit isn't theoretical — every section below is a trap I walked into and worked through.
The umbrella thesis
The MCP README's assumptions cascade. Laptop implies no firewall. Cursor or Claude Desktop implies a client orchestrates every tool call. mcp.json holds the seed implies the operator never invokes the MCP standalone. None of those assumptions survive contact with a production VPS deployment.
What the docs leave implicit:
- System hardening before installing anything
- Firewall configuration with correct ordering
- Kernel patching discipline
- How to invoke MCP tools without an MCP client
- How to verify a wallet works against the MCP before funding it
- How the MCP's output relates to the actual PFTL chain state
- How to send transactions directly when the MCP doesn't expose the tool you need
Each one is something the next headless operator will encounter. Each one has a trap.
Trap 1: Headless wallet creation has no documented path
The MCP exposes create_wallet as a tool. The README explains how to call it from an MCP-compatible client — Cursor, Claude Desktop, anything speaking MCP's stdio JSON-RPC protocol. The blessed path: install an MCP client, point it at the package, ask the AI to create a wallet, save the seed.
A headless operator on a VPS has no MCP client.
Two ways forward: install one just to invoke a single tool, or bypass create_wallet and generate the wallet directly using the same underlying library (xrpl) the MCP uses internally. I chose the second. The cryptography is identical — Ed25519 keypair with PFTL-compatible address encoding, functionally equivalent to what the MCP would have produced.
The bypass is safe only if verified. Generating a wallet costs nothing. Funding the wrong one — or one the MCP can't actually read — wastes the activation deposit and any further PFT until the operator notices. The verification step is the entire safety net for the bypass, and the docs don't describe it.
Trap 2: stdio JSON-RPC is the testing primitive nobody documents
MCP servers speak JSON-RPC over standard input and output. Every tool call from a client is a JSON message piped in; every response comes back out the same way. A headless operator can do exactly what a client does — pipe JSON in, read JSON out, parse the response. No client needed.
The three-message handshake required to call any tool:
initialize— declare protocol version and client capabilitiesnotifications/initialized— confirm the handshake completedtools/call— invoke the actual tool
Pipe all three sequentially into npx @postfiatorg/pft-chatbot-mcp and the server returns the tool's output as the third response. This is the universal primitive for testing any headless MCP work — wallet verification, chain reachability, registration dry-runs, all of it.
The official MCP docs describe none of this. They describe what tools exist and what arguments they take, but assume a client handles the wire protocol. Operators without a client never see the wire protocol — they just see "the MCP isn't responding."
Trap 3: The trust line that isn't
After funding the bot wallet with PFT from my main account, I ran the MCP's get_wallet_info again expecting confirmation. The response showed the correct native PFT balance — but with pft_trust_line: { active: false }.
On XRPL, that output means the wallet hasn't established a trust line to the token issuer and cannot hold the token. Standard remediation: send a TrustSet transaction to the issuer's address.
I went down that path. Wrote a script to issue the TrustSet, tried to identify the PFT issuer's address, hit a wall: there isn't one.
PFT is not an issued currency on PFTL. PFT is PFTL's native currency, the same way XRP is XRPL's. Transaction fees are paid in PFT. Balance changes show up directly on the recipient's AccountRoot ledger object with no issuer field anywhere.
The pft_trust_line field in get_wallet_info is legacy naming from before PFT migrated to its own standalone chain. When PFT existed as an XRPL-issued currency, that field meant something. After migration, it's a vestigial output that confuses new operators into chasing a problem that doesn't exist.
The fix is to ignore the field. The funded wallet is functionally activated as soon as the native balance is non-zero. No TrustSet is needed.
Worth knowing before you spend 90 minutes writing TrustSet code that will never succeed.
Trap 4: PFTL requires explicit NetworkID
Before realizing the TrustSet was unnecessary, I tried submitting one anyway. The transaction failed with telREQUIRES_NETWORK_ID.
PFTL uses Network ID 2025. The xrpl.js library — the same library the MCP uses internally — does not automatically populate the NetworkID field on transactions for non-mainnet chains. Its autofill function fills in everything else (sequence, fee, last-ledger-sequence) but leaves NetworkID blank.
Any transaction script built directly against PFTL needs to include NetworkID: 2025 explicitly in the transaction object. The MCP handles this internally for tools it exposes, so MCP-mediated transactions work fine. The moment an operator goes off-MCP to interact with PFTL directly — to set a trust line, send a payment outside the bot's command flow, query chain state with custom logic — the missing NetworkID becomes a hard wall.
This is invisible from the MCP docs because the MCP never exposes the problem. It's visible the instant you build anything that uses the same underlying library directly.
Trap 5: Firewall ordering can lock you out
A headless VPS needs a firewall. The MCP docs don't mention this because laptops don't.
UFW's three steps — set defaults, allow services, enable — are independent commands, and ufw enable becomes the gatekeeper for all inbound connections the moment it runs. If SSH wasn't explicitly allowed first, the active session terminates and reconnection is impossible without the cloud provider's web console.
Correct order: set defaults, allow OpenSSH, then enable. Standard sysadmin knowledge for anyone who's built a server before. Invisible to a developer whose mental model is laptop-shaped.
Trap 6: Kernel patching and reboot ordering
Running apt upgrade -y on a fresh Ubuntu 24.04 droplet installed a new kernel alongside the existing one. The system reported the upgrade succeeded. It also reported the running kernel was still the old one — needrestart flagged this explicitly.
The trap: an operator who installs the MCP, Node.js, and the rest of the agent stack without rebooting runs everything against the unpatched kernel. The new kernel is on disk but not in memory. Reboot has to happen before further installs to land the stack on a known-clean baseline. The MCP docs don't mention kernel patching at all because they assume your laptop is already up to date.
What the docs assume
Every trap above traces back to the same root: the MCP docs are written for a workflow where the operator has already set up the environment elsewhere. A laptop with kernel updates handled automatically. A development machine with Node installed correctly. A network with no firewall. A wallet flow mediated entirely by an MCP client that abstracts the wire protocol.
A headless VPS operator has none of that. They are setting up the environment as part of the agent build, not before it. The implicit prerequisites in the docs are explicit responsibilities for them.
The result is a knowledge moat that benefits no one. The agent directory is the network's primary surface for operator-built utility. Lowering the threshold for what it takes to ship something there grows the directory faster than gating it behind unwritten knowledge.
Closing observation
This memo isn't a tutorial. It's a map of where the docs and reality diverge for one specific deployment shape — headless, always-on, VPS-hosted, pre-registration.
If you're building an agent for the Post Fiat Task Node and the official docs work for you, ignore everything I wrote. If you're trying to deploy without a laptop in the loop, the six traps above will save you somewhere between half a day and three days depending on which ones you hit and in what order.
The structural fix is for the official docs to add a deployment-shape selector: laptop versus VPS, with each path getting its own assumption set. Until that happens, this memo and others like it are what closes the gap.