@qvapay/mcp-server 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # @qvapay/mcp-server
2
+
3
+ MCP (Model Context Protocol) server for [QvaPay](https://qvapay.com) — enabling AI assistants to interact with the QvaPay payment platform.
4
+
5
+ ## Features
6
+
7
+ - **31 tools** covering market data, P2P trading, merchant, and user operations
8
+ - **Market Data** (public, no auth): Crypto prices, coin listings, P2P averages, stats, and rankings
9
+ - **P2P Trading**: Create/edit/cancel offers, apply, chat, rate counterparties
10
+ - **Merchant API**: Create invoices, charge users, list transactions
11
+ - **User API**: Check balance, transfer money, search users, manage contacts
12
+ - **MCP Resources**: Quick access to profile and balance
13
+ - **Guided Prompts**: Step-by-step workflows for invoices, transfers, and account summaries
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npx @qvapay/mcp-server
19
+ ```
20
+
21
+ Or install globally:
22
+
23
+ ```bash
24
+ npm install -g @qvapay/mcp-server
25
+ ```
26
+
27
+ ## Configuration
28
+
29
+ Set environment variables based on which features you need:
30
+
31
+ ### Market Tools (5 tools) — No auth required
32
+
33
+ Market tools are always available. They provide public crypto prices, P2P averages, platform stats, and rankings.
34
+
35
+ ### Merchant Tools (7 tools)
36
+
37
+ ```bash
38
+ export QVAPAY_APP_ID="your-app-uuid"
39
+ export QVAPAY_APP_SECRET="your-app-secret"
40
+ ```
41
+
42
+ Get these from your [QvaPay Developer Dashboard](https://qvapay.com/apps).
43
+
44
+ ### User + P2P Tools (19 tools)
45
+
46
+ ```bash
47
+ export QVAPAY_API_TOKEN="your-bearer-token"
48
+ ```
49
+
50
+ Get this from your [QvaPay API Settings](https://qvapay.com/settings/api).
51
+
52
+ ### Optional
53
+
54
+ ```bash
55
+ export QVAPAY_API_URL="https://qvapay.com/api" # Custom API URL
56
+ ```
57
+
58
+ The server starts with whichever credentials are available. Missing credentials simply means those tools won't be registered. Market tools are always available.
59
+
60
+ ## Usage
61
+
62
+ ### Claude Desktop
63
+
64
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "qvapay": {
70
+ "command": "npx",
71
+ "args": ["@qvapay/mcp-server"],
72
+ "env": {
73
+ "QVAPAY_APP_ID": "your-app-uuid",
74
+ "QVAPAY_APP_SECRET": "your-app-secret",
75
+ "QVAPAY_API_TOKEN": "your-bearer-token"
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### Claude Code
83
+
84
+ Add to `.claude/settings.json` or `~/.claude.json`:
85
+
86
+ ```json
87
+ {
88
+ "mcpServers": {
89
+ "qvapay": {
90
+ "command": "npx",
91
+ "args": ["@qvapay/mcp-server"],
92
+ "env": {
93
+ "QVAPAY_APP_ID": "your-app-uuid",
94
+ "QVAPAY_APP_SECRET": "your-app-secret",
95
+ "QVAPAY_API_TOKEN": "your-bearer-token"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ ### If installed globally via npm
103
+
104
+ ```json
105
+ {
106
+ "mcpServers": {
107
+ "qvapay": {
108
+ "command": "qvapay-mcp",
109
+ "env": {
110
+ "QVAPAY_APP_ID": "your-app-uuid",
111
+ "QVAPAY_APP_SECRET": "your-app-secret",
112
+ "QVAPAY_API_TOKEN": "your-bearer-token"
113
+ }
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## Tools Reference
120
+
121
+ ### Market Tools (public, no auth)
122
+
123
+ | Tool | Description |
124
+ |------|-------------|
125
+ | `qvapay_coins` | List all cryptocurrencies with prices, fees, and limits |
126
+ | `qvapay_price_history` | Get price history for a cryptocurrency (1H to ALL) |
127
+ | `qvapay_p2p_averages` | Current P2P exchange rate averages for all coins |
128
+ | `qvapay_p2p_stats` | Platform statistics: volume, trades, growth |
129
+ | `qvapay_p2p_ranking` | Weekly P2P trading leaderboard (top 20) |
130
+
131
+ ### Merchant Tools (requires APP_ID + APP_SECRET)
132
+
133
+ | Tool | Description |
134
+ |------|-------------|
135
+ | `qvapay_app_info` | Get merchant app details |
136
+ | `qvapay_app_balance` | Get merchant balance |
137
+ | `qvapay_create_invoice` | Create payment invoice |
138
+ | `qvapay_modify_invoice` | Modify pending invoice |
139
+ | `qvapay_charge_user` | Charge authorized user |
140
+ | `qvapay_app_transactions` | List app transactions |
141
+ | `qvapay_app_transaction_detail` | Get transaction detail |
142
+
143
+ ### User Tools (requires API_TOKEN)
144
+
145
+ | Tool | Description |
146
+ |------|-------------|
147
+ | `qvapay_user_profile` | Get profile + balance |
148
+ | `qvapay_user_extended` | Extended profile with P2P stats |
149
+ | `qvapay_list_transactions` | List transactions with filters |
150
+ | `qvapay_transaction_detail` | Get transaction detail |
151
+ | `qvapay_transfer` | Transfer money (requires PIN) |
152
+ | `qvapay_search_users` | Search users |
153
+ | `qvapay_payment_methods` | List payment methods |
154
+ | `qvapay_contacts` | List contacts |
155
+
156
+ ### P2P Trading Tools (requires API_TOKEN)
157
+
158
+ | Tool | Description |
159
+ |------|-------------|
160
+ | `qvapay_p2p_list` | Browse P2P offers with filters |
161
+ | `qvapay_p2p_detail` | Get full details of a P2P offer |
162
+ | `qvapay_p2p_create` | Create a new buy/sell offer |
163
+ | `qvapay_p2p_edit` | Edit an open offer you own |
164
+ | `qvapay_p2p_apply` | Apply to an open offer |
165
+ | `qvapay_p2p_cancel` | Cancel a P2P offer |
166
+ | `qvapay_p2p_paid` | Mark offer as paid (buyer action) |
167
+ | `qvapay_p2p_received` | Confirm payment received (seller action) |
168
+ | `qvapay_p2p_chat_read` | Read chat messages from a trade |
169
+ | `qvapay_p2p_chat_send` | Send a chat message in a trade |
170
+ | `qvapay_p2p_rate` | Rate your counterparty (1-5 stars) |
171
+
172
+ ### Resources
173
+
174
+ | Resource | URI | Description |
175
+ |----------|-----|-------------|
176
+ | Profile | `qvapay://profile` | Full user profile with balance |
177
+ | Balance | `qvapay://balance` | Quick balance check |
178
+
179
+ ### Prompts
180
+
181
+ | Prompt | Description |
182
+ |--------|-------------|
183
+ | `create-invoice` | Guided invoice creation workflow |
184
+ | `transfer` | Guided money transfer workflow |
185
+ | `account-summary` | Complete account overview |
186
+
187
+ ## Testing
188
+
189
+ Use the MCP Inspector to verify the server:
190
+
191
+ ```bash
192
+ npx @modelcontextprotocol/inspector node src/index.js
193
+ ```
194
+
195
+ ## Security
196
+
197
+ - The transfer tool requires a PIN and will always prompt the user
198
+ - P2P create/apply operations that move funds require KYC verification
199
+ - Credentials are read from environment variables only
200
+ - The server is a thin HTTP client — no data is cached or stored
201
+ - All communication uses HTTPS
202
+
203
+ ## License
204
+
205
+ MIT
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@qvapay/mcp-server",
3
+ "version": "2.0.0",
4
+ "description": "MCP server for QvaPay — AI-native payment platform with P2P trading, market data, and crypto payments",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "qvapay-mcp": "src/index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/index.js",
12
+ "inspect": "npx @modelcontextprotocol/inspector node src/index.js"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "qvapay",
17
+ "payments",
18
+ "p2p",
19
+ "crypto",
20
+ "ai",
21
+ "claude",
22
+ "model-context-protocol",
23
+ "cuba",
24
+ "fintech"
25
+ ],
26
+ "license": "MIT",
27
+ "dependencies": {
28
+ "@modelcontextprotocol/sdk": "^1.12.0",
29
+ "zod": "^3.24.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=18.0.0"
33
+ }
34
+ }
package/server.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "$schema": "https://registry.modelcontextprotocol.io/schemas/server.json",
3
+ "name": "com.qvapay/mcp-server",
4
+ "description": "QvaPay — AI-native payment platform. Market data, P2P trading, invoices, transfers, and crypto payments for Cuba and Latin America.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/qvapay/mcp-server"
8
+ },
9
+ "version_detail": {
10
+ "version": "2.0.0",
11
+ "release_date": "2026-03-15"
12
+ },
13
+ "packages": [
14
+ {
15
+ "registry_name": "npm",
16
+ "name": "@qvapay/mcp-server",
17
+ "version": "2.0.0",
18
+ "runtime": "node",
19
+ "runtime_arguments": [],
20
+ "environment_variables": [
21
+ {
22
+ "name": "QVAPAY_APP_ID",
23
+ "description": "Merchant app UUID from QvaPay Developer Dashboard",
24
+ "required": false
25
+ },
26
+ {
27
+ "name": "QVAPAY_APP_SECRET",
28
+ "description": "Merchant app secret from QvaPay Developer Dashboard",
29
+ "required": false
30
+ },
31
+ {
32
+ "name": "QVAPAY_API_TOKEN",
33
+ "description": "User bearer token from QvaPay API Settings",
34
+ "required": false
35
+ }
36
+ ]
37
+ }
38
+ ],
39
+ "tools": [
40
+ { "name": "qvapay_coins", "description": "List all cryptocurrencies with prices, fees, and limits" },
41
+ { "name": "qvapay_price_history", "description": "Get price history for a cryptocurrency" },
42
+ { "name": "qvapay_p2p_averages", "description": "Current P2P exchange rate averages" },
43
+ { "name": "qvapay_p2p_stats", "description": "Platform statistics: volume, trades, growth" },
44
+ { "name": "qvapay_p2p_ranking", "description": "Weekly P2P trading leaderboard" },
45
+ { "name": "qvapay_app_info", "description": "Get merchant app details" },
46
+ { "name": "qvapay_app_balance", "description": "Get merchant balance" },
47
+ { "name": "qvapay_create_invoice", "description": "Create payment invoice" },
48
+ { "name": "qvapay_modify_invoice", "description": "Modify pending invoice" },
49
+ { "name": "qvapay_charge_user", "description": "Charge authorized user" },
50
+ { "name": "qvapay_app_transactions", "description": "List app transactions" },
51
+ { "name": "qvapay_app_transaction_detail", "description": "Get transaction detail" },
52
+ { "name": "qvapay_user_profile", "description": "Get profile + balance" },
53
+ { "name": "qvapay_user_extended", "description": "Extended profile with P2P stats" },
54
+ { "name": "qvapay_list_transactions", "description": "List transactions with filters" },
55
+ { "name": "qvapay_transaction_detail", "description": "Get transaction detail" },
56
+ { "name": "qvapay_transfer", "description": "Transfer money (requires PIN)" },
57
+ { "name": "qvapay_search_users", "description": "Search users" },
58
+ { "name": "qvapay_payment_methods", "description": "List payment methods" },
59
+ { "name": "qvapay_contacts", "description": "List contacts" },
60
+ { "name": "qvapay_p2p_list", "description": "Browse P2P offers with filters" },
61
+ { "name": "qvapay_p2p_detail", "description": "Get full details of a P2P offer" },
62
+ { "name": "qvapay_p2p_create", "description": "Create a new buy/sell P2P offer" },
63
+ { "name": "qvapay_p2p_edit", "description": "Edit an open P2P offer" },
64
+ { "name": "qvapay_p2p_apply", "description": "Apply to an open P2P offer" },
65
+ { "name": "qvapay_p2p_cancel", "description": "Cancel a P2P offer" },
66
+ { "name": "qvapay_p2p_paid", "description": "Mark P2P offer as paid" },
67
+ { "name": "qvapay_p2p_received", "description": "Confirm payment received" },
68
+ { "name": "qvapay_p2p_chat_read", "description": "Read P2P trade chat messages" },
69
+ { "name": "qvapay_p2p_chat_send", "description": "Send P2P trade chat message" },
70
+ { "name": "qvapay_p2p_rate", "description": "Rate counterparty after trade" }
71
+ ]
72
+ }
package/src/client.js ADDED
@@ -0,0 +1,100 @@
1
+ // QvaPay HTTP Client
2
+ // Handles both merchant (app-id/app-secret) and user (bearer token) auth
3
+
4
+ import { config } from './config.js'
5
+
6
+ class QvaPayClient {
7
+
8
+ // Public API call (no auth required)
9
+ async public(endpoint, { query } = {}) {
10
+
11
+ let url = `${config.apiUrl}${endpoint}`
12
+ if (query) {
13
+ const params = new URLSearchParams()
14
+ for (const [key, value] of Object.entries(query)) {
15
+ if (value !== undefined && value !== null && value !== '') {
16
+ params.set(key, String(value))
17
+ }
18
+ }
19
+ const qs = params.toString()
20
+ if (qs) url += `?${qs}`
21
+ }
22
+
23
+ const res = await fetch(url)
24
+ return this._handleResponse(res)
25
+ }
26
+
27
+ // Merchant API call (POST with app-id/app-secret headers)
28
+ async merchant(endpoint, body = {}) {
29
+
30
+ if (!config.hasMerchantAuth) { throw new Error('Merchant credentials not configured. Set QVAPAY_APP_ID and QVAPAY_APP_SECRET.') }
31
+
32
+ const url = `${config.apiUrl}${endpoint}`
33
+ const res = await fetch(url, {
34
+ method: 'POST',
35
+ headers: {
36
+ 'Content-Type': 'application/json',
37
+ 'app-id': config.appId,
38
+ 'app-secret': config.appSecret,
39
+ },
40
+ body: JSON.stringify(body),
41
+ })
42
+
43
+ return this._handleResponse(res)
44
+ }
45
+
46
+ // User API call with bearer token
47
+ async user(endpoint, { method = 'GET', body, query } = {}) {
48
+
49
+ if (!config.hasUserAuth) { throw new Error('User token not configured. Set QVAPAY_API_TOKEN.') }
50
+
51
+ let url = `${config.apiUrl}${endpoint}`
52
+ if (query) {
53
+ const params = new URLSearchParams()
54
+ for (const [key, value] of Object.entries(query)) {
55
+ if (value !== undefined && value !== null && value !== '') {
56
+ params.set(key, String(value))
57
+ }
58
+ }
59
+ const qs = params.toString()
60
+ if (qs) url += `?${qs}`
61
+ }
62
+
63
+ const options = {
64
+ method,
65
+ headers: {
66
+ 'Content-Type': 'application/json',
67
+ 'Authorization': `Bearer ${config.apiToken}`,
68
+ },
69
+ }
70
+
71
+ if (body && method !== 'GET') {
72
+ options.body = JSON.stringify(body)
73
+ }
74
+
75
+ const res = await fetch(url, options)
76
+ return this._handleResponse(res)
77
+ }
78
+
79
+ async _handleResponse(res) {
80
+ const text = await res.text()
81
+
82
+ let data
83
+ try {
84
+ data = JSON.parse(text)
85
+ } catch {
86
+ data = text
87
+ }
88
+
89
+ if (!res.ok) {
90
+ const message = typeof data === 'object' && data !== null
91
+ ? data.message || data.error || JSON.stringify(data)
92
+ : String(data)
93
+ throw new Error(`QvaPay API error (${res.status}): ${message}`)
94
+ }
95
+
96
+ return data
97
+ }
98
+ }
99
+
100
+ export const client = new QvaPayClient()
package/src/config.js ADDED
@@ -0,0 +1,23 @@
1
+ // QvaPay MCP Server Configuration
2
+ // Loads credentials from environment variables
3
+
4
+ export const config = {
5
+ // Merchant API credentials (v2 endpoints)
6
+ appId: process.env.QVAPAY_APP_ID || '',
7
+ appSecret: process.env.QVAPAY_APP_SECRET || '',
8
+
9
+ // User API token (bearer auth)
10
+ apiToken: process.env.QVAPAY_API_TOKEN || '',
11
+
12
+ // Base URL for QvaPay API
13
+ apiUrl: (process.env.QVAPAY_API_URL || 'https://qvapay.com/api').replace(/\/$/, ''),
14
+
15
+ // Auth availability checks
16
+ get hasMerchantAuth() {
17
+ return Boolean(this.appId && this.appSecret)
18
+ },
19
+
20
+ get hasUserAuth() {
21
+ return Boolean(this.apiToken)
22
+ },
23
+ }
package/src/index.js ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+
3
+ // QvaPay MCP Server
4
+ // AI-native payment platform integration via Model Context Protocol
5
+
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
7
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
8
+ import { config } from './config.js'
9
+ import { registerMerchantTools } from './tools/merchant.js'
10
+ import { registerUserTools } from './tools/user.js'
11
+ import { registerMarketTools } from './tools/market.js'
12
+ import { registerP2PTools } from './tools/p2p.js'
13
+ import { registerResources } from './resources/profile.js'
14
+ import { registerPrompts } from './prompts/index.js'
15
+
16
+ const server = new McpServer({
17
+ name: 'qvapay',
18
+ version: '2.0.0',
19
+ description: 'QvaPay — AI-native payment platform. Market data, P2P trading, invoices, transfers, and crypto payments.',
20
+ })
21
+
22
+ // Register tools based on available credentials
23
+ if (config.hasMerchantAuth) {
24
+ registerMerchantTools(server)
25
+ console.error('[qvapay-mcp] Merchant tools enabled (app-id: ' + config.appId.slice(0, 8) + '...)')
26
+ }
27
+
28
+ if (config.hasUserAuth) {
29
+ registerUserTools(server)
30
+ registerP2PTools(server)
31
+ registerResources(server)
32
+ console.error('[qvapay-mcp] User + P2P tools enabled')
33
+ }
34
+
35
+ // Market tools are always available (public endpoints, no auth)
36
+ registerMarketTools(server)
37
+ console.error('[qvapay-mcp] Market tools enabled (public)')
38
+
39
+ // Prompts are always available
40
+ registerPrompts(server)
41
+
42
+ if (!config.hasMerchantAuth && !config.hasUserAuth) {
43
+ console.error('[qvapay-mcp] WARNING: No credentials configured. Set QVAPAY_APP_ID + QVAPAY_APP_SECRET for merchant tools, or QVAPAY_API_TOKEN for user tools.')
44
+ }
45
+
46
+ // Start stdio transport
47
+ const transport = new StdioServerTransport()
48
+ await server.connect(transport)
49
+
50
+ console.error('[qvapay-mcp] Server started on stdio')
@@ -0,0 +1,98 @@
1
+ // MCP Prompts — Guided workflows for common QvaPay operations
2
+
3
+ import { z } from 'zod'
4
+
5
+ export function registerPrompts(server) {
6
+
7
+ // Create invoice workflow
8
+ server.prompt(
9
+ 'create-invoice',
10
+ 'Guided workflow to create a payment invoice',
11
+ {
12
+ amount: z.string().optional().describe('Amount in USD'),
13
+ description: z.string().optional().describe('Payment description'),
14
+ },
15
+ ({ amount, description } = {}) => {
16
+ const parts = []
17
+
18
+ if (amount && description) {
19
+ parts.push(`Create a QvaPay invoice for $${amount} with description: "${description}".`)
20
+ parts.push('Generate a unique remote_id and use the qvapay_create_invoice tool.')
21
+ } else if (amount) {
22
+ parts.push(`Create a QvaPay invoice for $${amount}.`)
23
+ parts.push('Ask me for a description, then generate a unique remote_id and use the qvapay_create_invoice tool.')
24
+ } else {
25
+ parts.push('Help me create a QvaPay payment invoice.')
26
+ parts.push('Ask me for: 1) Amount in USD, 2) Description of what this payment is for.')
27
+ parts.push('Then generate a unique remote_id and use the qvapay_create_invoice tool.')
28
+ }
29
+
30
+ parts.push('After creating the invoice, show me the payment URL.')
31
+
32
+ return {
33
+ messages: [{
34
+ role: 'user',
35
+ content: { type: 'text', text: parts.join('\n') },
36
+ }],
37
+ }
38
+ }
39
+ )
40
+
41
+ // Transfer workflow
42
+ server.prompt(
43
+ 'transfer',
44
+ 'Guided workflow to transfer money to another user',
45
+ {
46
+ to: z.string().optional().describe('Recipient username, email, or UUID'),
47
+ amount: z.string().optional().describe('Amount in USD'),
48
+ },
49
+ ({ to, amount } = {}) => {
50
+ const parts = []
51
+
52
+ if (to && amount) {
53
+ parts.push(`Transfer $${amount} to ${to} on QvaPay.`)
54
+ } else if (to) {
55
+ parts.push(`Transfer money to ${to} on QvaPay. Ask me for the amount.`)
56
+ } else {
57
+ parts.push('Help me transfer money on QvaPay.')
58
+ parts.push('Ask me for: 1) Recipient (username, email, or phone), 2) Amount in USD.')
59
+ }
60
+
61
+ parts.push('Before executing, verify the recipient using qvapay_search_users.')
62
+ parts.push('IMPORTANT: Ask me for my PIN before calling qvapay_transfer. Never guess the PIN.')
63
+ parts.push('After the transfer, confirm the transaction details.')
64
+
65
+ return {
66
+ messages: [{
67
+ role: 'user',
68
+ content: { type: 'text', text: parts.join('\n') },
69
+ }],
70
+ }
71
+ }
72
+ )
73
+
74
+ // Account summary
75
+ server.prompt(
76
+ 'account-summary',
77
+ 'Get a complete summary of the QvaPay account',
78
+ {},
79
+ () => ({
80
+ messages: [{
81
+ role: 'user',
82
+ content: {
83
+ type: 'text',
84
+ text: [
85
+ 'Give me a complete summary of my QvaPay account.',
86
+ 'Use qvapay_user_profile to get my profile and balance.',
87
+ 'Use qvapay_list_transactions with include_total=true to get recent transactions.',
88
+ 'Present a clear summary with:',
89
+ '- Account info (username, verification status, golden check)',
90
+ '- Current balance',
91
+ '- Recent transaction activity (last 5-10 transactions)',
92
+ '- Total incoming vs outgoing if visible from recent transactions',
93
+ ].join('\n'),
94
+ },
95
+ }],
96
+ })
97
+ )
98
+ }
@@ -0,0 +1,66 @@
1
+ // MCP Resources — User profile and balance
2
+ // Resources are read-only data that AI can access without explicit tool calls
3
+
4
+ import { client } from '../client.js'
5
+
6
+ export function registerResources(server) {
7
+
8
+ // User profile resource
9
+ server.resource(
10
+ 'profile',
11
+ 'qvapay://profile',
12
+ { description: 'Current QvaPay user profile with balance and account details', mimeType: 'application/json' },
13
+ async () => {
14
+ try {
15
+ const data = await client.user('/user')
16
+ return {
17
+ contents: [{
18
+ uri: 'qvapay://profile',
19
+ mimeType: 'application/json',
20
+ text: JSON.stringify(data, null, 2),
21
+ }],
22
+ }
23
+ } catch (error) {
24
+ return {
25
+ contents: [{
26
+ uri: 'qvapay://profile',
27
+ mimeType: 'text/plain',
28
+ text: `Error fetching profile: ${error.message}`,
29
+ }],
30
+ }
31
+ }
32
+ }
33
+ )
34
+
35
+ // Balance resource (quick access)
36
+ server.resource(
37
+ 'balance',
38
+ 'qvapay://balance',
39
+ { description: 'Current QvaPay account balance in USD', mimeType: 'application/json' },
40
+ async () => {
41
+ try {
42
+ const data = await client.user('/user')
43
+ const balance = {
44
+ balance: data.balance,
45
+ username: data.username,
46
+ golden_check: data.golden_check,
47
+ }
48
+ return {
49
+ contents: [{
50
+ uri: 'qvapay://balance',
51
+ mimeType: 'application/json',
52
+ text: JSON.stringify(balance, null, 2),
53
+ }],
54
+ }
55
+ } catch (error) {
56
+ return {
57
+ contents: [{
58
+ uri: 'qvapay://balance',
59
+ mimeType: 'text/plain',
60
+ text: `Error fetching balance: ${error.message}`,
61
+ }],
62
+ }
63
+ }
64
+ }
65
+ )
66
+ }
@@ -0,0 +1,96 @@
1
+ // Market Tools — Public endpoints for crypto prices, coins, and P2P averages (no auth required)
2
+
3
+ import { z } from 'zod'
4
+ import { client } from '../client.js'
5
+
6
+ export function registerMarketTools(server) {
7
+
8
+ // List available coins
9
+ server.tool(
10
+ 'qvapay_coins',
11
+ 'List all cryptocurrencies available on QvaPay with prices, fees, and deposit/withdrawal limits. Optionally filter by feature.',
12
+ {
13
+ enabled_in: z.boolean().optional().describe('Filter coins enabled for deposits'),
14
+ enabled_out: z.boolean().optional().describe('Filter coins enabled for withdrawals'),
15
+ enabled_p2p: z.boolean().optional().describe('Filter coins enabled for P2P trading'),
16
+ trade: z.boolean().optional().describe('Filter tradeable coins'),
17
+ },
18
+ async (params = {}) => {
19
+ try {
20
+ const query = {}
21
+ if (params.enabled_in) query.enabled_in = '1'
22
+ if (params.enabled_out) query.enabled_out = '1'
23
+ if (params.enabled_p2p) query.enabled_p2p = '1'
24
+ if (params.trade) query.trade = '1'
25
+ const data = await client.public('/coins/v2', { query })
26
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
27
+ } catch (error) {
28
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
29
+ }
30
+ }
31
+ )
32
+
33
+ // Get crypto price history
34
+ server.tool(
35
+ 'qvapay_price_history',
36
+ 'Get price history for a cryptocurrency. Returns time-series data points with timestamps and USD prices.',
37
+ {
38
+ coin: z.string().describe('Coin ticker symbol (e.g., BTC, ETH, TRX, SOL, USDT)'),
39
+ timeframe: z.enum(['1H', '24H', '1W', '1M', '1Y', 'ALL']).optional().describe('Time range (default: 24H). 1H=last hour, 24H=last day, 1W=last week, 1M=last month, 1Y=last year, ALL=all time'),
40
+ },
41
+ async ({ coin, timeframe }) => {
42
+ try {
43
+ const query = timeframe ? { timeframe } : undefined
44
+ const data = await client.public(`/coins/price-history/${coin.toUpperCase()}`, { query })
45
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
46
+ } catch (error) {
47
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
48
+ }
49
+ }
50
+ )
51
+
52
+ // Get P2P market averages
53
+ server.tool(
54
+ 'qvapay_p2p_averages',
55
+ 'Get current P2P exchange rate averages for all coins. Shows buy/sell averages and offer counts based on completed trades in the last 24 hours. This is the best way to check current exchange rates on QvaPay.',
56
+ {},
57
+ async () => {
58
+ try {
59
+ const data = await client.public('/p2p/averages')
60
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
61
+ } catch (error) {
62
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
63
+ }
64
+ }
65
+ )
66
+
67
+ // Get P2P platform stats
68
+ server.tool(
69
+ 'qvapay_p2p_stats',
70
+ 'Get QvaPay P2P platform statistics: volume, trade counts, monthly trends, user growth, and year-over-year metrics.',
71
+ {},
72
+ async () => {
73
+ try {
74
+ const data = await client.public('/p2p/stats')
75
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
76
+ } catch (error) {
77
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
78
+ }
79
+ }
80
+ )
81
+
82
+ // Get P2P weekly ranking
83
+ server.tool(
84
+ 'qvapay_p2p_ranking',
85
+ 'Get the weekly P2P trading leaderboard. Shows top 20 traders ranked by volume, trade count, unique partners, and ratings.',
86
+ {},
87
+ async () => {
88
+ try {
89
+ const data = await client.public('/p2p/ranking')
90
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
91
+ } catch (error) {
92
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
93
+ }
94
+ }
95
+ )
96
+ }
@@ -0,0 +1,145 @@
1
+ // Merchant Tools — QvaPay v2 API (requires QVAPAY_APP_ID + QVAPAY_APP_SECRET)
2
+
3
+ import { z } from 'zod'
4
+ import { client } from '../client.js'
5
+
6
+ export function registerMerchantTools(server) {
7
+
8
+ // Get merchant app info
9
+ server.tool(
10
+ 'qvapay_app_info',
11
+ 'Get merchant app details (name, URL, description, logo, status)',
12
+ {},
13
+ async () => {
14
+ try {
15
+ const data = await client.merchant('/v2/info')
16
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
17
+ } catch (error) {
18
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
19
+ }
20
+ }
21
+ )
22
+
23
+ // Get merchant balance
24
+ server.tool(
25
+ 'qvapay_app_balance',
26
+ 'Get merchant app balance in USD',
27
+ {},
28
+ async () => {
29
+ try {
30
+ const data = await client.merchant('/v2/balance')
31
+ return { content: [{ type: 'text', text: JSON.stringify({ balance: data }, null, 2) }] }
32
+ } catch (error) {
33
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
34
+ }
35
+ }
36
+ )
37
+
38
+ // Create payment invoice
39
+ server.tool(
40
+ 'qvapay_create_invoice',
41
+ 'Create a payment invoice. Returns a payment URL that can be shared with the payer.',
42
+ {
43
+ amount: z.number().positive().max(100000).describe('Amount in USD to charge'),
44
+ description: z.string().min(1).max(255).describe('Description of the payment'),
45
+ remote_id: z.string().min(1).max(255).describe('Your external reference ID for this payment'),
46
+ webhook: z.string().url().optional().describe('URL to receive payment webhook notifications'),
47
+ products: z.array(z.object({
48
+ name: z.string().min(1).describe('Product name'),
49
+ price: z.number().positive().describe('Product price'),
50
+ quantity: z.number().int().positive().optional().describe('Quantity (default: 1)'),
51
+ })).optional().describe('List of products in this invoice'),
52
+ expire_at: z.string().optional().describe('Expiration date in ISO 8601 format (must be in the future)'),
53
+ },
54
+ async (params) => {
55
+ try {
56
+ const data = await client.merchant('/v2/create_invoice', params)
57
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
58
+ } catch (error) {
59
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
60
+ }
61
+ }
62
+ )
63
+
64
+ // Modify pending invoice
65
+ server.tool(
66
+ 'qvapay_modify_invoice',
67
+ 'Modify a pending invoice. Only works if the invoice has not been paid yet.',
68
+ {
69
+ transaction_uuid: z.string().describe('UUID of the transaction/invoice to modify'),
70
+ amount: z.number().positive().max(100000).optional().describe('New amount in USD'),
71
+ description: z.string().min(1).max(255).optional().describe('New description'),
72
+ remote_id: z.string().min(1).max(255).optional().describe('New external reference ID'),
73
+ webhook: z.string().optional().describe('New webhook URL (empty string to clear)'),
74
+ products: z.array(z.object({
75
+ name: z.string().min(1).describe('Product name'),
76
+ price: z.number().positive().describe('Product price'),
77
+ quantity: z.number().int().positive().optional().describe('Quantity (default: 1)'),
78
+ })).optional().describe('Updated product list'),
79
+ expire_at: z.string().optional().describe('New expiration date in ISO 8601 format'),
80
+ },
81
+ async (params) => {
82
+ try {
83
+ const data = await client.merchant('/v2/modify_invoice', params)
84
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
85
+ } catch (error) {
86
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
87
+ }
88
+ }
89
+ )
90
+
91
+ // Charge an authorized user
92
+ server.tool(
93
+ 'qvapay_charge_user',
94
+ 'Charge a user who has authorized payments to your app. Deducts from their balance immediately.',
95
+ {
96
+ amount: z.number().positive().max(100000).describe('Amount in USD to charge'),
97
+ user_uuid: z.string().describe('UUID of the user to charge'),
98
+ description: z.string().min(1).max(255).describe('Description of the charge'),
99
+ remote_id: z.string().min(1).max(255).describe('Your external reference ID'),
100
+ },
101
+ async (params) => {
102
+ try {
103
+ const data = await client.merchant('/v2/charge', params)
104
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
105
+ } catch (error) {
106
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
107
+ }
108
+ }
109
+ )
110
+
111
+ // List app transactions
112
+ server.tool(
113
+ 'qvapay_app_transactions',
114
+ 'List transactions for the merchant app, paginated.',
115
+ {
116
+ page: z.number().int().positive().optional().describe('Page number (default: 1)'),
117
+ take: z.number().int().positive().max(100).optional().describe('Results per page (default: 30, max: 100)'),
118
+ },
119
+ async (params) => {
120
+ try {
121
+ const data = await client.merchant('/v2/transactions', params)
122
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
123
+ } catch (error) {
124
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
125
+ }
126
+ }
127
+ )
128
+
129
+ // Get single transaction detail
130
+ server.tool(
131
+ 'qvapay_app_transaction_detail',
132
+ 'Get detailed information about a specific merchant app transaction.',
133
+ {
134
+ uuid: z.string().describe('UUID of the transaction'),
135
+ },
136
+ async ({ uuid }) => {
137
+ try {
138
+ const data = await client.merchant(`/v2/transactions/${uuid}`)
139
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
140
+ } catch (error) {
141
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
142
+ }
143
+ }
144
+ )
145
+ }
@@ -0,0 +1,235 @@
1
+ // P2P Tools — QvaPay P2P Trading (requires QVAPAY_API_TOKEN)
2
+
3
+ import { z } from 'zod'
4
+ import { client } from '../client.js'
5
+
6
+ export function registerP2PTools(server) {
7
+
8
+ // List P2P offers
9
+ server.tool(
10
+ 'qvapay_p2p_list',
11
+ 'Browse P2P offers on QvaPay. Filter by type (buy/sell), coin, amount range, or see only your own offers.',
12
+ {
13
+ type: z.enum(['buy', 'sell']).optional().describe('Filter by offer type'),
14
+ coin: z.string().optional().describe('Filter by coin ticker (e.g., TRX, USDT, BTC)'),
15
+ my: z.boolean().optional().describe('Show only your own offers'),
16
+ status: z.string().optional().describe('Filter by status: open, processing, paid, completed, cancelled'),
17
+ min: z.number().optional().describe('Minimum amount filter'),
18
+ max: z.number().optional().describe('Maximum amount filter'),
19
+ search: z.string().optional().describe('Search query'),
20
+ page: z.number().int().positive().optional().describe('Page number (default: 1)'),
21
+ take: z.number().int().positive().max(30).optional().describe('Results per page (default: 20)'),
22
+ },
23
+ async (params = {}) => {
24
+ try {
25
+ const query = {}
26
+ for (const [key, value] of Object.entries(params)) {
27
+ if (value !== undefined && value !== null) query[key] = String(value)
28
+ }
29
+ const data = await client.user('/p2p', { query })
30
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
31
+ } catch (error) {
32
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
33
+ }
34
+ }
35
+ )
36
+
37
+ // Get P2P offer details
38
+ server.tool(
39
+ 'qvapay_p2p_detail',
40
+ 'Get full details of a specific P2P offer including participants, coin info, and chat availability.',
41
+ {
42
+ uuid: z.string().describe('UUID of the P2P offer'),
43
+ },
44
+ async ({ uuid }) => {
45
+ try {
46
+ const data = await client.user(`/p2p/${uuid}`)
47
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
48
+ } catch (error) {
49
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
50
+ }
51
+ }
52
+ )
53
+
54
+ // Create P2P offer
55
+ server.tool(
56
+ 'qvapay_p2p_create',
57
+ 'Create a new P2P trading offer. For SELL offers, the amount is deducted from your balance immediately. Requires KYC verification.',
58
+ {
59
+ type: z.enum(['buy', 'sell']).describe('Offer type: "buy" to buy crypto, "sell" to sell crypto'),
60
+ coin: z.string().describe('Coin ticker (e.g., TRX, USDT, BTC) or coin ID number'),
61
+ amount: z.number().min(0.1).max(100000).describe('Amount in QUSD to trade'),
62
+ receive: z.number().min(0.1).max(1000000).describe('Amount to receive in the target currency'),
63
+ details: z.record(z.string()).describe('Payment details object — keys depend on the coin (e.g., { "address": "TRX..." } for crypto, { "card": "..." } for fiat)'),
64
+ message: z.string().max(79).optional().describe('Public message for the offer (max 79 chars)'),
65
+ only_kyc: z.boolean().optional().describe('Only allow KYC-verified users to apply'),
66
+ private: z.boolean().optional().describe('Make this a private offer (not listed publicly)'),
67
+ only_vip: z.boolean().optional().describe('Only allow VIP users to apply'),
68
+ tags: z.array(z.string()).max(10).optional().describe('Tags for the offer (max 10)'),
69
+ webhook: z.string().url().optional().describe('Webhook URL for status updates'),
70
+ },
71
+ async (params) => {
72
+ try {
73
+ const body = { ...params }
74
+ if (body.only_kyc !== undefined) body.only_kyc = body.only_kyc ? 1 : 0
75
+ if (body.private !== undefined) body.private = body.private ? 1 : 0
76
+ if (body.only_vip !== undefined) body.only_vip = body.only_vip ? 1 : 0
77
+ const data = await client.user('/p2p/create', { method: 'POST', body })
78
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
79
+ } catch (error) {
80
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
81
+ }
82
+ }
83
+ )
84
+
85
+ // Edit P2P offer
86
+ server.tool(
87
+ 'qvapay_p2p_edit',
88
+ 'Edit an open P2P offer you own. Can change amount, receive, message, tags, and visibility. For SELL offers, balance is adjusted automatically.',
89
+ {
90
+ uuid: z.string().describe('UUID of the P2P offer to edit'),
91
+ amount: z.number().min(0.1).max(100000).optional().describe('New trade amount'),
92
+ receive: z.number().min(0.1).max(1000000).optional().describe('New receive amount'),
93
+ message: z.string().max(79).optional().describe('New public message'),
94
+ only_vip: z.boolean().optional().describe('Restrict to VIP users'),
95
+ tags: z.array(z.string()).max(10).optional().describe('New tags'),
96
+ webhook: z.string().url().optional().describe('New webhook URL'),
97
+ },
98
+ async ({ uuid, ...params }) => {
99
+ try {
100
+ const data = await client.user(`/p2p/${uuid}/edit`, { method: 'POST', body: params })
101
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
102
+ } catch (error) {
103
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
104
+ }
105
+ }
106
+ )
107
+
108
+ // Apply to P2P offer
109
+ server.tool(
110
+ 'qvapay_p2p_apply',
111
+ 'Apply to an open P2P offer. For BUY offers (you are selling), the amount is deducted from your balance. Requires KYC.',
112
+ {
113
+ uuid: z.string().describe('UUID of the P2P offer to apply to'),
114
+ },
115
+ async ({ uuid }) => {
116
+ try {
117
+ const data = await client.user(`/p2p/${uuid}/apply`, { method: 'POST', body: {} })
118
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
119
+ } catch (error) {
120
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
121
+ }
122
+ }
123
+ )
124
+
125
+ // Cancel P2P offer
126
+ server.tool(
127
+ 'qvapay_p2p_cancel',
128
+ 'Cancel a P2P offer. Behavior depends on your role (owner/peer) and offer type. Funds are refunded when applicable.',
129
+ {
130
+ uuid: z.string().describe('UUID of the P2P offer to cancel'),
131
+ },
132
+ async ({ uuid }) => {
133
+ try {
134
+ const data = await client.user(`/p2p/${uuid}/cancel`, { method: 'POST', body: {} })
135
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
136
+ } catch (error) {
137
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
138
+ }
139
+ }
140
+ )
141
+
142
+ // Mark P2P as paid
143
+ server.tool(
144
+ 'qvapay_p2p_paid',
145
+ 'Mark a P2P offer as paid. Only the buyer can do this (owner for BUY offers, peer for SELL offers). Call this after you have sent payment.',
146
+ {
147
+ uuid: z.string().describe('UUID of the P2P offer'),
148
+ tx_id: z.string().optional().describe('Transaction ID or proof of payment reference'),
149
+ },
150
+ async ({ uuid, tx_id }) => {
151
+ try {
152
+ const body = tx_id ? { tx_id } : {}
153
+ const data = await client.user(`/p2p/${uuid}/paid`, { method: 'POST', body })
154
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
155
+ } catch (error) {
156
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
157
+ }
158
+ }
159
+ )
160
+
161
+ // Mark P2P as received (complete)
162
+ server.tool(
163
+ 'qvapay_p2p_received',
164
+ 'Confirm payment received and complete a P2P trade. Only the seller can do this (peer for BUY offers, owner for SELL offers). A 0.25% fee is applied unless you have golden status.',
165
+ {
166
+ uuid: z.string().describe('UUID of the P2P offer'),
167
+ },
168
+ async ({ uuid }) => {
169
+ try {
170
+ const data = await client.user(`/p2p/${uuid}/received`, { method: 'POST', body: {} })
171
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
172
+ } catch (error) {
173
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
174
+ }
175
+ }
176
+ )
177
+
178
+ // P2P Chat - read messages
179
+ server.tool(
180
+ 'qvapay_p2p_chat_read',
181
+ 'Read chat messages from a P2P trade. Only participants can read the chat.',
182
+ {
183
+ uuid: z.string().describe('UUID of the P2P offer'),
184
+ },
185
+ async ({ uuid }) => {
186
+ try {
187
+ const data = await client.user(`/p2p/${uuid}/chat`)
188
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
189
+ } catch (error) {
190
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
191
+ }
192
+ }
193
+ )
194
+
195
+ // P2P Chat - send message
196
+ server.tool(
197
+ 'qvapay_p2p_chat_send',
198
+ 'Send a text message in a P2P trade chat. Only participants can send messages.',
199
+ {
200
+ uuid: z.string().describe('UUID of the P2P offer'),
201
+ message: z.string().min(1).max(599).describe('Message text to send'),
202
+ },
203
+ async ({ uuid, message }) => {
204
+ try {
205
+ const data = await client.user(`/p2p/${uuid}/chat`, {
206
+ method: 'POST',
207
+ body: { message },
208
+ })
209
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
210
+ } catch (error) {
211
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
212
+ }
213
+ }
214
+ )
215
+
216
+ // Rate P2P counterparty
217
+ server.tool(
218
+ 'qvapay_p2p_rate',
219
+ 'Rate your counterparty after completing a P2P trade. Rating is 1-5 stars with optional comment.',
220
+ {
221
+ uuid: z.string().describe('UUID of the completed P2P offer'),
222
+ rating: z.number().int().min(1).max(5).describe('Rating from 1 (worst) to 5 (best)'),
223
+ comment: z.string().max(120).optional().describe('Optional review comment (max 120 chars)'),
224
+ tags: z.array(z.enum(['rapido', 'confiable', 'comunicacion'])).optional().describe('Optional tags: rapido, confiable, comunicacion'),
225
+ },
226
+ async ({ uuid, ...body }) => {
227
+ try {
228
+ const data = await client.user(`/p2p/${uuid}/rate`, { method: 'POST', body })
229
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
230
+ } catch (error) {
231
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
232
+ }
233
+ }
234
+ )
235
+ }
@@ -0,0 +1,166 @@
1
+ // User Tools — QvaPay User API (requires QVAPAY_API_TOKEN)
2
+
3
+ import { z } from 'zod'
4
+ import { client } from '../client.js'
5
+
6
+ export function registerUserTools(server) {
7
+
8
+ // Get user profile
9
+ server.tool(
10
+ 'qvapay_user_profile',
11
+ 'Get your QvaPay profile including balance, username, verification status, and 3 most recent transactions.',
12
+ {},
13
+ async () => {
14
+ try {
15
+ const data = await client.user('/user')
16
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
17
+ } catch (error) {
18
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
19
+ }
20
+ }
21
+ )
22
+
23
+ // Get extended profile
24
+ server.tool(
25
+ 'qvapay_user_extended',
26
+ 'Get extended profile with P2P trading stats, recent withdrawals, and verification details.',
27
+ {},
28
+ async () => {
29
+ try {
30
+ const data = await client.user('/user/extended')
31
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
32
+ } catch (error) {
33
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
34
+ }
35
+ }
36
+ )
37
+
38
+ // List transactions with filters
39
+ server.tool(
40
+ 'qvapay_list_transactions',
41
+ 'List your transactions with optional filters. Returns paginated results.',
42
+ {
43
+ page: z.number().int().positive().optional().describe('Page number (default: 1)'),
44
+ take: z.number().int().positive().max(30).optional().describe('Results per page (default: 20, max: 30)'),
45
+ status: z.string().optional().describe('Filter by status: paid, pending, cancelled (default: paid)'),
46
+ search: z.string().optional().describe('Search by description, UUID, or remote_id'),
47
+ uuid: z.string().optional().describe('Find a specific transaction by UUID'),
48
+ user_uuid: z.string().optional().describe('Filter transactions involving a specific user'),
49
+ min_amount: z.number().optional().describe('Minimum amount filter'),
50
+ max_amount: z.number().optional().describe('Maximum amount filter'),
51
+ date_from: z.string().optional().describe('Start date filter (ISO 8601)'),
52
+ date_to: z.string().optional().describe('End date filter (ISO 8601)'),
53
+ order: z.enum(['asc', 'desc']).optional().describe('Sort order (default: desc)'),
54
+ include_total: z.boolean().optional().describe('Include total count for pagination'),
55
+ },
56
+ async (params) => {
57
+ try {
58
+ const query = { ...params }
59
+ if (query.include_total) {
60
+ query.include_total = 'true'
61
+ delete query.include_total_bool
62
+ }
63
+ const data = await client.user('/transaction', { query })
64
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
65
+ } catch (error) {
66
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
67
+ }
68
+ }
69
+ )
70
+
71
+ // Get transaction detail
72
+ server.tool(
73
+ 'qvapay_transaction_detail',
74
+ 'Get full details of a specific transaction by UUID.',
75
+ {
76
+ uuid: z.string().describe('UUID of the transaction'),
77
+ },
78
+ async ({ uuid }) => {
79
+ try {
80
+ const data = await client.user(`/transaction/${uuid}`)
81
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
82
+ } catch (error) {
83
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
84
+ }
85
+ }
86
+ )
87
+
88
+ // Transfer money
89
+ server.tool(
90
+ 'qvapay_transfer',
91
+ 'Transfer money to another QvaPay user. IMPORTANT: Always ask the user for their PIN before calling this tool. Never guess or assume the PIN.',
92
+ {
93
+ amount: z.number().positive().describe('Amount in USD to transfer'),
94
+ to: z.string().describe('Recipient identifier: UUID, email, username, or phone number'),
95
+ pin: z.string().regex(/^\d{4}$|^\d{6}$/).describe('Your 4 or 6 digit security PIN. Ask the user for this — never assume it.'),
96
+ description: z.string().optional().describe('Optional transfer description'),
97
+ },
98
+ async (params) => {
99
+ try {
100
+ const data = await client.user('/transaction/transfer', {
101
+ method: 'POST',
102
+ body: params,
103
+ })
104
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
105
+ } catch (error) {
106
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
107
+ }
108
+ }
109
+ )
110
+
111
+ // Search users
112
+ server.tool(
113
+ 'qvapay_search_users',
114
+ 'Search for QvaPay users by username, email, UUID, or phone number.',
115
+ {
116
+ query: z.string().min(1).describe('Search query (username, email, UUID, or phone)'),
117
+ },
118
+ async ({ query }) => {
119
+ try {
120
+ const data = await client.user('/user/search', {
121
+ method: 'POST',
122
+ body: { query },
123
+ })
124
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
125
+ } catch (error) {
126
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
127
+ }
128
+ }
129
+ )
130
+
131
+ // List payment methods
132
+ server.tool(
133
+ 'qvapay_payment_methods',
134
+ 'List your saved payment methods, optionally filtered by cryptocurrency.',
135
+ {
136
+ coin: z.string().optional().describe('Filter by coin ticker (e.g., BTC, ETH, TRX)'),
137
+ },
138
+ async ({ coin } = {}) => {
139
+ try {
140
+ const query = coin ? { coin } : undefined
141
+ const data = await client.user('/user/payment-methods', { query })
142
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
143
+ } catch (error) {
144
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
145
+ }
146
+ }
147
+ )
148
+
149
+ // List contacts
150
+ server.tool(
151
+ 'qvapay_contacts',
152
+ 'List your QvaPay contacts. Optionally filter to show only favorites.',
153
+ {
154
+ favorite: z.boolean().optional().describe('If true, only show favorite contacts'),
155
+ },
156
+ async ({ favorite } = {}) => {
157
+ try {
158
+ const query = favorite ? { favorite: 'true' } : undefined
159
+ const data = await client.user('/user/contact', { query })
160
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] }
161
+ } catch (error) {
162
+ return { content: [{ type: 'text', text: `Error: ${error.message}` }], isError: true }
163
+ }
164
+ }
165
+ )
166
+ }