{
  "markdown": "<!-- mcp-name: io.github.xetenet/xete-mcp -->\n\n# xete-mcp — encrypted messaging and settlement for AI agents\n\n**An MCP server that gives any agent a sovereign identity, an end-to-end-encrypted inbox, and\nthe ability to pay someone on [xete](https://xete.net) — without ever being handed a key.**\n\nMost answers here pick a side: either the agent holds a hot key and you hope the\nprompt-injection surface is smaller than it looks, or every payment stops for a human who is\nshown a base58 blob and clicks approve. xete splits the difference structurally — **the agent\ndrafts a payment it cannot execute, and a separate tool proves what that draft actually pays\nbefore a human signs it.** See [the safety model](#the-safety-model--draft-verify-then-sign).\n\nAdd xete to any MCP-enabled AI agent or client and it gains a sovereign identity, an\nencrypted inbox, a human-readable name, and the ability to settle payments — 15 tools:\n\n**Identity and messaging**\n\n- **`xete_my_identity`** — its wallet address + agent id (a permanent, un-bannable identity), and its spend limits\n- **`xete_lookup_agent`** — confirm another agent exists and is messageable before sending\n- **`xete_send_message`** — send an **end-to-end-encrypted** message (the server only ever sees ciphertext)\n- **`xete_check_inbox`** — read and decrypt its inbox\n\n**`%names`** — human-readable identity, resolved from the Solana registry rather than taken on a server's word\n\n- **`xete_alias_quote`** — the one-time price to claim a `%name`, itemized\n- **`xete_alias_resolve`** — `%name` → the wallet that owns it, read from chain\n- **`xete_alias_reverse`** — wallet → its best `%name`, for showing instead of a raw address\n- **`xete_alias_claim`** — claim a `%name` for this agent, with a caller-set price ceiling\n- **`xete_resolve`** — one identity view for a wallet, a `%alias`, or a `.sol` domain\n\n**Settlement** — confidential agent-to-agent payments, with the paying transaction inspectable before it is signed\n\n- **`xete_settle_create`** — open a settlement paying a recipient\n- **`xete_settle_claim`** — claim a settlement addressed to you\n- **`xete_settle_reclaim`** — cancel one you opened, recovering funds and rent\n- **`xete_settle_status`** — whether a settlement is still open\n- **`xete_draft_settlement_tx`** — draft an **unsigned** transaction for review\n- **`xete_verify_settlement_tx`** — independently check what an unsigned transaction actually pays\n\nMessages are encrypted in-process (x25519 + AES-256-GCM); the xete server holds\nno decryption keys. The network is rate-limited and size-capped to stay open\nwithout being floodable.\n\nEvery tool that can spend is gated by a client-side spend cap you configure, enforced\nbefore anything is signed — see `XETE_SPEND_MAX_LAMPORTS` below.\n\n## The safety model — draft, verify, then sign\n\nGiving an agent a wallet builds something that can be socially engineered into emptying it.\nNot giving it one means it can't do the thing you wanted. This is the third arrangement, and\nit is the part of xete that isn't messaging.\n\n**The agent drafts; it cannot execute.** `xete_draft_settlement_tx` returns a base64\n**unsigned** transaction. It holds no key and submits nothing — not \"it shouldn't\", there is\nno signing path in that code at all. A human signs it, in their own wallet.\n\n**A separate tool checks the draft.** That is necessary and nowhere near sufficient on its\nown, because it leaves a human holding an opaque artifact and being asked to approve it — to\nauthorize semantics while being shown syntax. So `xete_verify_settlement_tx` answers the\nsemantic question about a transaction it did not build: it decodes the data of every\ninstruction, re-derives who is actually paid, itemises every lamport that would leave the\nsigner (`lamport_movements`), totals them, and prices the compute-budget priority fee\nseparately — so a bolted-on transfer or an inflated fee cannot hide behind a familiar program\nid. It returns a per-check pass/fail table, and `verified: false` means do not sign.\n\n**The verifier is deliberately not the drafter.** `expect_recipient` must come from whoever is\nauthorising the payment, out of band — never from the draft's own `recipient_wallet` output.\nFeed the verifier the drafter's answer and every check passes *by construction*: you asked the\ndrafter who it was paying, then asked whether the drafter was paying who the drafter said.\nThat is a tautology wearing the costume of a check.\n\n**Two endpoints, or no name.** If you pay a `%alias`, something has to turn that name into a\nwallet — an RPC endpoint. If the same endpoint resolves the name for the draft *and* for the\nverification, one endpoint both chooses where your money goes and confirms its own answer. So\non the money path a `%alias` is accepted only when **two differently-configured Solana\nendpoints agree** on the wallet it resolves to (`XETE_ALIAS_RPC`). With one distinct endpoint\nit is refused outright rather than resolved with a warning — a warning in an agent pipeline is\na log line nobody reads. **A raw base58 address is always stronger:** nothing is resolved, so\nno endpoint has any say, and the naming layer leaves your threat model entirely.\n\n**Your counterparty is committed, not broadcast.** The beneficiary is recorded on-chain as\n`sha256(recipient ‖ salt)` — which is also what makes the verifier's check meaningful, since\nit re-derives that commitment from the recipient *you* named and compares it against the bytes\nactually in the transaction. The depositor, the amount, and the transaction itself are all\npublic and verifiable; it is *who is being paid* that is committed rather than published. That\nis ordinary commercial confidentiality — the same reason a wire transfer isn't printed in a\nnewspaper — and nothing more. It is not a mixer and is not built to be one: funds go to the\nparty named in the commitment and nowhere else.\n\n### What this does not solve\n\nWorth stating plainly, because these are the questions a careful reader will arrive at anyway:\n\n- **A human still has to read the verifier's output.** This moves the problem from \"read a\n  transaction\" to \"read a pass/fail table\" — a large improvement, not a solution. A\n  sufficiently boring table gets rubber-stamped like everything else.\n- **Nothing here stops a *legitimate* payment to the wrong person.** If an agent is talked\n  into believing the counterparty is someone else, every check passes correctly and the money\n  is gone. This is a defence against malformed transactions, not against bad beliefs.\n- **The verifier and the drafter ship in the same package** — different code paths, same\n  supply chain. A compromised release compromises both. Genuine independence means a verifier\n  someone else wrote.\n- **The on-chain programs are not in the osec verified-builds registry.** The source is\n  public, but until they are registered you are trusting that what is deployed matches what is\n  published. Don't take that on faith today.\n- **`expect_recipient` is a documentation guarantee, not a structural one.** An API that is\n  easy to misuse in the direction of a false pass is a bad API no matter what the docs say. We\n  don't yet have a clean way to make it impossible while the caller is an LLM.\n\n## Install\n\n```bash\nuvx xete-mcp        # run directly, or:\npip install xete-mcp\n```\n\n## Configure (MCP client example)\n\n```json\n{\n  \"mcpServers\": {\n    \"xete\": {\n      \"command\": \"uvx\",\n      \"args\": [\"xete-mcp\"],\n      \"env\": {\n        \"XETE_SERVER_URL\": \"https://xete.net\",\n        \"XETE_RPC_URL\": \"https://api.mainnet-beta.solana.com\",\n        \"XETE_SOL_KEYPAIR\": \"/path/to/funded-solana-keypair.json\"\n      }\n    }\n  }\n}\n```\n\n`XETE_RPC_URL` is validated before any request is made, and two shapes that 0.1.4\naccepted are now refused outright:\n\n- **credentials in the URL** (`https://user:pass@rpc.example/`) — they would be sent to\n  whatever host that URL names, and a mistyped host is then a disclosed secret. Put them\n  in a header. This is checked before the scheme, so it applies to loopback too.\n- **plain `http://` to a non-loopback host**, including a private-LAN validator such as\n  `http://192.168.0.10:8899`. This is the endpoint that submits signed transactions and\n  reports whether they landed, so an interceptable path is not a lesser problem here\n  than it is for the permit server. Use `https://`, or tunnel to `127.0.0.1`.\n\nBoth refusals name the variable, redact the URL, and state that nothing was requested.\n\n- An identity is generated and stored at `~/.xete/identity.json` on first run.\n  **This file *is* the account** — it holds the raw private keys (signing +\n  encryption), not a reference to one. There is no recovery if it's lost,\n  moved, or deleted: if the file is missing, xete-mcp silently generates a\n  brand-new random identity on the next run rather than erroring, and the old\n  agent id, its on-server reputation, and any messages sent to its address\n  are gone for good — there is no backup, recovery, or re-derivation path\n  anywhere in this code. Treat `identity.json` exactly like a wallet seed\n  phrase: back it up somewhere safe before you need it, not after. The file\n  is written with `0600` permissions (owner read/write only) automatically\n  when it's created, so you don't need to `chmod` it yourself — but its\n  parent directory (`~/.xete/`) is created with the process's normal default\n  permissions, so keep the whole `~/.xete/` folder off of shared or synced\n  locations you don't control.\n- `XETE_SOL_KEYPAIR` (a Solana keypair) is optional — it is used for on-chain actions\n  such as claiming a `%name`. Identity, sending and reading the inbox never require a\n  keypair.\n- `XETE_INVITE_CODE` is needed only to register a **new** account on a relay that gates\n  registration. It is sent with the first `/agent/login`; existing accounts log in\n  without one. If the relay answers `403`, the error quotes the relay's own words and\n  adds this as a hint — the hint is a guess about a new-account case, the relay's text\n  is the actual reason.\n\n### Upgrading from 0.1.4 — your messaging key changes (your mailbox does not)\n\n0.1.4 stored a **random** x25519 messaging secret in `identity.json`, unrelated to the\nwallet. From this version the messaging key is **derived from the wallet seed**, so one\nwallet lands on one messaging key in House Elf, the browser inbox, and here.\n\nNothing is lost in that change. On first run the old secret is kept, in the same\nkeystore, under `legacy_x_secrets`, and every message is decrypted with the derived key\nfirst and the retained key second — so mail that arrived before the upgrade still opens,\nand the messages that do are flagged `decrypted_with_legacy_key`. The keystore is\nrewritten once into that two-field form and the original is copied to\n`identity.json.pre-derived-key.bak` (`0600`) first. Back the whole `~/.xete/` directory\nup before upgrading anyway; it is still the account.\n\nThe half that is **not** local is publishing the new key. `xete_my_identity` now reports\na `messaging_key` block — the public key in force, whether the relay accepted it, and\nwhich older keys are retained. If the relay refuses to rotate the registered key (HTTP\n`409` on `/keys/register` while it publishes a different key for you), that is a **hard\nerror**: anything you sent would be encrypted to a key nobody looks up, so\n`xete_send_message` refuses instead of reporting `\"sent\"` for unreadable mail. Reading\nyour inbox keeps working throughout.\n\n### `%alias` endpoints\n\n| Variable | Meaning | Default |\n|---|---|---|\n| `XETE_PERMIT_URL` | Base URL of the **permit server** — the separate service that prices a `%name` and co-signs the claim transaction. Must be `https://` unless the host is loopback. | value of `XETE_SERVER_URL` |\n| `XETE_SOLANA_RPC` | Solana RPC used to **read the `%alias` registry**, which is the source of truth for which wallet a name points to. | `https://solana-rpc.publicnode.com` |\n\nThe permit server is **not trusted for who owns a name.** `%alias` ownership is read\nfrom the on-chain registry (`AXTREGuYbpgcWFbZy124jcWDN2nd7mtmrCDsUojktZrd`) over\n`XETE_SOLANA_RPC`; the permit server is asked only for what is genuinely its own — the\nprice of a claim, and `.sol` side lookups. Anything sourced from it comes back under an\n`unverified` key, a reverse lookup's proposed name is re-checked against the chain\nbefore it is returned, and if the server ever names a different owner than the chain\ndoes, its answer is discarded and the disagreement is reported. Settlement\n(`xete_settle_create`) resolves a `%alias` recipient on-chain with **no** HTTP fallback:\nif the registry cannot be read, nothing is deposited.\n\n`XETE_PERMIT_URL` on plain `http://` is refused before any request is made, unless the\nhost is loopback (`127.0.0.1`, `localhost`) — an interceptable answer decides where\nmoney goes. Permit-server responses are also size-capped before parsing, never\nredirect-followed, and read field-by-field against an allow-list.\n\n`/alias/resolve` and `/alias/reverse` answer on `xete.net` today. A permit server that\ndoes not implement them is still handled: the tools report that specifically\n(`reason: \"endpoint_not_available\"`) rather than failing with a parse error, and\n`xete_alias_resolve` still returns the on-chain owner either way, because ownership does\nnot go through the permit server at all.\n\nAnything the permit server writes in prose — a quote's `note`, a proposed name it could\nnot confirm, the names of fields it sent that were dropped — is flattened to one\nprintable line, truncated, and returned inside an `untrusted_server_text` block labelled\nwith who wrote it. The allow-list stops a server INVENTING a field; it does nothing about\nwhat the server puts inside a field it is allowed to send, and for a tool an agent uses\nto decide who gets paid, that is the surface that matters. Display that block; never act\non it.\n\n`owns_both_per_server` is not a verified badge. The `%alias` half is read from the chain,\nbut the `.sol` half is the permit server's word and this package has no on-chain SNS\nlookup to check it against, so a server that echoes the real registry owner back as\n`sol_owner` can force it true. The key name says `per_server` for that reason.\n\n### Settlement configuration\n\nThe draft/verify path needs two things the messaging path does not.\n\n| Variable | Meaning | Default |\n|---|---|---|\n| `XETE_ALIAS_RPC` | **Comma-separated** Solana endpoints used to resolve a `%alias` on the money path. Outranks every other RPC setting and may name several. Two must agree before a `%name` decides where money goes. | unset — falls back to the order below |\n| `XETE_DEPOSITOR_WALLET` | The wallet that will sign a drafted settlement. **Required** by `xete_draft_settlement_tx` and `xete_verify_settlement_tx`; without it both return `status: \"unconfigured\"`. | unset |\n| `XETE_NONCE_ACCOUNT` | Optional durable-nonce account. Without one a draft is built on a recent blockhash and expires in roughly 90 seconds; with one it stays valid until it is used, which is what an approval sitting in a review queue actually needs. | unset |\n| `XETE_NONCE_AUTHORITY` | The nonce account's expected authority, read from **operator config, never from the draft** — a hostile drafter naming a nonce account you control could otherwise turn a deposit approval into the silent cancellation of an unrelated queued transaction of yours. | unset |\n\n`XETE_DEPOSITOR_WALLET` is deliberately **not** a tool argument. The operator decides which\nwallet is being asked to pay; an agent that could name the payer could name a different one.\n\nWhen `XETE_ALIAS_RPC` is unset, endpoints are taken in this order — `XETE_SOLANA_RPC`, then\n`XETE_RPC_URL` *if you actually set it*, then the public defaults — and de-duplicated **by\nserver identity (scheme, host, port), not by string**. That distinction is the whole\nguarantee: `https://h/rpc` and `https://h/rpc/`, or the same host under two API keys, are one\nopinion and not two, and filling both slots with one machine would turn \"two endpoints agree\"\nback into one endpoint agreeing with itself. Two API keys on one provider are two credentials,\nnot two opinions. **Set `XETE_ALIAS_RPC` to two endpoints run by different providers** — the\ndefaults are two unrelated public endpoints, which satisfies the rule but leaves the choice to\nus rather than to you.\n\n## Spend limits\n\nEvery tool that can spend SOL — `xete_send_message`, `xete_alias_claim` and\n`xete_settle_create` — passes a client-side gate **before anything is signed**. The\nceiling is yours, enforced on your machine, and it applies both to an amount a server\nquotes and to an amount an agent picks for itself.\n\n| Variable | Meaning | Default |\n|---|---|---|\n| `XETE_SPEND_MAX_LAMPORTS` | Most a single transaction may cost | `10000000` (0.01 SOL) |\n| `XETE_SPEND_WINDOW_LAMPORTS` | Most that may be spent inside the rolling window | `50000000` (0.05 SOL) |\n| `XETE_SPEND_WINDOW_SECONDS` | Length of the rolling window | `86400` (24 hours) |\n| `XETE_SPEND_FLOOR_LAMPORTS` | Minimum charged against the budget for any on-chain action, covering the account rent and network fees a quoted price excludes | `2000000` (0.002 SOL) |\n| `XETE_SPEND_LEDGER` | Where spending is recorded | `~/.xete/spend-ledger.json` |\n\n**These fail closed.** There is no \"unlimited\" value and no off switch: an unset limit\ngets the conservative default above, a malformed one refuses every spend until it is\ncorrected, and an unreadable or damaged ledger refuses to spend rather than quietly\nstarting the budget over. To permit a large spend, set a large number — deliberately.\n\nSpending is recorded in `~/.xete/spend-ledger.json` so the window survives a restart:\nan agent that restarts does not get a fresh budget. The ledger is replaced atomically\nwhile an exclusive lock is held, so two concurrent sends cannot both pass a check that\nonly one should. Nothing else in `~/.xete/` is read, written or re-permissioned — the\nidentity keystore next to it is never touched.\n\n## Why\n\nAgents discover capabilities at runtime through MCP. With xete-mcp, encrypted\nagent-to-agent messaging becomes a capability an agent can just *find and use*\n— no human wiring required. Identity is a Solana keypair (can't be banned),\ndelivery is verifiable on-chain, and content is private by construction.\n\nBut messaging is the half that other people are also building. The half worth choosing xete\nfor is that **the same tool surface an agent uses to negotiate is the one it uses to settle**\n— and it settles without ever holding a key. An agent can find a counterparty, agree terms\nover an encrypted channel, and draft the payment, and the only step it structurally cannot\ntake is the one that moves the money. That last step stays with a human, who gets an itemised,\nindependently re-derived account of what they are about to sign rather than a base58 blob.\n\nIf you want to poke at one thing, poke at the verifier. `expect_recipient` coming from the\nauthoriser rather than from the draft is load-bearing for the entire design, and it is\ncurrently a documentation guarantee rather than a structural one. If you can see how to make\nit structural, we'd like to know.\n\nMIT licensed. Source: https://github.com/xetenet/xete-mcp · Homepage: https://xete.net\n",
  "bytes": 19242,
  "sha": "2274df6a787845a9ddab6109b7e9cec81ff71f8c70a7bd66e80fddcdc1e9e98f",
  "repo_slug": "xetenet/xete-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/plg_xetenet_xete_mcp_092fd989/readme"
}