MCP server
Post-quantum tools for your AI agents
Six tools on one local server: encrypt files, send sealed messages, read Quantum Seals and sign x402 payments, from Claude, Cursor or any MCP client.
$ npx -y @kvant/mcpInstall · next
One command, next.
The server is built and tested; the npm release is next. It ships under the @kvant scope, which only Kvant can publish to, so this command can never run someone else’s upload. Node.js 20 or later runs it over stdio.
Claude Desktop
{"mcpServers": {"kvant": {"command": "npx","args": ["-y", "@kvant/mcp"]}}}
This config runs once the package is on npm: drop it into your config and restart the client, and Kvant tools appear in the tools picker.
Cursor
{"mcpServers": {"kvant": {"command": "npx","args": ["-y", "@kvant/mcp"]}}}
Same shape, same server. Works with any client that speaks Model Context Protocol over stdio.
Tools
Six primitives.
| Tool | Description | Inputs | Outputs |
|---|---|---|---|
| kvant_encrypt_file | Encrypt bytes under ML-KEM-768 + X25519 + ML-DSA-65. | bytes_base64, recipient_pubkey_base64, signer?: keyfile | signature + address | blob_base64, seal_png_base64, signer_pubkey_base64 |
| kvant_decrypt_file | Decrypt a .kvant blob with a keyfile or wallet signature. | blob_base64, (keyfile_json + passphrase | signature_hex + address), expected_signer_pubkey_base64 (required unless self-signed) | plaintext_base64, signer_pubkey_base64, filename |
| kvant_send_message | Wrap text in a shareable encrypted URL, optionally signed by your wallet. | text, recipient_pubkey_base64?, signer?: keyfile | signature + address | url, mode |
| kvant_decrypt_message | Recover plaintext + signer from a Kvant message URL. | url, (keyfile_json + passphrase | signature_hex + address)?, expected_signer_pubkey_base64? | text, signer_pubkey_base64 |
| kvant_verify_seal | Read the unsigned fields of a Quantum Seal PNG. Given the envelope, check that its SHA-256 matches the Seal file_hash. | png_base64, blob_base64? | valid, metadata { version, timestamp, algo, file_hash, wallet?, x402?, anchor? }, file_hash_matches? |
| kvant_sign_x402 | Sign an x402 payload with ML-DSA-65 under context pqenv-v1-x402. | payload_json (JSON object), (keyfile_json + passphrase | signature_hex + address) | signature_base64, signer_pubkey_base64 |
Every tool runs locally over stdio. No network I/O. No telemetry. Keyfiles are unwrapped in-process with Argon2id and never persisted. A wallet signature is root key material: pass it over local stdio only, never a networked transport.
x402 signatures
What kvant_sign_x402 signs.
The tool signs the UTF-8 bytes of the payload’s canonical form with ML-DSA-65, under the context pqenv-v1-x402. Anyone holding the payload, signature_base64 and signer_pubkey_base64 can check which key signed, with any FIPS 204 implementation and no Kvant code.
- Object keys sorted in JavaScript default string order (UTF-16 code units), at every level.
- No whitespace. Arrays keep their order.
- Strings, numbers, booleans and null written exactly as JSON.stringify writes them.
- The ML-DSA-65 public key is bytes 1216 to 3168 of the decoded signer_pubkey_base64, after the ML-KEM-768 and X25519 parts.
import { ml_dsa65 } from '@noble/post-quantum/ml-dsa' // 0.4.x argument order// payload {"b":1,"a":{"d":[true,null],"c":2.5}} is signed as:const canonical = '{"a":{"c":2.5,"d":[true,null]},"b":1}'const hybridKey = Buffer.from(signer_pubkey_base64, 'base64')const mldsaKey = hybridKey.subarray(1216, 3168)const valid = ml_dsa65.verify(mldsaKey,new TextEncoder().encode(canonical),Buffer.from(signature_base64, 'base64'),new TextEncoder().encode('pqenv-v1-x402'),)
Wallet identity
Your wallet is the key.
An agent signs one fixed message with its EOA wallet and derives the same hybrid keypair the browser does. Encrypt at /app, decrypt from the agent: no keyfile export, no copied secrets. Same wallet, same keys, same files, as long as the wallet signs deterministically. EOA only: smart-contract wallets are rejected at derivation. MPC and threshold signers return a different signature every time; the browser catches them by asking for two signatures the first time an address derives keys on a device. The server runs the same check when you pass a second signature as signature_hex_repeat the first time you use a wallet, and it refuses a signature that does not recover to the address you name. Sign the Kvant message only in your own agent or on kvant.world: anyone who gets the signature can open your files. For a durable copy, /app wraps the same keypair as a passphrase-protected keyfile (Back up keys).
import { privateKeyToAccount } from 'viem/accounts'const SIGN_MESSAGE = ['Kvant keypair derivation v1','','This signature derives your personal post-quantum encryption keypair.','It grants no permissions and costs no gas.','','Domain: kvant.world',].join('\n')// An EOA with a local private key signs with RFC 6979: the bytes repeat.// MPC and threshold signers produce a different valid signature each time,// so pass a second one the first time: the server refuses the call if they differ.const account = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY)const signature_hex = await account.signMessage({ message: SIGN_MESSAGE })const signature_hex_repeat = await account.signMessage({ message: SIGN_MESSAGE })// The server recovers the signer and refuses a signature that is not from address.// Encrypting tools take the same fields with a signer_ prefix.await mcp.call('kvant_decrypt_file', {blob_base64, // encrypted to this wallet in /appsignature_hex,address: account.address,signature_hex_repeat,})