@dcprotocol/server

Fastify-based REST API server providing HTTP access to your DCP vault. Perfect for local agents built with any language or framework.

Installation

npm install @dcprotocol/server

Quick Start

# Start server (default port 8420)
npx @dcprotocol/server

# Or with custom port
npx @dcprotocol/server --port 3000

# Server runs at http://127.0.0.1:8420

Overview

The DCP server provides a REST API for agents to interact with the vault. It runs on localhost only (127.0.0.1) for security - never exposed to the public internet.

When to Use

  • Building agents in Python, Go, Rust, or other non-JS languages
  • Using frameworks that don't support MCP (Langchain, AutoGen, etc.)
  • Need HTTP/REST instead of stdio or native SDK
  • Want browser-based consent UI instead of terminal prompts

Architecture

┌─────────────────┐
│  Your Agent     │ (Python/Go/Any Language)
│  localhost:3000 │
└────────┬────────┘
         │ HTTP
         ▼
┌─────────────────────┐
│  DCP Server         │ http://127.0.0.1:8420
│  @dcprotocol/server │
└────────┬────────────┘
         │
         ▼
┌─────────────────────┐
│  @dcprotocol/core   │ SQLite vault @ ~/.dcp
└─────────────────────┘

API Endpoints

GET /health

Health check endpoint.

curl http://127.0.0.1:8420/health

Response:
{
  "status": "ok",
  "vault_unlocked": true
}

GET /scopes

List all available wallets and data in vault (capability discovery).

curl http://127.0.0.1:8420/scopes

Response:
{
  "scopes": [
    {
      "scope": "wallet:solana",
      "type": "WALLET_KEY",
      "sensitivity": "critical",
      "chain": "solana",
      "public_address": "5YzW8d...AbC123"
    },
    {
      "scope": "identity.email",
      "type": "DATA",
      "sensitivity": "sensitive",
      "chain": null,
      "public_address": null
    }
  ]
}

# Use this endpoint to discover what the agent can access

GET /address/:chain

Get wallet address for a blockchain.

curl http://127.0.0.1:8420/address/solana

Response:
{
  "address": "5YzW8d...AbC123",
  "chain": "solana"
}

# Supported chains: solana, ethereum, base

POST /v1/vault/unlock

Unlock vault with passphrase.

curl -X POST http://127.0.0.1:8420/v1/vault/unlock \
  -H "Content-Type: application/json" \
  -d '{"password": "your-passphrase"}'

Response:
{
  "success": true,
  "session_expires_at": "2026-03-20T11:30:00Z"
}

POST /v1/vault/sign

Sign a blockchain transaction.

curl -X POST http://127.0.0.1:8420/v1/vault/sign \
  -H "Content-Type: application/json" \
  -d '{
    "chain": "solana",
    "agent_name": "my-trading-bot",
    "amount": 1.5,
    "currency": "SOL",
    "unsigned_tx": "base64-encoded-transaction"
  }'

Response:
{
  "signed_tx": "base64-encoded-signed-transaction",
  "signature": "transaction-signature-hex"
}

Request Fields

FieldTypeDescription
chainstringsolana | ethereum | base
agent_namestringName of agent making request
amountnumberTransaction amount
currencystringSOL | ETH | BASE_ETH | USDC
unsigned_txstringBase64-encoded unsigned transaction

POST /v1/vault/read

Read encrypted data from vault.

curl -X POST http://127.0.0.1:8420/v1/vault/read \
  -H "Content-Type: application/json" \
  -d '{
    "scope": "identity.email",
    "agent_name": "my-agent"
  }'

Response:
{
  "value": "john@example.com",
  "scope": "identity.email",
  "sensitivity": "sensitive"
}

GET /budget/check

Check if transaction is within budget limits.

curl "http://127.0.0.1:8420/budget/check?chain=solana&amount=1.5&currency=SOL"

Response:
{
  "allowed": true,
  "requires_approval": false,
  "remaining_daily": 18.5,
  "remaining_tx": 3.5
}

GET /v1/vault/activity

Get audit log.

curl http://127.0.0.1:8420/v1/vault/activity?limit=10

Response:
{
  "events": [
    {
      "timestamp": "2026-03-20T10:30:00Z",
      "agent": "TradingBot",
      "action": "SIGN",
      "chain": "solana",
      "amount": 1.5,
      "currency": "SOL",
      "status": "APPROVED"
    }
  ]
}

POST /v1/vault/consent/approve

Approve pending consent request (from browser UI).

curl -X POST http://127.0.0.1:8420/v1/vault/consent/approve \
  -H "Content-Type: application/json" \
  -d '{
    "consent_id": "consent-uuid-123"
  }'

Response:
{
  "success": true
}

POST /v1/vault/consent/deny

Deny pending consent request.

curl -X POST http://127.0.0.1:8420/v1/vault/consent/deny \
  -H "Content-Type: application/json" \
  -d '{
    "consent_id": "consent-uuid-123"
  }'

Response:
{
  "success": true
}

Consent UI

When an agent requests a sensitive operation (signing, reading data), the server automatically opens a browser window at http://127.0.0.1:8420/consentshowing the request details and approve/deny buttons.

Consent Flow

