npm package: @rapay/mcp-server
Description: Ra Pay MCP Server for Claude Desktop and Claude Code - AI Agent Payment Infrastructure
Author: Ra Pay
Latest version: 1.3.2
Deprecated: Deprecated: Ra Pay MCP server has been sunset. AI agents should use
  the Ra Pay CLI directly (npm install -g @rapay/cli). See rapay.ai for docs.

# Ra Pay MCP Server

MCP (Model Context Protocol) server for AI agent payment automation. Enables Claude Desktop, Claude API, and ChatGPT to execute payments via Ra Pay CLI.

**Status:** Perplexity Security Review APPROVED (98% confidence)

## Features

- 6 MVP tools for payment operations
- Subprocess isolation (credentials never leave keyring)
- Response sanitization (prevents prompt injection)
- Rate limiting (1 payment/min, 10 queries/min)
- Audit logging

## Installation

### Prerequisites

- Node.js 18+
- Ra Pay CLI installed and authenticated (`ra link-bank`)

### Setup

```bash
cd rapay/mcp-server
npm install
npm run build
```

### Claude Desktop Configuration

**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`

```json
{
  "mcpServers": {
    "rapay": {
      "command": "node",
      "args": ["/Users/yourname/rapay/mcp-server/dist/index.js"]
    }
  }
}
```

**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`

```json
{
  "mcpServers": {
    "rapay": {
      "command": "node",
      "args": ["C:\\Users\\yourname\\rapay\\mcp-server\\dist\\index.js"]
    }
  }
}
```

**With custom CLI path:**

```json
{
  "mcpServers": {
    "rapay": {
      "command": "node",
      "args": ["/path/to/rapay/mcp-server/dist/index.js"],
      "env": {
        "RAPAY_CLI_PATH": "/custom/path/to/ra"
      }
    }
  }
}
```

After adding, restart Claude Desktop. You should see "rapay" in the MCP servers list.

## Tools

### Payment Operations (SENSITIVE)

| Tool | Description |
|------|-------------|
| `ra_send` | Execute a payment transaction |
| `ra_subscribe` | Create a subscription for a customer |
| `ra_refund` | Open Stripe Dashboard for refunds |

### Query Operations

| Tool | Description |
|------|-------------|
| `ra_balance` | Check available balance |
| `ra_history` | Get transaction history |
| `ra_whoami` | Check account status |

## Security

### Subprocess Isolation

MCP server spawns Ra Pay CLI as subprocess. Credentials remain in OS keyring - MCP server never sees them directly.

### Response Sanitization

All CLI output is sanitized to prevent prompt injection:
- ANSI escape sequences removed
- System markers filtered (`[SYSTEM]`, `[USER]`, etc.)
- Control characters stripped

### Rate Limiting

Defense-in-depth layer at MCP level:

| Tool | Limit |
|------|-------|
| `ra_send` | 1 per 60 seconds |
| `ra_subscribe` | 1 per 60 seconds |
| `ra_refund` | 5 per 60 seconds |
| `ra_balance` | 10 per 60 seconds |
| `ra_history` | 10 per 60 seconds |
| `ra_whoami` | 20 per 60 seconds |

Note: Backend also enforces velocity controls (account-tier daily limits).

## Privacy & Data Storage

Ra Pay is designed as a "dumb pipe" to Stripe:

**What Ra Pay stores:**
- Your user ID
- Your Stripe account ID (encrypted)
- Action logs: "payment sent", "balance checked" (no amounts)
- Transaction audit trail with Stripe transfer IDs

**What Ra Pay does NOT store:**
- Your payment amounts
- Recipient details
- Payment descriptions
- Your account balance
- Any personally identifiable information (Stripe handles KYC)

**What MCP server adds:**
- Client type tracking ("called via Claude Desktop")
- Tool call audit logs (same privacy level as above)
- No new PII storage

## Configuration

### Environment Variables

| Variable | Description | Default |
|----------|-------------|---------|
| `RAPAY_CLI_PATH` | Path to Ra Pay CLI executable | `ra` |

### Audit Logging

Logs are written to `~/.rapay/mcp-audit.log` with 7-day retention:
- Tool name, timestamp, duration
- Result (success/error/rate_limited)
- Sanitized inputs (amounts redacted, emails masked)

## Error Handling

### Error Codes

| Code | Description | Retryable |
|------|-------------|-----------|
| `RATE_LIMIT_EXCEEDED` | MCP rate limit hit | No (wait) |
| `CLI_NOT_FOUND` | Ra Pay CLI not installed | No |
| `TOS_ACCEPTANCE_REQUIRED` | ToS not accepted | No |
| `ACCOUNT_NOT_LINKED` | Stripe account not linked | No |
| `VELOCITY_EXCEEDED` | Daily limit exceeded | No |
| `TIMEOUT` | Request timed out | Yes |
| `NETWORK_ERROR` | Network connectivity issue | Yes |
| `EXECUTION_FAILED` | Generic CLI error | No |

