npm package: @mcp-wallet/sdk
Description: Extend the MCP SDK with on-chain transaction signing capabilities
Latest version: 0.1.0

# mcp-wallet

Extend the [MCP SDK](https://github.com/modelcontextprotocol/typescript-sdk) with on-chain transaction signing. Give your AI agents a wallet.

`mcp-wallet` adds `server.transaction()` alongside the standard `server.tool()`. Transaction tools go through a full safety pipeline: **build -> introspect -> policy -> simulate -> sign -> submit** — with audit logging, spending tracking, and webhook notifications at every step.

## Install

```bash
npm install @mcp-wallet/sdk @modelcontextprotocol/sdk
```

For the Solana adapter:
```bash
npm install @solana/web3.js
```

For SQLite persistence:
```bash
npm install better-sqlite3
```

## Quick Start

```typescript
import { SigningMcpServer, maxPerTransaction, maxPerDay } from '@mcp-wallet/sdk';
import { SolanaKeypairAdapter } from '@mcp-wallet/sdk/adapters/solana';
import { Keypair } from '@solana/web3.js';
import { z } from 'zod';

const server = new SigningMcpServer(
  { name: 'My Agent', version: '1.0.0' },
  {
    wallet: new SolanaKeypairAdapter({
      keypair: Keypair.generate(),
      rpcUrl: 'https://api.devnet.solana.com',
    }),
    policies: [
      maxPerTransaction(500_000_000),  // 0.5 SOL
      maxPerDay(2_000_000_000),        // 2 SOL
    ],
  },
);

// Regular MCP tool — no signing
server.tool('get_price', 'Get token price', { token: z.string() }, async (params) => ({
  content: [{ type: 'text', text: `Price: $1.00` }],
}));

// Transaction tool — full signing pipeline
server.transaction(
  'transfer_sol',
  'Transfer SOL to a recipient',
  {
    recipient: z.string().describe('Recipient address'),
    amount: z.number().describe('Amount in lamports'),
  },
  async (params, extra) => {
    const tx = buildTransferTx(params.recipient, params.amount, extra.signerPublicKey);
    return {
      transaction: tx.serialize({ requireAllSignatures: false }),
      amount: params.amount as number,
      recipient: params.recipient as string,
    };
  },
);
```

## Features

### Policy Engine

Guard every transaction with composable rules:

```typescript
import {
  maxPerTransaction,
  maxPerDay,
  allowedRecipients,
  allowedPrograms,
  rateLimit,
  replayProtection,
  customPolicy,
} from '@mcp-wallet/sdk';

const server = new SigningMcpServer(info, {
  wallet: adapter,
  policies: [
    maxPerTransaction(100_000_000),           // max per tx
    maxPerDay(1_000_000_000),                 // max per session
    allowedRecipients(['addr1', 'addr2']),     // whitelist
    allowedPrograms(['TokenkegQ...']),         // program whitelist
    rateLimit(10, 60),                        // 10 calls per 60s
    replayProtection(30),                     // block duplicate tx within 30s
    customPolicy('my-rule', (ctx) => {        // your own logic
      return { allowed: true, policyName: 'my-rule' };
    }),
  ],
});
```

Policies read structured metadata from the handler's return value (`amount`, `recipient`, `programs`) — not from raw params.

### Transaction Introspection

Verify that handler-declared metadata matches the actual transaction bytes:

```typescript
import type { TransactionDecoder } from '@mcp-wallet/sdk';

const decoder: TransactionDecoder = {
  chain: 'solana',
  decode(tx) {
    // Parse the raw transaction and extract facts
    return { totalAmount: 100, recipients: ['addr'], programs: ['prog'] };
  },
};

const server = new SigningMcpServer(info, {
  wallet: adapter,
  decoders: [decoder],  // mismatches reject the transaction
});
```

### Dry-Run Mode

Preview what a transaction would do without signing:

```typescript
const result = await server.dryRun('transfer_sol', {
  recipient: 'addr',
  amount: 500_000_000,
});
// result: { allowed, policyResults, simulationResult, estimatedFee }
```

### Multi-Wallet (Multi-Chain)

One server, multiple chains:

```typescript
const server = new SigningMcpServer(info, {
  wallets: [solanaAdapter, evmAdapter],
});

server.transaction('send_sol', 'Send SOL', schema, handler, { chain: 'solana' });
server.transaction('send_eth', 'Send ETH', schema, handler, { chain: 'evm' });
```

### Key Rotation

Hot-swap wallet adapters at runtime:

```typescript
server.rotateWallet('solana', newSolanaAdapter);
// Existing tools pick up the new adapter on next call
```

### Transaction Batching

Multiple operations in one tool call:

```typescript
server.batch('multi_send', 'Send to multiple recipients', schema,
  async (params) => [
    { transaction: tx1, amount: 100, recipient: 'addr-1' },
    { transaction: tx2, amount: 200, recipient: 'addr-2' },
  ],
);
```

Each sub-transaction goes through the full pipeline independently.

### Persistence

Durable spending tracking and audit history:

```typescript
import { SqlitePersistenceAdapter } from '@mcp-wallet/sdk/persistence/sqlite';

const server = new SigningMcpServer(info, {
  wallet: adapter,
  persistence: new SqlitePersistenceAdapter('./mcp-wallet.db'),
});
```

Spending limits survive restarts. Audit history is queryable:

```typescript
const history = await server.getAuditLog().queryHistory({
  chain: 'solana',
  since: new Date('2026-03-01'),
  limit: 50,
});
```

Implement `PersistenceAdapter` for your own storage (Postgres, Redis, etc.).

### Chain Query Tools

Auto-register read-only tools from a query provider:

```typescript
const server = new SigningMcpServer(info, {
  wallet: adapter,
  queryProviders: [{
    chain: 'solana',
    getBalance: async (addr) => ({ balance: 5_000_000_000, decimals: 9 }),
    getTokenAccounts: async (addr) => [{ mint: 'USDC...', balance: 1000000 }],
    getTransactionHistory: async (addr, limit) => [{ txHash: '...', timestamp: '...', status: 'confirmed' }],
  }],
});
// Auto-creates: get_balance_solana, get_token_accounts_solana, get_transaction_history_solana
```

### Webhook Notifications

Push signing events to external systems:

```typescript
const server = new SigningMcpServer(info, {
  wallet: adapter,
  webhooks: [
    {
      url: 'https://hooks.slack.com/...',
      events: ['signed', 'denied'],  // or 'failed', 'all'
      secret: 'hmac-key',            // adds X-Signature-256 header
    },
  ],
});
```

### Audit Logging

Every signing decision is recorded:

```typescript
const server = new SigningMcpServer(info, {
  wallet: adapter,
  auditSink: (entry) => {
    console.log(`[${entry.signed ? 'SIGNED' : 'DENIED'}] ${entry.toolName} ${entry.txHash ?? ''}`);
  },
});
```

## Transport

`SigningMcpServer` extends `McpServer` — it works with any MCP transport:

```typescript
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';

const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => crypto.randomUUID() });
await server.connect(transport);
```

See [examples/solana-signing-server](examples/solana-signing-server) for a complete Express setup.

## Writing a Wallet Adapter

Implement the `WalletAdapter` interface for any chain:

```typescript
import type { WalletAdapter, SimulationResult } from '@mcp-wallet/sdk';

class MyChainAdapter implements WalletAdapter {
  readonly chain = 'my-chain';
  readonly publicKey = '0x...';

  async sign(transaction: Uint8Array): Promise<Uint8Array> { /* ... */ }
  async simulate?(transaction: Uint8Array): Promise<SimulationResult> { /* ... */ }
  async submit?(signedTransaction: Uint8Array): Promise<string> { /* ... */ }
}
```

The SDK ships `SolanaKeypairAdapter` at `@mcp-wallet/sdk/adapters/solana`.

## API Reference

### SigningMcpServer

| Method | Description |
|---|---|
| `transaction(name, desc, schema, handler, opts?)` | Register a signing tool |
| `batch(name, desc, schema, handler, opts?)` | Register a batch signing tool |
| `dryRun(toolName, params)` | Preview a transaction without signing |
| `rotateWallet(chain, newAdapter)` | Hot-swap a wallet adapter |
| `getAuditLog()` | Access audit log and history |
| `getSpendingTracker()` | Access spending data |
| `getWallet(chain)` | Get a wallet adapter by chain |
| `getSigningCapability()` | Get advertised capabilities |
| `tool()`, `resource()`, `prompt()` | Standard MCP methods (inherited) |

### Built-in Policies

| Policy | Description |
|---|---|
| `maxPerTransaction(amount)` | Max spend per transaction |
| `maxPerDay(amount)` | Max cumulative spend per session |
| `allowedRecipients(addrs)` | Recipient whitelist |
| `allowedPrograms(addrs)` | Program/contract whitelist |
| `rateLimit(maxCalls, windowSec)` | Call frequency limit |
| `replayProtection(windowSec)` | Block duplicate transactions |
| `customPolicy(name, fn)` | Your own policy logic |

## License

MIT