1. Agent calls POST /v1/vault/sign
2. Server creates consent request
3. Browser window opens showing:
   - Agent name
   - Operation (sign transaction)
   - Amount and currency
   - Transaction details
   - Approve/Deny buttons
4. User clicks Approve
5. Server processes request
6. Agent receives signed transaction

Trusted Agents

Agents marked as "trusted" skip the consent UI for transactions below the auto-approve threshold.

# Trust an agent via CLI
dcp agents trust TradingBot

# Now TradingBot can auto-sign transactions under the threshold
# (default: 2 SOL, 0.1 ETH, 100 USDC)

Integration Examples

Python

import requests

BASE_URL = "http://127.0.0.1:8420"

# Get Solana address
response = requests.get(f"{BASE_URL}/address/solana")
address = response.json()["address"]
print(f"Solana address: {address}")

# Sign transaction
response = requests.post(f"{BASE_URL}/v1/vault/sign", json={
    "chain": "solana",
    "agent_name": "python-trader",
    "amount": 1.5,
    "currency": "SOL",
    "unsigned_tx": unsigned_tx_base64
})

if response.status_code == 200:
    result = response.json()
    signed_tx = result["signed_tx"]
    print(f"Signed: {signed_tx}")
else:
    print(f"Error: {response.json()}")

Go

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
)

const baseURL = "http://127.0.0.1:8420"

func getAddress(chain string) (string, error) {
    resp, err := http.Get(fmt.Sprintf("%s/address/%s", baseURL, chain))
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    var result struct {
        Address string `json:"address"`
    }
    json.NewDecoder(resp.Body).Decode(&result)
    return result.Address, nil
}

func signTransaction(chain string, amount float64, unsignedTx string) (string, error) {
    payload := map[string]interface{}{
        "chain":       chain,
        "agent_name":  "go-trader",
        "amount":      amount,
        "currency":    "SOL",
        "unsigned_tx": unsignedTx,
    }

    body, _ := json.Marshal(payload)
    resp, err := http.Post(
        baseURL+"/v1/vault/sign",
        "application/json",
        bytes.NewBuffer(body),
    )
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    var result struct {
        SignedTx string `json:"signed_tx"`
    }
    json.NewDecoder(resp.Body).Decode(&result)
    return result.SignedTx, nil
}

cURL Scripts

#!/bin/bash
# save as dcp-sign.sh

AGENT_NAME="bash-trader"
CHAIN="solana"
AMOUNT=1.5
TX="base64-encoded-transaction"

curl -X POST http://127.0.0.1:8420/v1/vault/sign \
  -H "Content-Type: application/json" \
  -d "{
    \"chain\": \"$CHAIN\",
    \"agent_name\": \"$AGENT_NAME\",
    \"amount\": $AMOUNT,
    \"currency\": \"SOL\",
    \"unsigned_tx\": \"$TX\"
  }" | jq -r '.signed_tx'

Server Configuration

Command-Line Options

OptionDefaultDescription
--port8420HTTP port to listen on
--host127.0.0.1Host to bind to (localhost only)
--vault-dir~/.dcpVault directory
--no-browserfalseDisable auto-opening consent UI

Environment Variables

VariableDefaultDescription
VAULT_DIR~/.dcpVault storage directory
DCP_SERVER_PORT8420HTTP port
DCP_NO_BROWSERfalseDisable consent browser UI

Security Considerations

  • Localhost only: Server binds to 127.0.0.1 and cannot be accessed remotely
  • No authentication: Assumes localhost is trusted (single-user machine)
  • CORS restricted: Only allows requests from localhost origins
  • Session-based: Vault unlocks expire after inactivity
  • Consent required: All sensitive operations require user approval (unless trusted)
⚠️ Never expose to internet: This server is designed for localhost only. Use @dcprotocol/proxy for remote access via encrypted relay.

Production Deployment

systemd Service

Run server as systemd service on Linux:

# /etc/systemd/system/dcp-server.service
[Unit]
Description=DCP Vault Server
After=network.target

[Service]
Type=simple
User=youruser
ExecStart=/usr/local/bin/npx @dcprotocol/server
Restart=on-failure
Environment="VAULT_DIR=/home/youruser/.dcp"

[Install]
WantedBy=multi-user.target

# Enable and start
sudo systemctl enable dcp-server
sudo systemctl start dcp-server

Docker

# Not recommended - vault should stay on host machine
# If you must use Docker, mount vault directory:

docker run -d \
  --name dcp-server \
  -p 127.0.0.1:8420:8420 \
  -v ~/.dcp:/root/.dcp \
  node:18 \
  npx @dcprotocol/server

Troubleshooting

Port Already in Use

# Error: Port 8420 already in use
# Use different port
npx @dcprotocol/server --port 3000

# Or kill existing process
lsof -ti:8420 | xargs kill

Vault Locked

# Error: Vault is locked
# Unlock via API
curl -X POST http://127.0.0.1:8420/v1/vault/unlock \
  -H "Content-Type: application/json" \
  -d '{"password": "your-passphrase"}'

Consent UI Not Opening

# If browser doesn't open automatically
# Manually navigate to: http://127.0.0.1:8420/consent

# Or disable browser UI and handle via API
npx @dcprotocol/server --no-browser

See Also