### Rate Limit Error

```json
{
  "error": "rate_limit_exceeded",
  "code": "RATE_LIMIT_EXCEEDED",
  "message": "Too many requests. Please wait 60 seconds.",
  "retry_after_seconds": 60,
  "retryable": false
}
```

### CLI Not Found Error

```json
{
  "error": "cli_not_found",
  "code": "CLI_NOT_FOUND",
  "message": "Ra Pay CLI not found. Please install it first.",
  "retryable": false
}
```

### ToS Required Error

```json
{
  "error": "tos_required",
  "code": "TOS_ACCEPTANCE_REQUIRED",
  "message": "Terms of Service must be accepted. Run 'ra accept-tos' first.",
  "retryable": false
}
```

### For Claude API Callers: Exponential Backoff

If you receive `RATE_LIMIT_EXCEEDED`, implement exponential backoff:

```typescript
const maxRetries = 3;
let delay = 60; // seconds

for (let attempt = 0; attempt < maxRetries; attempt++) {
  try {
    return await mcp.callTool('ra_send', params);
  } catch (error) {
    if (error.code === 'RATE_LIMIT_EXCEEDED') {
      console.log(`Rate limited. Waiting ${delay}s before retry...`);
      await sleep(delay * 1000);
      delay *= 2; // exponential backoff
    } else {
      throw error;
    }
  }
}

// DO NOT:
// - Retry immediately (wastes time, still rate limited)
// - Retry more than 3 times (indicates genuine rate limit)
// - Ignore retry_after_seconds field
```

Note: MCP rate limiting is client-side defense-in-depth. Backend also enforces velocity controls per account tier.

## Data Flow

```
You (Claude Desktop/API)
    |
    v
MCP Server (this package)
    | - Logs tool calls (no amounts/PII)
    | - Rate limits requests
    | - Sanitizes responses
    v
Ra Pay CLI (subprocess)
    | - Credentials in OS keyring
    | - Adds replay protection
    v
Ra Pay Backend
    | - Validates requests
    | - Enforces velocity limits
    v
Stripe API
    | - Owns all PII
    | - Processes payments
    v
Recipient's Bank
```

All sensitive data flows directly to Stripe. Ra Pay only records that an action occurred.

## Security Model

This section documents the security posture of the published npm package.

### What's Published to npm

| Category | Included | Excluded |
|----------|----------|----------|
| Compiled JavaScript | Yes | - |
| TypeScript declarations | Yes | - |
| Source maps (.js.map) | No | Excluded for code privacy |
| Source code (src/) | No | Development only |

### Intentionally Public Information

| Metadata | Value | Rationale |
|----------|-------|-----------|
| Repository | github.com/Ra-Pay-AI/rapay | Open source by design |
| Author | Ra Pay | Company name |
| License | MIT | Standard permissive license |
| Keywords | mcp, payments, stripe, claude | Discoverability |

### Security Features Exposed (By Design)

These are documented for users and do not represent vulnerabilities:

- **Rate limiting rules** - Users need to know limits to implement backoff
- **Error codes** - Required for proper error handling
- **Tool schemas** - Required by MCP protocol specification
- **Audit log location** (~/.rapay/mcp-audit.log) - Users may need to inspect

### What Is NOT Exposed

| Category | Protection |
|----------|------------|
| API keys/secrets | Never in code (OS keyring only) |
| Backend URLs | Only public rapay.ai endpoints |
| User data | Subprocess isolation, never in MCP process |
| Payment amounts | Redacted as `[redacted]` in all logs |
| Email addresses | Masked (j***@example.com) in audit logs |

### Subprocess Isolation Model

```
┌─────────────────────┐
│  AI Agent (Claude)  │
└─────────┬───────────┘
          │ MCP Protocol (stdio)
          ▼
┌─────────────────────┐
│  MCP Server (npm)   │  ← No credentials here
│  - Rate limiting    │
│  - Input validation │
│  - Response sanitize│
└─────────┬───────────┘
          │ Spawns subprocess
          ▼
┌─────────────────────┐
│  Ra Pay CLI         │  ← Credentials in OS keyring
│  - Session tokens   │
│  - Stripe API calls │
└─────────────────────┘
```

The MCP server never has access to credentials. All sensitive operations are delegated to the CLI subprocess, which reads credentials directly from the OS keyring.

## Development

```bash
npm run dev    # Watch mode
npm run build  # Build
npm run lint   # Lint
npm run test   # Test
```

## License

MIT