Skip to content
Veilnet v1.0

MCP Server

The VeilNet MCP server (@veilnet/mcp-server) lets any Model Context Protocol client — Claude Code, Claude Desktop, Cursor, Continue, Cline, OpenClaw, Zed, or any custom SDK build — create and run encrypted on-chain agent strategies without leaking the strategy parameters to VeilNet or to the LLM provider.

  • Seven tools: agent_create, agent_list, agent_get, agent_update, agent_delete, agent_run, wallet_status
  • Encryption: AES-256-GCM with AAD-bound metadata ({kind, version}) — VeilNet servers only ever see ciphertext
  • Master key: 32 bytes generated in your browser at https://app.veilnet.to (Agent tab), wrapped with your passphrase via PBKDF2-SHA256 (600,000 iterations), stored as an opaque envelope by VeilNet
  • Transport: stdio (works in every major MCP client today). HTTP+SSE deferred to a later release.
  • Package: @veilnet/mcp-server on npm

How it works

┌─────────────────────────────────────────────────────────────┐
│  Your machine                                               │
│                                                             │
│  MCP Client (Claude Code / Cursor / etc.)                  │
│       │                                                     │
│       │  stdio (JSON-RPC)                                   │
│       ▼                                                     │
│  @veilnet/mcp-server                                        │
│   • reads ~/.veilnet/session.json + master.key on boot      │
│   • encrypts params locally → AES-256-GCM                   │
│   • decrypts agent payloads locally on agent_get/agent_run  │
└─────────────────────────────────────────────────────────────┘

       │  HTTPS  Authorization: Bearer <JWT>

┌─────────────────────────────────────────────────────────────┐
│  api.veilnet.to                                             │
│   /agents              CRUD (auth + rate-limited)           │
│   /agents/keys/envelope  GET/POST wrapped key               │
│                                                             │
│   Stores: ciphertext, iv, kind, timestamps — that's it.     │
└─────────────────────────────────────────────────────────────┘


   MongoDB — server is blind to your strategy parameters

Privacy invariant: strategy parameters are encrypted on your machine with a master key VeilNet does not have. The backend stores only ciphertext + IV + the agent kind + lifecycle timestamps. The LLM provider only sees tool calls and any plaintext you put in the chat — never your master key, never your JWT.


Prerequisites

  1. Node.js ≥ 22. Native WebCrypto is required for the master-key file loader.
  2. A funded VeilNet shielded-USDC balance. Get one at https://app.veilnet.to (Deposit tab).
  3. Two files at ~/.veilnet/ — see Initial setup below.

Initial setup — one-time

  1. Open https://app.veilnet.to, connect your EVM wallet (MetaMask, Rabby, etc.), and sign the SIWE login message.
  2. Click the Agent tab in the dApp.
  3. Enter a passphrase (≥ 8 characters). If you lose this passphrase, you lose access to your encrypted agents on any new device.
  4. Click Generate & download. Two files download:
    • session.json — short-lived JWT + your wallet address + API base URL (valid for 7 days)
    • master.key — your 32-byte AES master key, base64-encoded inside JSON
  5. Move both files into ~/.veilnet/.
bash
mkdir -p ~/.veilnet
mv ~/Downloads/session.json ~/.veilnet/session.json
mv ~/Downloads/master.key   ~/.veilnet/master.key
chmod 600 ~/.veilnet/session.json ~/.veilnet/master.key
cmd
mkdir %USERPROFILE%\.veilnet 2>nul
move /Y "%USERPROFILE%\Downloads\session.json" "%USERPROFILE%\.veilnet\session.json"
move /Y "%USERPROFILE%\Downloads\master.key"   "%USERPROFILE%\.veilnet\master.key"
powershell
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.veilnet" | Out-Null
Move-Item "$env:USERPROFILE\Downloads\session.json" "$env:USERPROFILE\.veilnet\session.json" -Force
Move-Item "$env:USERPROFILE\Downloads\master.key"   "$env:USERPROFILE\.veilnet\master.key"   -Force

File permissions

On POSIX systems, tighten both files with chmod 600. v0.1 of the MCP server prints a warning if it sees world-readable files; v0.2 will hard-fail. Windows uses ACLs — the file is already only readable by your user account by default.

JWT expiry

session.json is valid for 7 days. When it expires the tools start returning VEILNET_REAUTH_REQUIRED — reopen the Agent tab in the dApp and re-download just session.json. Your master.key does not change unless you click Reset on the dApp (which invalidates all existing encrypted agents).


Install in your MCP client

The server is the same binary for every client; only the wiring changes. Pick the one(s) you use.

Claude Code

