@magnolia-financial/banking-mcp 1.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 +180 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +582 -0
- package/dist/magnolia-client.d.ts +210 -0
- package/dist/magnolia-client.js +305 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Magnolia MCP Server
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that wraps the Magnolia banking API, letting AI agents manage crypto wallets, bank accounts, trading, fiat operations, and Lightning Network payments.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
- Node.js 18+
|
|
8
|
+
- A Magnolia API key (get one at [clawbot.cash](https://clawbot.cash))
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install
|
|
14
|
+
npm run build
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Configuration
|
|
18
|
+
|
|
19
|
+
Set your API key as an environment variable:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
export MAGNOLIA_API_KEY=magfi_your_api_key_here
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Variable | Required | Default | Description |
|
|
26
|
+
|----------|----------|---------|-------------|
|
|
27
|
+
| `MAGNOLIA_API_KEY` | Yes | — | API key from ClawBot.cash KYC flow |
|
|
28
|
+
| `MAGNOLIA_API_URL` | No | `https://api.magfi.net` | Magnolia API base URL |
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
### Claude Desktop
|
|
33
|
+
|
|
34
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"magnolia": {
|
|
40
|
+
"command": "node",
|
|
41
|
+
"args": ["/path/to/clawbot.cash/mcp-server/dist/index.js"],
|
|
42
|
+
"env": {
|
|
43
|
+
"MAGNOLIA_API_KEY": "magfi_your_api_key_here"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Claude Code
|
|
51
|
+
|
|
52
|
+
Add to your project's `.mcp.json`:
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"mcpServers": {
|
|
57
|
+
"magnolia": {
|
|
58
|
+
"command": "node",
|
|
59
|
+
"args": ["/path/to/clawbot.cash/mcp-server/dist/index.js"],
|
|
60
|
+
"env": {
|
|
61
|
+
"MAGNOLIA_API_KEY": "magfi_your_api_key_here"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Direct
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
MAGNOLIA_API_KEY=magfi_... node dist/index.js
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Available Tools (30 total)
|
|
75
|
+
|
|
76
|
+
### Enterprise
|
|
77
|
+
|
|
78
|
+
| Tool | Description |
|
|
79
|
+
|------|-------------|
|
|
80
|
+
| `list_enterprises` | List all enterprises accessible to this API key |
|
|
81
|
+
|
|
82
|
+
### Crypto Wallets
|
|
83
|
+
|
|
84
|
+
| Tool | Description |
|
|
85
|
+
|------|-------------|
|
|
86
|
+
| `list_wallets` | List wallets for a cryptocurrency (btc, eth, tbtc, teth) |
|
|
87
|
+
| `get_wallet` | Get wallet details by coin and wallet ID |
|
|
88
|
+
| `generate_address` | Generate a new receive address for a wallet |
|
|
89
|
+
| `list_addresses` | List all addresses for a wallet |
|
|
90
|
+
| `get_address_balances` | Get address balances for a wallet |
|
|
91
|
+
| `send_crypto` | Send a cryptocurrency transaction |
|
|
92
|
+
| `get_wallet_transfer` | Get transfer status for a wallet |
|
|
93
|
+
| `list_wallet_transfers` | List all transfers for a wallet |
|
|
94
|
+
|
|
95
|
+
### Bank Accounts
|
|
96
|
+
|
|
97
|
+
| Tool | Description |
|
|
98
|
+
|------|-------------|
|
|
99
|
+
| `list_bank_accounts` | List linked bank accounts (filterable by type, state, enterprise) |
|
|
100
|
+
| `get_bank_account` | Get bank account details by ID |
|
|
101
|
+
| `add_bank_account` | Add a new bank account (wire, ACH, or SEPA) |
|
|
102
|
+
| `delete_bank_account` | Delete a bank account |
|
|
103
|
+
| `get_deposit_info` | Get deposit/wiring instructions |
|
|
104
|
+
|
|
105
|
+
### Trading
|
|
106
|
+
|
|
107
|
+
| Tool | Description |
|
|
108
|
+
|------|-------------|
|
|
109
|
+
| `get_trading_balances` | Get balances for a trading account |
|
|
110
|
+
| `get_trading_products` | List available trading products/pairs |
|
|
111
|
+
| `place_order` | Place a trading order (market, limit, TWAP, steady pace) |
|
|
112
|
+
| `list_orders` | List orders for a trading account |
|
|
113
|
+
| `get_order` | Get order details by ID |
|
|
114
|
+
| `cancel_order` | Cancel a pending order |
|
|
115
|
+
|
|
116
|
+
### Fiat/ACH
|
|
117
|
+
|
|
118
|
+
| Tool | Description |
|
|
119
|
+
|------|-------------|
|
|
120
|
+
| `get_ach_agreement` | Get the ACH debit agreement |
|
|
121
|
+
| `accept_ach_agreement` | Accept the ACH debit agreement for a bank account |
|
|
122
|
+
| `create_ach_debit` | Create an ACH debit to pull funds from a bank account |
|
|
123
|
+
|
|
124
|
+
### Lightning Network
|
|
125
|
+
|
|
126
|
+
| Tool | Description |
|
|
127
|
+
|------|-------------|
|
|
128
|
+
| `create_lightning_invoice` | Create a Lightning invoice to receive payment |
|
|
129
|
+
| `get_lightning_invoice` | Check Lightning invoice status |
|
|
130
|
+
| `pay_lightning_invoice` | Pay a Lightning invoice |
|
|
131
|
+
| `list_lightning_transactions` | List Lightning transactions for a wallet |
|
|
132
|
+
|
|
133
|
+
### Utility
|
|
134
|
+
|
|
135
|
+
| Tool | Description |
|
|
136
|
+
|------|-------------|
|
|
137
|
+
| `lookup_routing_number` | Look up bank info by ACH or wire routing number |
|
|
138
|
+
|
|
139
|
+
### Auth
|
|
140
|
+
|
|
141
|
+
| Tool | Description |
|
|
142
|
+
|------|-------------|
|
|
143
|
+
| `list_api_keys` | List all API keys for the authenticated user |
|
|
144
|
+
| `delete_api_key` | Delete an API key by ID |
|
|
145
|
+
|
|
146
|
+
## Example Interactions
|
|
147
|
+
|
|
148
|
+
Once configured, you can ask your AI agent things like:
|
|
149
|
+
|
|
150
|
+
- "List my BTC wallets"
|
|
151
|
+
- "Generate a new receive address for my ETH wallet"
|
|
152
|
+
- "Send 0.001 BTC to address bc1q..."
|
|
153
|
+
- "Show my bank accounts"
|
|
154
|
+
- "Add my Chase checking account for ACH"
|
|
155
|
+
- "Place a market order to buy $100 of BTC"
|
|
156
|
+
- "What are my trading balances?"
|
|
157
|
+
- "Create a Lightning invoice for 10000 sats"
|
|
158
|
+
- "Look up routing number 021000021"
|
|
159
|
+
|
|
160
|
+
## Testing
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm test # Run all tests
|
|
164
|
+
npm run test:watch # Watch mode
|
|
165
|
+
npm run test:integration # Integration tests only
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
118 tests across 5 suites covering auth, API paths, client methods, edge cases, and backward compatibility.
|
|
169
|
+
|
|
170
|
+
## Development
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
npm run dev # Watch mode (recompiles on change)
|
|
174
|
+
npm run build # One-time build
|
|
175
|
+
npm start # Run the compiled server
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Magnolia MCP Server
|
|
4
|
+
*
|
|
5
|
+
* An MCP server that wraps the Magnolia banking API, allowing AI agents
|
|
6
|
+
* to manage crypto wallets, bank accounts, trading, fiat operations,
|
|
7
|
+
* and Lightning Network payments.
|
|
8
|
+
*
|
|
9
|
+
* API paths are based on the official Magnolia documentation at
|
|
10
|
+
* docs.magnolia.financial.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* MAGNOLIA_API_KEY=magfi_... node dist/index.js
|
|
14
|
+
* MAGNOLIA_API_KEY=magfi_... MAGNOLIA_API_URL=https://api.magfi.net node dist/index.js
|
|
15
|
+
*/
|
|
16
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Magnolia MCP Server
|
|
4
|
+
*
|
|
5
|
+
* An MCP server that wraps the Magnolia banking API, allowing AI agents
|
|
6
|
+
* to manage crypto wallets, bank accounts, trading, fiat operations,
|
|
7
|
+
* and Lightning Network payments.
|
|
8
|
+
*
|
|
9
|
+
* API paths are based on the official Magnolia documentation at
|
|
10
|
+
* docs.magnolia.financial.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* MAGNOLIA_API_KEY=magfi_... node dist/index.js
|
|
14
|
+
* MAGNOLIA_API_KEY=magfi_... MAGNOLIA_API_URL=https://api.magfi.net node dist/index.js
|
|
15
|
+
*/
|
|
16
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
import { MagnoliaClient } from "./magnolia-client.js";
|
|
20
|
+
const API_KEY = process.env.MAGNOLIA_API_KEY;
|
|
21
|
+
const API_URL = process.env.MAGNOLIA_API_URL;
|
|
22
|
+
if (!API_KEY) {
|
|
23
|
+
console.error("Error: MAGNOLIA_API_KEY environment variable is required.\n" +
|
|
24
|
+
"Get your API key at https://clawbot.cash");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
const client = new MagnoliaClient(API_KEY, API_URL);
|
|
28
|
+
const server = new McpServer({
|
|
29
|
+
name: "magnolia",
|
|
30
|
+
version: "1.0.0",
|
|
31
|
+
});
|
|
32
|
+
// Helper to format JSON responses
|
|
33
|
+
function jsonResponse(data) {
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: JSON.stringify(data, null, 2),
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function errorResponse(error) {
|
|
44
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: `Error: ${message}`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
isError: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
// ==========================================
|
|
56
|
+
// Enterprise Tools
|
|
57
|
+
// ==========================================
|
|
58
|
+
server.registerTool("list_enterprises", {
|
|
59
|
+
description: "List all enterprises accessible to this API key.",
|
|
60
|
+
inputSchema: {},
|
|
61
|
+
}, async () => {
|
|
62
|
+
try {
|
|
63
|
+
const enterprises = await client.getEnterprises();
|
|
64
|
+
return jsonResponse(enterprises);
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
return errorResponse(e);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// ==========================================
|
|
71
|
+
// Crypto Wallet Tools — /api/v2/{coin}/wallet
|
|
72
|
+
// ==========================================
|
|
73
|
+
server.registerTool("list_wallets", {
|
|
74
|
+
description: "List wallets for a specific cryptocurrency. Use coin tickers like 'btc', 'eth', 'tbtc' (testnet BTC), 'teth' (testnet ETH).",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
coin: z.string().describe("Coin ticker (e.g., 'btc', 'eth', 'tbtc', 'teth')"),
|
|
77
|
+
},
|
|
78
|
+
}, async ({ coin }) => {
|
|
79
|
+
try {
|
|
80
|
+
const wallets = await client.listWallets(coin);
|
|
81
|
+
return jsonResponse(wallets);
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
return errorResponse(e);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
server.registerTool("get_wallet", {
|
|
88
|
+
description: "Get details for a specific wallet by coin and wallet ID.",
|
|
89
|
+
inputSchema: {
|
|
90
|
+
coin: z.string().describe("Coin ticker (e.g., 'btc', 'eth')"),
|
|
91
|
+
walletId: z.string().describe("The wallet ID"),
|
|
92
|
+
},
|
|
93
|
+
}, async ({ coin, walletId }) => {
|
|
94
|
+
try {
|
|
95
|
+
const wallet = await client.getWallet(coin, walletId);
|
|
96
|
+
return jsonResponse(wallet);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
return errorResponse(e);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
server.registerTool("generate_address", {
|
|
103
|
+
description: "Generate a new receive address for a crypto wallet.",
|
|
104
|
+
inputSchema: {
|
|
105
|
+
coin: z.string().describe("Coin ticker (e.g., 'btc', 'eth')"),
|
|
106
|
+
walletId: z.string().describe("The wallet ID"),
|
|
107
|
+
},
|
|
108
|
+
}, async ({ coin, walletId }) => {
|
|
109
|
+
try {
|
|
110
|
+
const address = await client.generateAddress(coin, walletId);
|
|
111
|
+
return jsonResponse(address);
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
return errorResponse(e);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
server.registerTool("list_addresses", {
|
|
118
|
+
description: "List all addresses for a crypto wallet.",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
coin: z.string().describe("Coin ticker (e.g., 'btc', 'eth')"),
|
|
121
|
+
walletId: z.string().describe("The wallet ID"),
|
|
122
|
+
},
|
|
123
|
+
}, async ({ coin, walletId }) => {
|
|
124
|
+
try {
|
|
125
|
+
const addresses = await client.listAddresses(coin, walletId);
|
|
126
|
+
return jsonResponse(addresses);
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
return errorResponse(e);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
server.registerTool("get_address_balances", {
|
|
133
|
+
description: "Get address balances for a crypto wallet.",
|
|
134
|
+
inputSchema: {
|
|
135
|
+
coin: z.string().describe("Coin ticker (e.g., 'btc', 'eth')"),
|
|
136
|
+
walletId: z.string().describe("The wallet ID"),
|
|
137
|
+
},
|
|
138
|
+
}, async ({ coin, walletId }) => {
|
|
139
|
+
try {
|
|
140
|
+
const balances = await client.getAddressBalances(coin, walletId);
|
|
141
|
+
return jsonResponse(balances);
|
|
142
|
+
}
|
|
143
|
+
catch (e) {
|
|
144
|
+
return errorResponse(e);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
server.registerTool("send_crypto", {
|
|
148
|
+
description: "Send a cryptocurrency transaction from a wallet.",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
coin: z.string().describe("Coin ticker (e.g., 'btc', 'eth')"),
|
|
151
|
+
walletId: z.string().describe("Source wallet ID"),
|
|
152
|
+
address: z.string().describe("Destination address"),
|
|
153
|
+
amount: z.string().describe("Amount to send (in base units)"),
|
|
154
|
+
walletPassphrase: z.string().optional().describe("Wallet passphrase for signing"),
|
|
155
|
+
memo: z.string().optional().describe("Optional memo/tag"),
|
|
156
|
+
},
|
|
157
|
+
}, async ({ coin, walletId, address, amount, walletPassphrase, memo }) => {
|
|
158
|
+
try {
|
|
159
|
+
const tx = await client.sendTransaction(coin, walletId, {
|
|
160
|
+
address,
|
|
161
|
+
amount,
|
|
162
|
+
walletPassphrase,
|
|
163
|
+
memo,
|
|
164
|
+
});
|
|
165
|
+
return jsonResponse(tx);
|
|
166
|
+
}
|
|
167
|
+
catch (e) {
|
|
168
|
+
return errorResponse(e);
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
server.registerTool("get_wallet_transfer", {
|
|
172
|
+
description: "Get the status of a specific transfer for a wallet.",
|
|
173
|
+
inputSchema: {
|
|
174
|
+
coin: z.string().describe("Coin ticker (e.g., 'btc', 'eth')"),
|
|
175
|
+
walletId: z.string().describe("The wallet ID"),
|
|
176
|
+
transferId: z.string().describe("The transfer ID"),
|
|
177
|
+
},
|
|
178
|
+
}, async ({ coin, walletId, transferId }) => {
|
|
179
|
+
try {
|
|
180
|
+
const transfer = await client.getWalletTransfer(coin, walletId, transferId);
|
|
181
|
+
return jsonResponse(transfer);
|
|
182
|
+
}
|
|
183
|
+
catch (e) {
|
|
184
|
+
return errorResponse(e);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
server.registerTool("list_wallet_transfers", {
|
|
188
|
+
description: "List all transfers for a specific crypto wallet.",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
coin: z.string().describe("Coin ticker (e.g., 'btc', 'eth')"),
|
|
191
|
+
walletId: z.string().describe("The wallet ID"),
|
|
192
|
+
},
|
|
193
|
+
}, async ({ coin, walletId }) => {
|
|
194
|
+
try {
|
|
195
|
+
const transfers = await client.listWalletTransfers(coin, walletId);
|
|
196
|
+
return jsonResponse(transfers);
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
return errorResponse(e);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
// ==========================================
|
|
203
|
+
// Bank Account Tools — /api/v2/bankaccounts
|
|
204
|
+
// ==========================================
|
|
205
|
+
server.registerTool("list_bank_accounts", {
|
|
206
|
+
description: "List all linked bank accounts. Can filter by type, verification state, or enterprise.",
|
|
207
|
+
inputSchema: {
|
|
208
|
+
type: z.string().optional().describe("Filter by type: 'wire', 'ach', or 'sepa'"),
|
|
209
|
+
verificationState: z.string().optional().describe("Filter by verification state"),
|
|
210
|
+
enterpriseId: z.string().optional().describe("Filter by enterprise ID"),
|
|
211
|
+
},
|
|
212
|
+
}, async ({ type, verificationState, enterpriseId }) => {
|
|
213
|
+
try {
|
|
214
|
+
const accounts = await client.listBankAccounts({
|
|
215
|
+
type,
|
|
216
|
+
verificationState,
|
|
217
|
+
enterpriseId,
|
|
218
|
+
});
|
|
219
|
+
return jsonResponse(accounts);
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
return errorResponse(e);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
server.registerTool("get_bank_account", {
|
|
226
|
+
description: "Get details for a specific bank account by ID.",
|
|
227
|
+
inputSchema: {
|
|
228
|
+
bankAccountId: z.string().describe("The bank account ID"),
|
|
229
|
+
},
|
|
230
|
+
}, async ({ bankAccountId }) => {
|
|
231
|
+
try {
|
|
232
|
+
const account = await client.getBankAccount(bankAccountId);
|
|
233
|
+
return jsonResponse(account);
|
|
234
|
+
}
|
|
235
|
+
catch (e) {
|
|
236
|
+
return errorResponse(e);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
server.registerTool("add_bank_account", {
|
|
240
|
+
description: "Add a new bank account for deposits and withdrawals.",
|
|
241
|
+
inputSchema: {
|
|
242
|
+
type: z.enum(["wire", "ach", "sepa"]).describe("Bank account type"),
|
|
243
|
+
name: z.string().describe("A label for this bank account"),
|
|
244
|
+
ownerName: z.string().describe("Name of the account owner"),
|
|
245
|
+
shortCountryCode: z.string().describe("Two-letter country code (e.g., 'US')"),
|
|
246
|
+
accountNumber: z.string().describe("Bank account number"),
|
|
247
|
+
currency: z.string().describe("Currency code (e.g., 'USD')"),
|
|
248
|
+
routingNumber: z.string().optional().describe("Routing number (required for ACH/wire in US)"),
|
|
249
|
+
swiftCode: z.string().optional().describe("SWIFT code (for international wire)"),
|
|
250
|
+
accountType: z.enum(["checking", "saving"]).optional().describe("Account type"),
|
|
251
|
+
ownerAddressCountryCode: z.string().describe("Two-letter country code for the owner's address (e.g., 'US')"),
|
|
252
|
+
ownerAddress: z.object({
|
|
253
|
+
address_line_1: z.string().describe("Street address"),
|
|
254
|
+
city_locality: z.string().describe("City"),
|
|
255
|
+
state_province: z.string().describe("State or province"),
|
|
256
|
+
postal_code: z.string().describe("Postal/ZIP code"),
|
|
257
|
+
}).describe("Owner's physical address"),
|
|
258
|
+
},
|
|
259
|
+
}, async ({ type, name, ownerName, shortCountryCode, accountNumber, currency, routingNumber, swiftCode, accountType, ownerAddressCountryCode, ownerAddress }) => {
|
|
260
|
+
try {
|
|
261
|
+
const account = await client.addBankAccount({
|
|
262
|
+
type,
|
|
263
|
+
name,
|
|
264
|
+
ownerName,
|
|
265
|
+
shortCountryCode,
|
|
266
|
+
accountNumber,
|
|
267
|
+
currency,
|
|
268
|
+
routingNumber,
|
|
269
|
+
swiftCode,
|
|
270
|
+
accountType,
|
|
271
|
+
ownerAddressCountryCode,
|
|
272
|
+
ownerAddress,
|
|
273
|
+
});
|
|
274
|
+
return jsonResponse(account);
|
|
275
|
+
}
|
|
276
|
+
catch (e) {
|
|
277
|
+
return errorResponse(e);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
server.registerTool("delete_bank_account", {
|
|
281
|
+
description: "Delete a bank account by ID.",
|
|
282
|
+
inputSchema: {
|
|
283
|
+
bankAccountId: z.string().describe("The bank account ID to delete"),
|
|
284
|
+
},
|
|
285
|
+
}, async ({ bankAccountId }) => {
|
|
286
|
+
try {
|
|
287
|
+
const result = await client.deleteBankAccount(bankAccountId);
|
|
288
|
+
return jsonResponse(result);
|
|
289
|
+
}
|
|
290
|
+
catch (e) {
|
|
291
|
+
return errorResponse(e);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
server.registerTool("get_deposit_info", {
|
|
295
|
+
description: "Get deposit information for bank accounts (wiring instructions, etc.).",
|
|
296
|
+
inputSchema: {},
|
|
297
|
+
}, async () => {
|
|
298
|
+
try {
|
|
299
|
+
const info = await client.getDepositInfo();
|
|
300
|
+
return jsonResponse(info);
|
|
301
|
+
}
|
|
302
|
+
catch (e) {
|
|
303
|
+
return errorResponse(e);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
// ==========================================
|
|
307
|
+
// Trading Tools — /api/prime/trading/v1
|
|
308
|
+
// ==========================================
|
|
309
|
+
server.registerTool("get_trading_balances", {
|
|
310
|
+
description: "Get balances for a trading account.",
|
|
311
|
+
inputSchema: {
|
|
312
|
+
accountId: z.string().describe("Trading account ID"),
|
|
313
|
+
},
|
|
314
|
+
}, async ({ accountId }) => {
|
|
315
|
+
try {
|
|
316
|
+
const balances = await client.getTradingBalances(accountId);
|
|
317
|
+
return jsonResponse(balances);
|
|
318
|
+
}
|
|
319
|
+
catch (e) {
|
|
320
|
+
return errorResponse(e);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
server.registerTool("get_trading_products", {
|
|
324
|
+
description: "List available trading products (pairs) for an account.",
|
|
325
|
+
inputSchema: {
|
|
326
|
+
accountId: z.string().describe("Trading account ID"),
|
|
327
|
+
},
|
|
328
|
+
}, async ({ accountId }) => {
|
|
329
|
+
try {
|
|
330
|
+
const products = await client.getTradingProducts(accountId);
|
|
331
|
+
return jsonResponse(products);
|
|
332
|
+
}
|
|
333
|
+
catch (e) {
|
|
334
|
+
return errorResponse(e);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
server.registerTool("place_order", {
|
|
338
|
+
description: "Place a trading order (market, limit, TWAP, or steady pace).",
|
|
339
|
+
inputSchema: {
|
|
340
|
+
accountId: z.string().describe("Trading account ID"),
|
|
341
|
+
type: z.enum(["market", "limit", "twap", "steady_pace"]).describe("Order type"),
|
|
342
|
+
product: z.string().describe("Trading product/pair (e.g., 'BTC-USD')"),
|
|
343
|
+
side: z.enum(["buy", "sell"]).describe("Order side"),
|
|
344
|
+
quantity: z.string().describe("Order quantity"),
|
|
345
|
+
quantityCurrency: z.string().describe("Currency of the quantity (e.g., 'USD' or 'BTC')"),
|
|
346
|
+
limitPrice: z.string().optional().describe("Limit price (required for limit orders)"),
|
|
347
|
+
duration: z.number().optional().describe("Duration in seconds (for TWAP/steady pace)"),
|
|
348
|
+
clientOrderId: z.string().optional().describe("Client-defined order ID"),
|
|
349
|
+
fundingType: z.enum(["margin", "funded"]).optional().describe("Funding type"),
|
|
350
|
+
},
|
|
351
|
+
}, async ({ accountId, type, product, side, quantity, quantityCurrency, limitPrice, duration, clientOrderId, fundingType }) => {
|
|
352
|
+
try {
|
|
353
|
+
const order = await client.placeOrder(accountId, {
|
|
354
|
+
type,
|
|
355
|
+
product,
|
|
356
|
+
side,
|
|
357
|
+
quantity,
|
|
358
|
+
quantityCurrency,
|
|
359
|
+
limitPrice,
|
|
360
|
+
duration,
|
|
361
|
+
clientOrderId,
|
|
362
|
+
fundingType,
|
|
363
|
+
});
|
|
364
|
+
return jsonResponse(order);
|
|
365
|
+
}
|
|
366
|
+
catch (e) {
|
|
367
|
+
return errorResponse(e);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
server.registerTool("list_orders", {
|
|
371
|
+
description: "List orders for a trading account.",
|
|
372
|
+
inputSchema: {
|
|
373
|
+
accountId: z.string().describe("Trading account ID"),
|
|
374
|
+
limit: z.number().optional().describe("Max number of orders to return"),
|
|
375
|
+
offset: z.number().optional().describe("Offset for pagination"),
|
|
376
|
+
},
|
|
377
|
+
}, async ({ accountId, limit, offset }) => {
|
|
378
|
+
try {
|
|
379
|
+
const orders = await client.listOrders(accountId, { limit, offset });
|
|
380
|
+
return jsonResponse(orders);
|
|
381
|
+
}
|
|
382
|
+
catch (e) {
|
|
383
|
+
return errorResponse(e);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
server.registerTool("get_order", {
|
|
387
|
+
description: "Get details for a specific trading order.",
|
|
388
|
+
inputSchema: {
|
|
389
|
+
accountId: z.string().describe("Trading account ID"),
|
|
390
|
+
orderId: z.string().describe("The order ID"),
|
|
391
|
+
},
|
|
392
|
+
}, async ({ accountId, orderId }) => {
|
|
393
|
+
try {
|
|
394
|
+
const order = await client.getOrder(accountId, orderId);
|
|
395
|
+
return jsonResponse(order);
|
|
396
|
+
}
|
|
397
|
+
catch (e) {
|
|
398
|
+
return errorResponse(e);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
server.registerTool("cancel_order", {
|
|
402
|
+
description: "Cancel a pending trading order.",
|
|
403
|
+
inputSchema: {
|
|
404
|
+
accountId: z.string().describe("Trading account ID"),
|
|
405
|
+
orderId: z.string().describe("The order ID to cancel"),
|
|
406
|
+
},
|
|
407
|
+
}, async ({ accountId, orderId }) => {
|
|
408
|
+
try {
|
|
409
|
+
const result = await client.cancelOrder(accountId, orderId);
|
|
410
|
+
return jsonResponse(result);
|
|
411
|
+
}
|
|
412
|
+
catch (e) {
|
|
413
|
+
return errorResponse(e);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
// ==========================================
|
|
417
|
+
// Fiat/ACH Tools — /api/fiat/v1
|
|
418
|
+
// ==========================================
|
|
419
|
+
server.registerTool("get_ach_agreement", {
|
|
420
|
+
description: "Get the ACH debit agreement that must be accepted before making ACH debits.",
|
|
421
|
+
inputSchema: {},
|
|
422
|
+
}, async () => {
|
|
423
|
+
try {
|
|
424
|
+
const agreement = await client.getAchAgreement();
|
|
425
|
+
return jsonResponse(agreement);
|
|
426
|
+
}
|
|
427
|
+
catch (e) {
|
|
428
|
+
return errorResponse(e);
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
server.registerTool("accept_ach_agreement", {
|
|
432
|
+
description: "Accept the ACH debit agreement for a specific bank account.",
|
|
433
|
+
inputSchema: {
|
|
434
|
+
bankAccountId: z.string().describe("The bank account ID to accept the agreement for"),
|
|
435
|
+
},
|
|
436
|
+
}, async ({ bankAccountId }) => {
|
|
437
|
+
try {
|
|
438
|
+
const result = await client.acceptAchAgreement({ bankAccountId });
|
|
439
|
+
return jsonResponse(result);
|
|
440
|
+
}
|
|
441
|
+
catch (e) {
|
|
442
|
+
return errorResponse(e);
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
server.registerTool("create_ach_debit", {
|
|
446
|
+
description: "Create an ACH debit transaction to pull funds from a linked bank account.",
|
|
447
|
+
inputSchema: {
|
|
448
|
+
bankAccountId: z.string().describe("Source bank account ID"),
|
|
449
|
+
amount: z.string().describe("Amount to debit (e.g., '100.00')"),
|
|
450
|
+
currency: z.string().describe("Currency code (e.g., 'USD')"),
|
|
451
|
+
},
|
|
452
|
+
}, async ({ bankAccountId, amount, currency }) => {
|
|
453
|
+
try {
|
|
454
|
+
const tx = await client.createAchDebit({ bankAccountId, amount, currency });
|
|
455
|
+
return jsonResponse(tx);
|
|
456
|
+
}
|
|
457
|
+
catch (e) {
|
|
458
|
+
return errorResponse(e);
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
// ==========================================
|
|
462
|
+
// Lightning Network Tools
|
|
463
|
+
// ==========================================
|
|
464
|
+
server.registerTool("create_lightning_invoice", {
|
|
465
|
+
description: "Create a Lightning Network invoice to receive a payment.",
|
|
466
|
+
inputSchema: {
|
|
467
|
+
walletId: z.string().describe("Bitcoin wallet ID"),
|
|
468
|
+
amount: z.string().describe("Amount in satoshis"),
|
|
469
|
+
memo: z.string().optional().describe("Invoice memo/description"),
|
|
470
|
+
},
|
|
471
|
+
}, async ({ walletId, amount, memo }) => {
|
|
472
|
+
try {
|
|
473
|
+
const invoice = await client.createLightningInvoice(walletId, { amount, memo });
|
|
474
|
+
return jsonResponse(invoice);
|
|
475
|
+
}
|
|
476
|
+
catch (e) {
|
|
477
|
+
return errorResponse(e);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
server.registerTool("get_lightning_invoice", {
|
|
481
|
+
description: "Check the status of a Lightning Network invoice.",
|
|
482
|
+
inputSchema: {
|
|
483
|
+
walletId: z.string().describe("Bitcoin wallet ID"),
|
|
484
|
+
paymentHash: z.string().describe("The payment hash of the invoice"),
|
|
485
|
+
},
|
|
486
|
+
}, async ({ walletId, paymentHash }) => {
|
|
487
|
+
try {
|
|
488
|
+
const invoice = await client.getLightningInvoice(walletId, paymentHash);
|
|
489
|
+
return jsonResponse(invoice);
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
return errorResponse(e);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
server.registerTool("pay_lightning_invoice", {
|
|
496
|
+
description: "Pay a Lightning Network invoice.",
|
|
497
|
+
inputSchema: {
|
|
498
|
+
walletId: z.string().describe("Bitcoin wallet ID to pay from"),
|
|
499
|
+
invoice: z.string().describe("Lightning invoice string (lnbc...)"),
|
|
500
|
+
},
|
|
501
|
+
}, async ({ walletId, invoice }) => {
|
|
502
|
+
try {
|
|
503
|
+
const payment = await client.makeLightningPayment(walletId, { invoice });
|
|
504
|
+
return jsonResponse(payment);
|
|
505
|
+
}
|
|
506
|
+
catch (e) {
|
|
507
|
+
return errorResponse(e);
|
|
508
|
+
}
|
|
509
|
+
});
|
|
510
|
+
server.registerTool("list_lightning_transactions", {
|
|
511
|
+
description: "List Lightning Network transactions for a wallet.",
|
|
512
|
+
inputSchema: {
|
|
513
|
+
walletId: z.string().describe("Bitcoin wallet ID"),
|
|
514
|
+
},
|
|
515
|
+
}, async ({ walletId }) => {
|
|
516
|
+
try {
|
|
517
|
+
const txs = await client.listLightningTransactions(walletId);
|
|
518
|
+
return jsonResponse(txs);
|
|
519
|
+
}
|
|
520
|
+
catch (e) {
|
|
521
|
+
return errorResponse(e);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
// ==========================================
|
|
525
|
+
// Utility Tools
|
|
526
|
+
// ==========================================
|
|
527
|
+
server.registerTool("lookup_routing_number", {
|
|
528
|
+
description: "Look up bank information by routing number. Returns bank name and address.",
|
|
529
|
+
inputSchema: {
|
|
530
|
+
bankType: z.enum(["ach", "wire"]).describe("Type of routing number: 'ach' or 'wire'"),
|
|
531
|
+
routingNumber: z.string().describe("The 9-digit routing number"),
|
|
532
|
+
},
|
|
533
|
+
}, async ({ bankType, routingNumber }) => {
|
|
534
|
+
try {
|
|
535
|
+
const info = await client.lookupRoutingNumber(bankType, routingNumber);
|
|
536
|
+
return jsonResponse(info);
|
|
537
|
+
}
|
|
538
|
+
catch (e) {
|
|
539
|
+
return errorResponse(e);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
// ==========================================
|
|
543
|
+
// Auth Tools
|
|
544
|
+
// ==========================================
|
|
545
|
+
server.registerTool("list_api_keys", {
|
|
546
|
+
description: "List all API keys for the authenticated user.",
|
|
547
|
+
inputSchema: {},
|
|
548
|
+
}, async () => {
|
|
549
|
+
try {
|
|
550
|
+
const keys = await client.listApiKeys();
|
|
551
|
+
return jsonResponse(keys);
|
|
552
|
+
}
|
|
553
|
+
catch (e) {
|
|
554
|
+
return errorResponse(e);
|
|
555
|
+
}
|
|
556
|
+
});
|
|
557
|
+
server.registerTool("delete_api_key", {
|
|
558
|
+
description: "Delete an API key by ID. Use list_api_keys first to find the key ID.",
|
|
559
|
+
inputSchema: {
|
|
560
|
+
keyId: z.string().describe("The API key ID to delete"),
|
|
561
|
+
},
|
|
562
|
+
}, async ({ keyId }) => {
|
|
563
|
+
try {
|
|
564
|
+
const result = await client.deleteApiKey(keyId);
|
|
565
|
+
return jsonResponse(result);
|
|
566
|
+
}
|
|
567
|
+
catch (e) {
|
|
568
|
+
return errorResponse(e);
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
// ==========================================
|
|
572
|
+
// Start Server
|
|
573
|
+
// ==========================================
|
|
574
|
+
async function main() {
|
|
575
|
+
const transport = new StdioServerTransport();
|
|
576
|
+
await server.connect(transport);
|
|
577
|
+
console.error("Magnolia MCP server v1.0 running on stdio");
|
|
578
|
+
}
|
|
579
|
+
main().catch((error) => {
|
|
580
|
+
console.error("Fatal error:", error);
|
|
581
|
+
process.exit(1);
|
|
582
|
+
});
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Magnolia API client for the MCP server.
|
|
3
|
+
* Uses API keys obtained from the ClawBot.cash KYC flow.
|
|
4
|
+
*
|
|
5
|
+
* IMPORTANT: API paths are based on the official Magnolia documentation at
|
|
6
|
+
* docs.magnolia.financial. The API has multiple services:
|
|
7
|
+
*
|
|
8
|
+
* - /auth/* — Authentication (login, API keys)
|
|
9
|
+
* - /api/v2/{coin}/wallet/* — Crypto wallet operations
|
|
10
|
+
* - /api/v2/bankaccounts/* — Bank account management
|
|
11
|
+
* - /api/v2/enterprise/* — Enterprise management
|
|
12
|
+
* - /api/prime/trading/v1/accounts/* — Trading (orders, balances, products)
|
|
13
|
+
* - /api/fiat/v1/* — Fiat/ACH operations
|
|
14
|
+
* - /api/evs/v1/* — KYC/Identity verification
|
|
15
|
+
* - /api/tradfi/v1/* — Traditional finance utilities
|
|
16
|
+
*/
|
|
17
|
+
export declare class MagnoliaClient {
|
|
18
|
+
private apiUrl;
|
|
19
|
+
private apiKey;
|
|
20
|
+
constructor(apiKey: string, apiUrl?: string);
|
|
21
|
+
private request;
|
|
22
|
+
/**
|
|
23
|
+
* Login with email/password to get a JWT token.
|
|
24
|
+
* JWT tokens expire after 1 hour.
|
|
25
|
+
*/
|
|
26
|
+
login(email: string, password: string): Promise<unknown>;
|
|
27
|
+
/**
|
|
28
|
+
* List all API keys for the authenticated user.
|
|
29
|
+
*/
|
|
30
|
+
listApiKeys(): Promise<unknown>;
|
|
31
|
+
/**
|
|
32
|
+
* Delete an API key by ID.
|
|
33
|
+
*/
|
|
34
|
+
deleteApiKey(keyId: string): Promise<unknown>;
|
|
35
|
+
/**
|
|
36
|
+
* List enterprises accessible to this user.
|
|
37
|
+
*/
|
|
38
|
+
getEnterprises(): Promise<unknown>;
|
|
39
|
+
/**
|
|
40
|
+
* List wallets for a specific cryptocurrency.
|
|
41
|
+
* @param coin - Coin ticker (e.g., "btc", "eth", "tbtc" for testnet)
|
|
42
|
+
*/
|
|
43
|
+
listWallets(coin: string): Promise<unknown>;
|
|
44
|
+
/**
|
|
45
|
+
* Get a specific wallet by ID.
|
|
46
|
+
*/
|
|
47
|
+
getWallet(coin: string, walletId: string): Promise<unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* Generate a new receive address for a wallet.
|
|
50
|
+
*/
|
|
51
|
+
generateAddress(coin: string, walletId: string): Promise<unknown>;
|
|
52
|
+
/**
|
|
53
|
+
* List all addresses for a wallet.
|
|
54
|
+
*/
|
|
55
|
+
listAddresses(coin: string, walletId: string): Promise<unknown>;
|
|
56
|
+
/**
|
|
57
|
+
* Get address balances for a wallet.
|
|
58
|
+
*/
|
|
59
|
+
getAddressBalances(coin: string, walletId: string): Promise<unknown>;
|
|
60
|
+
/**
|
|
61
|
+
* Send a transaction from a wallet.
|
|
62
|
+
*/
|
|
63
|
+
sendTransaction(coin: string, walletId: string, data: {
|
|
64
|
+
address: string;
|
|
65
|
+
amount: string;
|
|
66
|
+
walletPassphrase?: string;
|
|
67
|
+
memo?: string;
|
|
68
|
+
}): Promise<unknown>;
|
|
69
|
+
/**
|
|
70
|
+
* Get transfer status for a wallet.
|
|
71
|
+
*/
|
|
72
|
+
getWalletTransfer(coin: string, walletId: string, transferId: string): Promise<unknown>;
|
|
73
|
+
/**
|
|
74
|
+
* List transfers for a wallet.
|
|
75
|
+
*/
|
|
76
|
+
listWalletTransfers(coin: string, walletId: string): Promise<unknown>;
|
|
77
|
+
/**
|
|
78
|
+
* List all bank accounts.
|
|
79
|
+
*/
|
|
80
|
+
listBankAccounts(params?: {
|
|
81
|
+
type?: string;
|
|
82
|
+
verificationState?: string;
|
|
83
|
+
enterpriseId?: string;
|
|
84
|
+
}): Promise<unknown>;
|
|
85
|
+
/**
|
|
86
|
+
* Get a specific bank account by ID.
|
|
87
|
+
*/
|
|
88
|
+
getBankAccount(bankAccountId: string): Promise<unknown>;
|
|
89
|
+
/**
|
|
90
|
+
* Add a new bank account.
|
|
91
|
+
*
|
|
92
|
+
* NOTE: API requires complete owner address information (discovered via E2E testing).
|
|
93
|
+
* The ownerAddress object must contain these fields (snake_case):
|
|
94
|
+
* - address_line_1
|
|
95
|
+
* - city_locality
|
|
96
|
+
* - state_province
|
|
97
|
+
* - postal_code
|
|
98
|
+
*/
|
|
99
|
+
addBankAccount(data: {
|
|
100
|
+
type: "wire" | "ach" | "sepa";
|
|
101
|
+
name: string;
|
|
102
|
+
ownerName: string;
|
|
103
|
+
shortCountryCode: string;
|
|
104
|
+
accountNumber: string;
|
|
105
|
+
currency: string;
|
|
106
|
+
routingNumber?: string;
|
|
107
|
+
swiftCode?: string;
|
|
108
|
+
accountType?: "checking" | "saving";
|
|
109
|
+
ownerAddressCountryCode: string;
|
|
110
|
+
ownerAddress: {
|
|
111
|
+
address_line_1: string;
|
|
112
|
+
city_locality: string;
|
|
113
|
+
state_province: string;
|
|
114
|
+
postal_code: string;
|
|
115
|
+
};
|
|
116
|
+
}): Promise<unknown>;
|
|
117
|
+
/**
|
|
118
|
+
* Update a bank account.
|
|
119
|
+
*/
|
|
120
|
+
updateBankAccount(bankAccountId: string, data: Record<string, unknown>): Promise<unknown>;
|
|
121
|
+
/**
|
|
122
|
+
* Delete a bank account.
|
|
123
|
+
*/
|
|
124
|
+
deleteBankAccount(bankAccountId: string): Promise<unknown>;
|
|
125
|
+
/**
|
|
126
|
+
* Get deposit info for bank accounts.
|
|
127
|
+
*/
|
|
128
|
+
getDepositInfo(): Promise<unknown>;
|
|
129
|
+
/**
|
|
130
|
+
* Get account balances for a trading account.
|
|
131
|
+
*/
|
|
132
|
+
getTradingBalances(accountId: string): Promise<unknown>;
|
|
133
|
+
/**
|
|
134
|
+
* List available trading products for an account.
|
|
135
|
+
*/
|
|
136
|
+
getTradingProducts(accountId: string): Promise<unknown>;
|
|
137
|
+
/**
|
|
138
|
+
* Place a trading order.
|
|
139
|
+
*/
|
|
140
|
+
placeOrder(accountId: string, data: {
|
|
141
|
+
type: "market" | "limit" | "twap" | "steady_pace";
|
|
142
|
+
product: string;
|
|
143
|
+
side: "buy" | "sell";
|
|
144
|
+
quantity: string;
|
|
145
|
+
quantityCurrency: string;
|
|
146
|
+
limitPrice?: string;
|
|
147
|
+
duration?: number;
|
|
148
|
+
clientOrderId?: string;
|
|
149
|
+
fundingType?: "margin" | "funded";
|
|
150
|
+
}): Promise<unknown>;
|
|
151
|
+
/**
|
|
152
|
+
* List orders for a trading account.
|
|
153
|
+
*/
|
|
154
|
+
listOrders(accountId: string, params?: {
|
|
155
|
+
limit?: number;
|
|
156
|
+
offset?: number;
|
|
157
|
+
}): Promise<unknown>;
|
|
158
|
+
/**
|
|
159
|
+
* Get a specific order by ID.
|
|
160
|
+
*/
|
|
161
|
+
getOrder(accountId: string, orderId: string): Promise<unknown>;
|
|
162
|
+
/**
|
|
163
|
+
* Cancel an order.
|
|
164
|
+
*/
|
|
165
|
+
cancelOrder(accountId: string, orderId: string): Promise<unknown>;
|
|
166
|
+
/**
|
|
167
|
+
* Get ACH debit agreement.
|
|
168
|
+
*/
|
|
169
|
+
getAchAgreement(): Promise<unknown>;
|
|
170
|
+
/**
|
|
171
|
+
* Accept ACH debit agreement.
|
|
172
|
+
*/
|
|
173
|
+
acceptAchAgreement(data: {
|
|
174
|
+
bankAccountId: string;
|
|
175
|
+
}): Promise<unknown>;
|
|
176
|
+
/**
|
|
177
|
+
* Create an ACH debit transaction.
|
|
178
|
+
*/
|
|
179
|
+
createAchDebit(data: {
|
|
180
|
+
bankAccountId: string;
|
|
181
|
+
amount: string;
|
|
182
|
+
currency: string;
|
|
183
|
+
}): Promise<unknown>;
|
|
184
|
+
/**
|
|
185
|
+
* Create a lightning invoice.
|
|
186
|
+
*/
|
|
187
|
+
createLightningInvoice(walletId: string, data: {
|
|
188
|
+
amount: string;
|
|
189
|
+
memo?: string;
|
|
190
|
+
}): Promise<unknown>;
|
|
191
|
+
/**
|
|
192
|
+
* Get a lightning invoice status.
|
|
193
|
+
*/
|
|
194
|
+
getLightningInvoice(walletId: string, paymentHash: string): Promise<unknown>;
|
|
195
|
+
/**
|
|
196
|
+
* Make a lightning payment.
|
|
197
|
+
*/
|
|
198
|
+
makeLightningPayment(walletId: string, data: {
|
|
199
|
+
invoice: string;
|
|
200
|
+
}): Promise<unknown>;
|
|
201
|
+
/**
|
|
202
|
+
* List lightning transactions.
|
|
203
|
+
*/
|
|
204
|
+
listLightningTransactions(walletId: string): Promise<unknown>;
|
|
205
|
+
/**
|
|
206
|
+
* Look up bank routing number information.
|
|
207
|
+
* @param bankType - "ach" or "wire"
|
|
208
|
+
*/
|
|
209
|
+
lookupRoutingNumber(bankType: "ach" | "wire", routingNumber: string): Promise<unknown>;
|
|
210
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Magnolia API client for the MCP server.
|
|
3
|
+
* Uses API keys obtained from the ClawBot.cash KYC flow.
|
|
4
|
+
*
|
|
5
|
+
* IMPORTANT: API paths are based on the official Magnolia documentation at
|
|
6
|
+
* docs.magnolia.financial. The API has multiple services:
|
|
7
|
+
*
|
|
8
|
+
* - /auth/* — Authentication (login, API keys)
|
|
9
|
+
* - /api/v2/{coin}/wallet/* — Crypto wallet operations
|
|
10
|
+
* - /api/v2/bankaccounts/* — Bank account management
|
|
11
|
+
* - /api/v2/enterprise/* — Enterprise management
|
|
12
|
+
* - /api/prime/trading/v1/accounts/* — Trading (orders, balances, products)
|
|
13
|
+
* - /api/fiat/v1/* — Fiat/ACH operations
|
|
14
|
+
* - /api/evs/v1/* — KYC/Identity verification
|
|
15
|
+
* - /api/tradfi/v1/* — Traditional finance utilities
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_API_URL = "https://api.magfi.net";
|
|
18
|
+
export class MagnoliaClient {
|
|
19
|
+
apiUrl;
|
|
20
|
+
apiKey;
|
|
21
|
+
constructor(apiKey, apiUrl) {
|
|
22
|
+
this.apiKey = apiKey;
|
|
23
|
+
this.apiUrl = apiUrl || DEFAULT_API_URL;
|
|
24
|
+
}
|
|
25
|
+
async request(path, options = {}) {
|
|
26
|
+
const headers = {
|
|
27
|
+
"User-Agent": "ClawBot-MCP/1.0",
|
|
28
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
29
|
+
...(options.headers || {}),
|
|
30
|
+
};
|
|
31
|
+
if (options.body && typeof options.body === "string") {
|
|
32
|
+
headers["Content-Type"] = "application/json";
|
|
33
|
+
}
|
|
34
|
+
const res = await fetch(`${this.apiUrl}${path}`, {
|
|
35
|
+
...options,
|
|
36
|
+
headers,
|
|
37
|
+
});
|
|
38
|
+
const text = await res.text();
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
throw new Error(`Magnolia API error ${res.status} on ${options.method || "GET"} ${path}: ${text}`);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(text);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return text;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// --- Authentication ---
|
|
50
|
+
/**
|
|
51
|
+
* Login with email/password to get a JWT token.
|
|
52
|
+
* JWT tokens expire after 1 hour.
|
|
53
|
+
*/
|
|
54
|
+
async login(email, password) {
|
|
55
|
+
return this.request("/auth/login", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: JSON.stringify({ email, password }),
|
|
58
|
+
// Don't send API key for login, use raw request
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* List all API keys for the authenticated user.
|
|
63
|
+
*/
|
|
64
|
+
async listApiKeys() {
|
|
65
|
+
return this.request("/auth/api-key");
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Delete an API key by ID.
|
|
69
|
+
*/
|
|
70
|
+
async deleteApiKey(keyId) {
|
|
71
|
+
return this.request(`/auth/api-key/${keyId}`, {
|
|
72
|
+
method: "DELETE",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// --- Enterprise ---
|
|
76
|
+
/**
|
|
77
|
+
* List enterprises accessible to this user.
|
|
78
|
+
*/
|
|
79
|
+
async getEnterprises() {
|
|
80
|
+
return this.request("/api/v2/enterprise");
|
|
81
|
+
}
|
|
82
|
+
// --- Crypto Wallets (v2/{coin}/wallet) ---
|
|
83
|
+
/**
|
|
84
|
+
* List wallets for a specific cryptocurrency.
|
|
85
|
+
* @param coin - Coin ticker (e.g., "btc", "eth", "tbtc" for testnet)
|
|
86
|
+
*/
|
|
87
|
+
async listWallets(coin) {
|
|
88
|
+
return this.request(`/api/v2/${coin}/wallet`);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get a specific wallet by ID.
|
|
92
|
+
*/
|
|
93
|
+
async getWallet(coin, walletId) {
|
|
94
|
+
return this.request(`/api/v2/${coin}/wallet/${walletId}`);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Generate a new receive address for a wallet.
|
|
98
|
+
*/
|
|
99
|
+
async generateAddress(coin, walletId) {
|
|
100
|
+
return this.request(`/api/v2/${coin}/wallet/${walletId}/address`, {
|
|
101
|
+
method: "POST",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* List all addresses for a wallet.
|
|
106
|
+
*/
|
|
107
|
+
async listAddresses(coin, walletId) {
|
|
108
|
+
return this.request(`/api/v2/${coin}/wallet/${walletId}/addresses`);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Get address balances for a wallet.
|
|
112
|
+
*/
|
|
113
|
+
async getAddressBalances(coin, walletId) {
|
|
114
|
+
return this.request(`/api/v2/${coin}/wallet/${walletId}/addresses/balances`);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Send a transaction from a wallet.
|
|
118
|
+
*/
|
|
119
|
+
async sendTransaction(coin, walletId, data) {
|
|
120
|
+
return this.request(`/api/v2/${coin}/wallet/${walletId}/tx/send`, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
body: JSON.stringify(data),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Get transfer status for a wallet.
|
|
127
|
+
*/
|
|
128
|
+
async getWalletTransfer(coin, walletId, transferId) {
|
|
129
|
+
return this.request(`/api/v2/${coin}/wallet/${walletId}/transfer/${transferId}`);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* List transfers for a wallet.
|
|
133
|
+
*/
|
|
134
|
+
async listWalletTransfers(coin, walletId) {
|
|
135
|
+
return this.request(`/api/v2/${coin}/wallet/${walletId}/transfer`);
|
|
136
|
+
}
|
|
137
|
+
// --- Bank Accounts (v2/bankaccounts) ---
|
|
138
|
+
/**
|
|
139
|
+
* List all bank accounts.
|
|
140
|
+
*/
|
|
141
|
+
async listBankAccounts(params) {
|
|
142
|
+
const query = new URLSearchParams();
|
|
143
|
+
if (params?.type)
|
|
144
|
+
query.set("type", params.type);
|
|
145
|
+
if (params?.verificationState)
|
|
146
|
+
query.set("verificationState", params.verificationState);
|
|
147
|
+
if (params?.enterpriseId)
|
|
148
|
+
query.set("enterpriseId", params.enterpriseId);
|
|
149
|
+
const qs = query.toString();
|
|
150
|
+
return this.request(`/api/v2/bankaccounts${qs ? `?${qs}` : ""}`);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Get a specific bank account by ID.
|
|
154
|
+
*/
|
|
155
|
+
async getBankAccount(bankAccountId) {
|
|
156
|
+
return this.request(`/api/v2/bankaccounts/${bankAccountId}`);
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Add a new bank account.
|
|
160
|
+
*
|
|
161
|
+
* NOTE: API requires complete owner address information (discovered via E2E testing).
|
|
162
|
+
* The ownerAddress object must contain these fields (snake_case):
|
|
163
|
+
* - address_line_1
|
|
164
|
+
* - city_locality
|
|
165
|
+
* - state_province
|
|
166
|
+
* - postal_code
|
|
167
|
+
*/
|
|
168
|
+
async addBankAccount(data) {
|
|
169
|
+
return this.request("/api/v2/bankaccounts", {
|
|
170
|
+
method: "POST",
|
|
171
|
+
body: JSON.stringify(data),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Update a bank account.
|
|
176
|
+
*/
|
|
177
|
+
async updateBankAccount(bankAccountId, data) {
|
|
178
|
+
return this.request(`/api/v2/bankaccounts/${bankAccountId}`, {
|
|
179
|
+
method: "PUT",
|
|
180
|
+
body: JSON.stringify(data),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Delete a bank account.
|
|
185
|
+
*/
|
|
186
|
+
async deleteBankAccount(bankAccountId) {
|
|
187
|
+
return this.request(`/api/v2/bankaccounts/${bankAccountId}`, { method: "DELETE" });
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get deposit info for bank accounts.
|
|
191
|
+
*/
|
|
192
|
+
async getDepositInfo() {
|
|
193
|
+
return this.request("/api/v2/bankaccounts/deposit/info");
|
|
194
|
+
}
|
|
195
|
+
// --- Trading (Prime) ---
|
|
196
|
+
/**
|
|
197
|
+
* Get account balances for a trading account.
|
|
198
|
+
*/
|
|
199
|
+
async getTradingBalances(accountId) {
|
|
200
|
+
return this.request(`/api/prime/trading/v1/accounts/${accountId}/balances`);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* List available trading products for an account.
|
|
204
|
+
*/
|
|
205
|
+
async getTradingProducts(accountId) {
|
|
206
|
+
return this.request(`/api/prime/trading/v1/accounts/${accountId}/products`);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Place a trading order.
|
|
210
|
+
*/
|
|
211
|
+
async placeOrder(accountId, data) {
|
|
212
|
+
return this.request(`/api/prime/trading/v1/accounts/${accountId}/orders`, {
|
|
213
|
+
method: "POST",
|
|
214
|
+
body: JSON.stringify(data),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* List orders for a trading account.
|
|
219
|
+
*/
|
|
220
|
+
async listOrders(accountId, params) {
|
|
221
|
+
const query = new URLSearchParams();
|
|
222
|
+
if (params?.limit)
|
|
223
|
+
query.set("limit", String(params.limit));
|
|
224
|
+
if (params?.offset)
|
|
225
|
+
query.set("offset", String(params.offset));
|
|
226
|
+
const qs = query.toString();
|
|
227
|
+
return this.request(`/api/prime/trading/v1/accounts/${accountId}/orders${qs ? `?${qs}` : ""}`);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Get a specific order by ID.
|
|
231
|
+
*/
|
|
232
|
+
async getOrder(accountId, orderId) {
|
|
233
|
+
return this.request(`/api/prime/trading/v1/accounts/${accountId}/orders/${orderId}`);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Cancel an order.
|
|
237
|
+
*/
|
|
238
|
+
async cancelOrder(accountId, orderId) {
|
|
239
|
+
return this.request(`/api/prime/trading/v1/accounts/${accountId}/orders/${orderId}/cancel`, { method: "POST" });
|
|
240
|
+
}
|
|
241
|
+
// --- Fiat/ACH ---
|
|
242
|
+
/**
|
|
243
|
+
* Get ACH debit agreement.
|
|
244
|
+
*/
|
|
245
|
+
async getAchAgreement() {
|
|
246
|
+
return this.request("/api/fiat/v1/transaction/ach-debit/agreement");
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Accept ACH debit agreement.
|
|
250
|
+
*/
|
|
251
|
+
async acceptAchAgreement(data) {
|
|
252
|
+
return this.request("/api/fiat/v1/transaction/ach-debit/agreement", {
|
|
253
|
+
method: "POST",
|
|
254
|
+
body: JSON.stringify(data),
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Create an ACH debit transaction.
|
|
259
|
+
*/
|
|
260
|
+
async createAchDebit(data) {
|
|
261
|
+
return this.request("/api/fiat/v1/transaction/ach-debit", {
|
|
262
|
+
method: "POST",
|
|
263
|
+
body: JSON.stringify(data),
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
// --- Lightning Network ---
|
|
267
|
+
/**
|
|
268
|
+
* Create a lightning invoice.
|
|
269
|
+
*/
|
|
270
|
+
async createLightningInvoice(walletId, data) {
|
|
271
|
+
return this.request(`/api/v2/wallet/${walletId}/lightning/invoice`, {
|
|
272
|
+
method: "POST",
|
|
273
|
+
body: JSON.stringify(data),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Get a lightning invoice status.
|
|
278
|
+
*/
|
|
279
|
+
async getLightningInvoice(walletId, paymentHash) {
|
|
280
|
+
return this.request(`/api/v2/wallet/${walletId}/lightning/invoice/${paymentHash}`);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Make a lightning payment.
|
|
284
|
+
*/
|
|
285
|
+
async makeLightningPayment(walletId, data) {
|
|
286
|
+
return this.request(`/api/v2/wallet/${walletId}/lightning/payment`, {
|
|
287
|
+
method: "POST",
|
|
288
|
+
body: JSON.stringify(data),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* List lightning transactions.
|
|
293
|
+
*/
|
|
294
|
+
async listLightningTransactions(walletId) {
|
|
295
|
+
return this.request(`/api/v2/wallet/${walletId}/lightning/transaction`);
|
|
296
|
+
}
|
|
297
|
+
// --- Utility ---
|
|
298
|
+
/**
|
|
299
|
+
* Look up bank routing number information.
|
|
300
|
+
* @param bankType - "ach" or "wire"
|
|
301
|
+
*/
|
|
302
|
+
async lookupRoutingNumber(bankType, routingNumber) {
|
|
303
|
+
return this.request(`/api/tradfi/v1/banks/${bankType}/${routingNumber}`);
|
|
304
|
+
}
|
|
305
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@magnolia-financial/banking-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server wrapping the Magnolia banking API for AI agents",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"banking-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"start": "node dist/index.js",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"test:integration": "MAGNOLIA_API_KEY=${MAGNOLIA_API_KEY} MAGNOLIA_API_URL=https://api.dev.magfi.dev vitest run --reporter=verbose"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"mcp",
|
|
23
|
+
"magnolia",
|
|
24
|
+
"banking",
|
|
25
|
+
"ai-agent"
|
|
26
|
+
],
|
|
27
|
+
"author": "ClawBot",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"type": "module",
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
32
|
+
"zod": "^4.3.6"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^25.2.2",
|
|
36
|
+
"typescript": "^5.9.3",
|
|
37
|
+
"vitest": "^4.0.18"
|
|
38
|
+
}
|
|
39
|
+
}
|