bash
# Registers the server globally for your user
claude mcp add veilnet --scope user -- npx -y @veilnet/mcp-server

Verify with claude mcp list — you should see veilnet: ✓ Connected.

Claude Desktop

Edit claude_desktop_config.json:

OSPath
macOS~/Library/Application Support/Claude/claude_desktop_config.json
Windows%APPDATA%\Claude\claude_desktop_config.json
Linux~/.config/Claude/claude_desktop_config.json
json
{
  "mcpServers": {
    "veilnet": {
      "command": "npx",
      "args": ["-y", "@veilnet/mcp-server"]
    }
  }
}

If npx isn't on Claude Desktop's PATH (common on macOS when Node was installed via nvm or fnm), use the absolute node path and binary path instead:

json
{
  "mcpServers": {
    "veilnet": {
      "command": "/usr/local/bin/node",
      "args": ["/path/to/global/node_modules/@veilnet/mcp-server/bin/veilnet-mcp.js"]
    }
  }
}

Restart Claude Desktop. The 🔌 icon at the bottom of any chat shows registered MCP servers.

Cursor

  • Global (every workspace): ~/.cursor/mcp.json
  • Per-project (overrides global for that repo): .cursor/mcp.json in the repo root
json
{
  "mcpServers": {
    "veilnet": {
      "command": "npx",
      "args": ["-y", "@veilnet/mcp-server"]
    }
  }
}

Toggle the server on under Settings → Cursor Settings → MCP.

Continue (VS Code / JetBrains)

Continue uses standalone YAML blocks per server. Create .continue/mcpServers/veilnet.yaml in your home or project root:

yaml
name: VeilNet MCP
version: 0.1.0
schema: v1
mcpServers:
  - name: veilnet
    type: stdio
    command: npx
    args:
      - "-y"
      - "@veilnet/mcp-server"

TIP

MCP tools are only usable in Continue's agent mode — not chat or edit. Switch with the mode selector at the bottom of the input box.

Cline (VS Code extension)

Easiest path: in the Cline panel, click the MCP Servers icon → Configure tab → Configure MCP Servers. The JSON file that opens is the one you want.

To edit by hand:

OSPath
macOS~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
Windows%APPDATA%\Code\User\globalStorage\saoudrizwan.claude-dev\settings\cline_mcp_settings.json
Linux~/.config/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json
json
{
  "mcpServers": {
    "veilnet": {
      "command": "npx",
      "args": ["-y", "@veilnet/mcp-server"]
    }
  }
}

OpenClaw

Edit ~/.openclaw/mcp.json (or use the in-app Settings → MCP Servers panel — the file it opens is the same one):

json
{
  "mcpServers": {
    "veilnet": {
      "command": "npx",
      "args": ["-y", "@veilnet/mcp-server"]
    }
  }
}

Restart OpenClaw. The server appears in the MCP indicator at the bottom of any chat.

Zed

Open settings.json (cmd/ctrl + ,) and add a context_servers entry:

json
{
  "context_servers": {
    "veilnet": {
      "command": {
        "path": "npx",
        "args": ["-y", "@veilnet/mcp-server"]
      }
    }
  }
}

Zed picks up new context servers without a restart. Open the assistant panel and look for veilnet under available tools.

Any other MCP-spec client (custom SDK build)

If your client speaks the MCP stdio transport, point it at the same binary.

TypeScript:

ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "npx",
  args: ["-y", "@veilnet/mcp-server"],
});
const client = new Client({ name: "my-app", version: "1.0.0" }, { capabilities: {} });
await client.connect(transport);
const tools = await client.listTools(); // 7 veilnet tools

Python:

python
from mcp.client.stdio import stdio_client, StdioServerParameters

params = StdioServerParameters(command="npx", args=["-y", "@veilnet/mcp-server"])
async with stdio_client(params) as (read, write):
    # use read/write to drive the seven veilnet tools
    ...

Tool names, shapes, and error codes are identical across every client.


Optional — install the Claude Code skill

A skill at skills/veilnet-agent/SKILL.md teaches Claude how to map natural-language strategy descriptions ("weekly $50 USDC into ETH") to the right tool call without you writing JSON.

bash
mkdir -p ~/.claude/skills/veilnet-agent
curl -fsSL https://raw.githubusercontent.com/syncodemayo/VeilNet/main/skills/veilnet-agent/SKILL.md \
  -o ~/.claude/skills/veilnet-agent/SKILL.md
powershell
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.claude\skills\veilnet-agent" | Out-Null
Invoke-WebRequest `
  -Uri "https://raw.githubusercontent.com/syncodemayo/VeilNet/main/skills/veilnet-agent/SKILL.md" `
  -OutFile "$env:USERPROFILE\.claude\skills\veilnet-agent\SKILL.md"

Restart Claude Code. The skill auto-activates whenever you mention agents, DCA, rebalance, or yield.

Private repo

The repo is currently private; the curl/PowerShell links above are placeholders until the skill is published publicly. In the meantime, copy the file manually from the package — node_modules/@veilnet/mcp-server/skills/veilnet-agent/SKILL.md (if shipped) or from the VeilNet team.


Tools reference

Tool names and shapes are the same in every MCP client.

ToolShape (Zod)Notes
wallet_status{}Confirms session + backend reachability. Returns {address, exp, backend: "ok" | "unreachable"}.
agent_create{ kind: "dca" | "rebalance" | "yield", params: object }Encrypts params locally with AAD {kind, version}.
agent_list{}Returns metadata only — never params.
agent_get{ id: string }Decrypts params locally; returned to the agent process only.
agent_update{ id: string, status?: "active" | "paused", params?: object }At least one of status or params is required. Re-encrypts with the existing kind.
agent_delete{ id: string }Soft delete.
agent_run{ id: string }Bumps lastRunAt and returns decrypted params.

Agent kinds

Pass the param object as a plain JSON value — the MCP server stringifies and encrypts it.

dca — dollar-cost averaging

jsonc
{
  "fromAsset": "USDC",          // source ticker
  "toAsset": "ETH",             // destination ticker
  "amountPerRun": "50",         // human string (NOT raw units)
  "cadence": "weekly",          // hourly | daily | weekly | monthly
  "maxSlippageBps": 50,         // 50 = 0.5%; default 50
  "expiresAt": "2026-12-31T00:00:00Z" // optional ISO timestamp
}

rebalance — keep a basket at target weights

jsonc
{
  "targetWeights": { "ETH": 5000, "USDC": 3000, "WBTC": 2000 }, // bps; must sum to 10000
  "tolerance": 200,             // bps drift before rebalancing; default 200 (2%)
  "cadence": "daily"            // hourly | daily | weekly
}

yield — auto-route to highest-APR pool

jsonc
{
  "asset": "USDC",
  "protocol": "aave-v3",        // aave-v3 | compound-v3 | morpho
  "minAprBps": 300,             // 300 = 3%; default 300
  "maxAllocation": "1000"       // human string cap
}

Sensible defaults (your AI agent will pick these automatically)

FieldDefault
maxSlippageBps50 (0.5%)
cadence (DCA)weekly
cadence (rebalance)daily
tolerance (rebalance)200 bps
minAprBps (yield)300

Natural-language usage

Once installed, you don't need to write JSON. Just describe what you want:

You sayYour AI calls
"Set up a weekly $50 USDC into ETH DCA"agent_create({kind:"dca", params:{fromAsset:"USDC", toAsset:"ETH", amountPerRun:"50", cadence:"weekly", maxSlippageBps:50}})
"Rebalance my wallet to 50% ETH, 30% USDC, 20% WBTC daily"agent_create({kind:"rebalance", params:{targetWeights:{ETH:5000, USDC:3000, WBTC:2000}, tolerance:200, cadence:"daily"}})
"Yield-farm 1000 USDC into Aave with 3% minimum"agent_create({kind:"yield", params:{asset:"USDC", protocol:"aave-v3", minAprBps:300, maxAllocation:"1000"}})
"What agents do I have?"agent_list()
"Pause my DCA"agent_list() → find kind:"dca", status:"active"agent_update({id, status:"paused"})
"Run my rebalance now"agent_run({id})
"Delete the yield bot"agent_delete({id})

Quick smoke test

To prove the connection without modifying anything:

  1. Ask your AI: "What's my VeilNet wallet status?" → triggers wallet_status → should report address: 0x… and backend: "ok".
  2. Ask: "List my agents." → triggers agent_list → empty array is fine on first run.

If wallet_status returns VEILNET_REAUTH_REQUIRED, re-download session.json from the Agent tab.


Troubleshooting

Error codeMeaningFix
VEILNET_SESSION_MISSING~/.veilnet/session.json not foundRun the Agent-tab setup.
VEILNET_SESSION_MALFORMEDBad JSON or missing required fieldsRe-download session.json.
VEILNET_REAUTH_REQUIREDJWT expired (server returned 401)Re-download session.json from the Agent tab.
VEILNET_MASTER_KEY_MISSING~/.veilnet/master.key not foundRe-download master.key.
VEILNET_MASTER_KEY_MALFORMEDWrong shape (must be base64 of exactly 32 bytes inside the JSON)Re-download master.key.
VEILNET_DECRYPT_FAILEDMaster key doesn't match this agent's ciphertextConfirm you're using the master.key for the same wallet that created the agent.
VEILNET_API_RATE_LIMITEDHit the per-user rate limitWait the seconds in the Retry-After header / error message.
VEILNET_API_NOT_FOUNDAgent id doesn't exist (or was soft-deleted)agent_list to see current ids.

Common gotchas

  • Two browser downloads were blocked. Chrome auto-blocks the second of two near-simultaneous downloads. If only session.json came through, scroll to the bottom of the success panel in the Agent tab and click the second download button for master.key explicitly.
  • npx not found in Claude Desktop. Use the absolute node + binary path layout shown in the Claude Desktop snippet above.
  • world-readable warnings on stderr. Run chmod 600 ~/.veilnet/* on macOS / Linux. v0.1 only warns; v0.2 will refuse to start.
  • Tool calls return VEILNET_REAUTH_REQUIRED on every call. Your JWT has expired. The 7-day clock starts from each Generate.

Threat model

VeilNet servers can see:

  • Ciphertext, IV, the kind string ("dca" / "rebalance" / "yield"), lifecycle timestamps.

VeilNet servers cannot see:

  • Strategy parameters (amounts, tickers, weights, slippage, etc.).
  • Your master key — the wrapped envelope at /agents/keys/envelope is decryptable only with your passphrase.

The LLM provider (Anthropic, OpenAI, etc.) sees:

  • The names of the tools your AI calls.
  • Any plain text you type into the chat.
  • Decrypted params returned by agent_get / agent_run — the MCP server passes these back as tool results. Don't dump strategy plaintext into the chat scrollback if you want to keep it private from the provider.

Network observers (between you and api.veilnet.to) see:

  • TLS-encrypted traffic. The wire shape (ciphertext + IV + iterations) is opaque even inside the TLS stream.

An attacker who steals only your wallet:

  • Can re-authenticate to VeilNet, but cannot decrypt your existing agents without master.key.

An attacker who steals master.key + a valid JWT:

  • Can decrypt all your agents. Keep master.key offline-backed-up (a hardware vault, password manager, or air-gapped USB). To rotate, click Reset on the Agent tab — this replaces the envelope and invalidates the old key (and any agents encrypted with it).

FAQ

What does VeilNet actually store?

{ address, agentId, kind, ciphertext, iv, version, status, createdAt, updatedAt }. Plus your wrapped envelope at /agents/keys/envelope. That's it. No plaintext params ever touch the disk.

Can I use the same setup on multiple machines?

Yes. Copy ~/.veilnet/session.json and ~/.veilnet/master.key to the second machine (over a secure channel — encrypted USB, SSH, etc.). Both machines share the same JWT, so the 7-day expiry is shared too.

For a clean per-device setup, the Agent tab will (in a future release) offer per-device envelope wrapping. For now, the simple file copy is the supported path.

What happens if I lose my passphrase?

You can still authenticate (SIWE doesn't need the passphrase), but you cannot decrypt your existing agents on a new device. On the current device, your master.key file is intact and still works.

To recover, you must agent_delete every existing agent, click Reset on the Agent tab, choose a new passphrase, and re-create your strategies.

What models / providers does this work with?

Any model your MCP client supports. The MCP server is provider-agnostic — Claude (3.5 Sonnet, Opus 4.x, Haiku), GPT-4o, GPT-4.1, Gemini 1.5/2.0, Llama, local Ollama models, etc.

Does the MCP server run on remote / serverless?

Not in v0.1 — stdio only. HTTP+SSE transport is on the roadmap. For now, the server runs as a local subprocess of your MCP client.

Is this open source?

The packages (@veilnet/agent-crypto, @veilnet/mcp-server) and the dApp are open source. The protocol contracts are audited and on-chain. See the npm page and https://github.com/syncodemayo/VeilNet (private during beta — request access via support@veilnet.to).

Where can I report bugs?

Email support@veilnet.to or open an issue on the GitHub repo if you have access. Include the error code (VEILNET_*) and which MCP client / OS / Node version you're on. Never paste the contents of master.key or session.json into a bug report.


Versioning

  • v0.1 — stdio only, 7 tools, AAD-bound encryption, single envelope per user, soft-delete agents.
  • v0.2 (planned) — hard-fail on world-readable files, JWT auto-refresh, per-device envelope wrapping, HTTP+SSE transport, OS-keychain integration (macOS Keychain / Windows Credential Manager / libsecret).

License

MIT.

Privacy layer for EVM chains