@codespar/mcp-bitso 0.1.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 +115 -0
- package/dist/index.js +239 -0
- package/package.json +30 -0
- package/src/index.ts +246 -0
- package/tsconfig.json +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @codespar/mcp-bitso
|
|
2
|
+
|
|
3
|
+
> MCP server for **Bitso** — Latin American cryptocurrency exchange with trading, orders, and withdrawals
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@codespar/mcp-bitso)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
### Claude Desktop
|
|
11
|
+
|
|
12
|
+
Add to `~/.config/claude/claude_desktop_config.json`:
|
|
13
|
+
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"mcpServers": {
|
|
17
|
+
"bitso": {
|
|
18
|
+
"command": "npx",
|
|
19
|
+
"args": ["-y", "@codespar/mcp-bitso"],
|
|
20
|
+
"env": {
|
|
21
|
+
"BITSO_API_KEY": "your-key",
|
|
22
|
+
"BITSO_API_SECRET": "your-secret"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Claude Code
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
claude mcp add bitso -- npx @codespar/mcp-bitso
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Cursor / VS Code
|
|
36
|
+
|
|
37
|
+
Add to `.cursor/mcp.json` or `.vscode/mcp.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"servers": {
|
|
42
|
+
"bitso": {
|
|
43
|
+
"command": "npx",
|
|
44
|
+
"args": ["-y", "@codespar/mcp-bitso"],
|
|
45
|
+
"env": {
|
|
46
|
+
"BITSO_API_KEY": "your-key",
|
|
47
|
+
"BITSO_API_SECRET": "your-secret"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Tools
|
|
55
|
+
|
|
56
|
+
| Tool | Description |
|
|
57
|
+
|------|-------------|
|
|
58
|
+
| `get_ticker` | Get ticker data for a trading pair (price, volume, VWAP, etc.) |
|
|
59
|
+
| `list_orderbook` | Get order book (bids and asks) for a trading pair |
|
|
60
|
+
| `create_order` | Create a buy or sell order |
|
|
61
|
+
| `get_order` | Get order details by ID |
|
|
62
|
+
| `cancel_order` | Cancel an open order |
|
|
63
|
+
| `list_orders` | List orders with optional filters |
|
|
64
|
+
| `get_balances` | Get account balances for all assets |
|
|
65
|
+
| `list_trades` | List executed trades for an order book |
|
|
66
|
+
| `list_funding_sources` | List available funding sources (bank accounts, etc.) |
|
|
67
|
+
| `create_withdrawal` | Create a withdrawal request (crypto or fiat) |
|
|
68
|
+
|
|
69
|
+
## Authentication
|
|
70
|
+
|
|
71
|
+
Bitso uses HMAC-SHA256 signed requests with an API key and secret.
|
|
72
|
+
|
|
73
|
+
## Sandbox / Testing
|
|
74
|
+
|
|
75
|
+
Bitso provides a developer sandbox via the developer account.
|
|
76
|
+
|
|
77
|
+
### Get your credentials
|
|
78
|
+
|
|
79
|
+
1. Go to [Bitso](https://bitso.com)
|
|
80
|
+
2. Create an account
|
|
81
|
+
3. Navigate to API settings and generate key and secret
|
|
82
|
+
4. Set the environment variables
|
|
83
|
+
|
|
84
|
+
## Environment Variables
|
|
85
|
+
|
|
86
|
+
| Variable | Required | Description |
|
|
87
|
+
|----------|----------|-------------|
|
|
88
|
+
| `BITSO_API_KEY` | Yes | API key from Bitso |
|
|
89
|
+
| `BITSO_API_SECRET` | Yes | API secret for HMAC signature |
|
|
90
|
+
|
|
91
|
+
## Roadmap
|
|
92
|
+
|
|
93
|
+
### v0.2 (planned)
|
|
94
|
+
- `get_account_status` — Get account verification status
|
|
95
|
+
- `list_currencies` — List available cryptocurrencies
|
|
96
|
+
- `create_spei_withdrawal` — Create a SPEI (Mexican bank) withdrawal
|
|
97
|
+
- `get_phone_number` — Get phone number associated with account
|
|
98
|
+
- `list_open_orders` — List all open orders
|
|
99
|
+
|
|
100
|
+
### v0.3 (planned)
|
|
101
|
+
- `recurring_orders` — Create and manage recurring buy/sell orders
|
|
102
|
+
- `advanced_orders` — Advanced order types (OCO, trailing stop)
|
|
103
|
+
|
|
104
|
+
Want to contribute? [Open a PR](https://github.com/codespar/mcp-dev-brasil) or [request a tool](https://github.com/codespar/mcp-dev-brasil/issues).
|
|
105
|
+
|
|
106
|
+
## Links
|
|
107
|
+
|
|
108
|
+
- [Bitso Website](https://bitso.com)
|
|
109
|
+
- [Bitso API Documentation](https://bitso.com/developers)
|
|
110
|
+
- [MCP Dev Brasil](https://github.com/codespar/mcp-dev-brasil)
|
|
111
|
+
- [Landing Page](https://codespar.dev/mcp)
|
|
112
|
+
|
|
113
|
+
## License
|
|
114
|
+
|
|
115
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Server for Bitso — Latin American cryptocurrency exchange.
|
|
4
|
+
*
|
|
5
|
+
* Tools:
|
|
6
|
+
* - get_ticker: Get ticker data for a trading pair
|
|
7
|
+
* - list_orderbook: Get order book for a trading pair
|
|
8
|
+
* - create_order: Create a buy or sell order
|
|
9
|
+
* - get_order: Get order details by ID
|
|
10
|
+
* - cancel_order: Cancel an open order
|
|
11
|
+
* - list_orders: List orders with filters
|
|
12
|
+
* - get_balances: Get account balances
|
|
13
|
+
* - list_trades: List executed trades
|
|
14
|
+
* - list_funding_sources: List available funding sources
|
|
15
|
+
* - create_withdrawal: Create a withdrawal request
|
|
16
|
+
*
|
|
17
|
+
* Environment:
|
|
18
|
+
* BITSO_API_KEY — API key from https://bitso.com/
|
|
19
|
+
* BITSO_API_SECRET — API secret for HMAC signature
|
|
20
|
+
*/
|
|
21
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
22
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
23
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
24
|
+
import * as crypto from "node:crypto";
|
|
25
|
+
const API_KEY = process.env.BITSO_API_KEY || "";
|
|
26
|
+
const API_SECRET = process.env.BITSO_API_SECRET || "";
|
|
27
|
+
const BASE_URL = "https://api.bitso.com/v3";
|
|
28
|
+
function generateAuthHeader(method, path, body) {
|
|
29
|
+
const nonce = Date.now().toString();
|
|
30
|
+
const payload = nonce + method.toUpperCase() + path + (body || "");
|
|
31
|
+
const signature = crypto.createHmac("sha256", API_SECRET).update(payload).digest("hex");
|
|
32
|
+
return `Bitso ${API_KEY}:${nonce}:${signature}`;
|
|
33
|
+
}
|
|
34
|
+
async function bitsoRequest(method, path, body) {
|
|
35
|
+
const bodyStr = body ? JSON.stringify(body) : undefined;
|
|
36
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
37
|
+
method,
|
|
38
|
+
headers: {
|
|
39
|
+
"Content-Type": "application/json",
|
|
40
|
+
"Authorization": generateAuthHeader(method, `/v3${path}`, bodyStr),
|
|
41
|
+
},
|
|
42
|
+
body: bodyStr,
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
const err = await res.text();
|
|
46
|
+
throw new Error(`Bitso API ${res.status}: ${err}`);
|
|
47
|
+
}
|
|
48
|
+
return res.json();
|
|
49
|
+
}
|
|
50
|
+
const server = new Server({ name: "mcp-bitso", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
51
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
52
|
+
tools: [
|
|
53
|
+
{
|
|
54
|
+
name: "get_ticker",
|
|
55
|
+
description: "Get ticker data for a trading pair (price, volume, VWAP, etc.)",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
book: { type: "string", description: "Order book symbol (e.g. btc_mxn, eth_mxn, usdc_mxn)" },
|
|
60
|
+
},
|
|
61
|
+
required: ["book"],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "list_orderbook",
|
|
66
|
+
description: "Get order book (bids and asks) for a trading pair",
|
|
67
|
+
inputSchema: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: {
|
|
70
|
+
book: { type: "string", description: "Order book symbol (e.g. btc_mxn)" },
|
|
71
|
+
aggregate: { type: "boolean", description: "Aggregate orders at same price level (default true)" },
|
|
72
|
+
},
|
|
73
|
+
required: ["book"],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "create_order",
|
|
78
|
+
description: "Create a buy or sell order",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: "object",
|
|
81
|
+
properties: {
|
|
82
|
+
book: { type: "string", description: "Order book symbol (e.g. btc_mxn)" },
|
|
83
|
+
side: { type: "string", enum: ["buy", "sell"], description: "Order side" },
|
|
84
|
+
type: { type: "string", enum: ["limit", "market"], description: "Order type" },
|
|
85
|
+
major: { type: "string", description: "Amount of major currency (e.g. BTC quantity)" },
|
|
86
|
+
minor: { type: "string", description: "Amount of minor currency (e.g. MXN amount)" },
|
|
87
|
+
price: { type: "string", description: "Limit price (required for limit orders)" },
|
|
88
|
+
},
|
|
89
|
+
required: ["book", "side", "type"],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "get_order",
|
|
94
|
+
description: "Get order details by ID",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
oid: { type: "string", description: "Order ID" },
|
|
99
|
+
},
|
|
100
|
+
required: ["oid"],
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "cancel_order",
|
|
105
|
+
description: "Cancel an open order",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
oid: { type: "string", description: "Order ID to cancel" },
|
|
110
|
+
},
|
|
111
|
+
required: ["oid"],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "list_orders",
|
|
116
|
+
description: "List orders with optional filters",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
book: { type: "string", description: "Filter by order book symbol" },
|
|
121
|
+
status: { type: "string", enum: ["open", "partially_filled", "completed", "cancelled"], description: "Filter by status" },
|
|
122
|
+
limit: { type: "number", description: "Number of results (default 25, max 100)" },
|
|
123
|
+
marker: { type: "string", description: "Pagination marker" },
|
|
124
|
+
sort: { type: "string", enum: ["asc", "desc"], description: "Sort direction" },
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "get_balances",
|
|
130
|
+
description: "Get account balances for all assets",
|
|
131
|
+
inputSchema: { type: "object", properties: {} },
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "list_trades",
|
|
135
|
+
description: "List executed trades for an order book",
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: "object",
|
|
138
|
+
properties: {
|
|
139
|
+
book: { type: "string", description: "Order book symbol (e.g. btc_mxn)" },
|
|
140
|
+
limit: { type: "number", description: "Number of results (default 25, max 100)" },
|
|
141
|
+
marker: { type: "string", description: "Pagination marker" },
|
|
142
|
+
sort: { type: "string", enum: ["asc", "desc"], description: "Sort direction" },
|
|
143
|
+
},
|
|
144
|
+
required: ["book"],
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "list_funding_sources",
|
|
149
|
+
description: "List available funding sources (bank accounts, etc.)",
|
|
150
|
+
inputSchema: { type: "object", properties: {} },
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "create_withdrawal",
|
|
154
|
+
description: "Create a withdrawal request (crypto or fiat)",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
type: "object",
|
|
157
|
+
properties: {
|
|
158
|
+
currency: { type: "string", description: "Currency to withdraw (e.g. btc, eth, mxn)" },
|
|
159
|
+
amount: { type: "string", description: "Amount to withdraw" },
|
|
160
|
+
address: { type: "string", description: "Destination address (for crypto)" },
|
|
161
|
+
destination_tag: { type: "string", description: "Destination tag (for XRP, etc.)" },
|
|
162
|
+
network: { type: "string", description: "Blockchain network" },
|
|
163
|
+
},
|
|
164
|
+
required: ["currency", "amount"],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
],
|
|
168
|
+
}));
|
|
169
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
170
|
+
const { name, arguments: args } = request.params;
|
|
171
|
+
try {
|
|
172
|
+
switch (name) {
|
|
173
|
+
case "get_ticker":
|
|
174
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", `/ticker?book=${args?.book}`), null, 2) }] };
|
|
175
|
+
case "list_orderbook": {
|
|
176
|
+
const params = new URLSearchParams();
|
|
177
|
+
params.set("book", String(args?.book));
|
|
178
|
+
if (args?.aggregate !== undefined)
|
|
179
|
+
params.set("aggregate", String(args.aggregate));
|
|
180
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", `/order_book?${params}`), null, 2) }] };
|
|
181
|
+
}
|
|
182
|
+
case "create_order":
|
|
183
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("POST", "/orders", args), null, 2) }] };
|
|
184
|
+
case "get_order":
|
|
185
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", `/orders/${args?.oid}`), null, 2) }] };
|
|
186
|
+
case "cancel_order":
|
|
187
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("DELETE", `/orders/${args?.oid}`), null, 2) }] };
|
|
188
|
+
case "list_orders": {
|
|
189
|
+
const params = new URLSearchParams();
|
|
190
|
+
if (args?.book)
|
|
191
|
+
params.set("book", String(args.book));
|
|
192
|
+
if (args?.status)
|
|
193
|
+
params.set("status", String(args.status));
|
|
194
|
+
if (args?.limit)
|
|
195
|
+
params.set("limit", String(args.limit));
|
|
196
|
+
if (args?.marker)
|
|
197
|
+
params.set("marker", String(args.marker));
|
|
198
|
+
if (args?.sort)
|
|
199
|
+
params.set("sort", String(args.sort));
|
|
200
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", `/open_orders?${params}`), null, 2) }] };
|
|
201
|
+
}
|
|
202
|
+
case "get_balances":
|
|
203
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", "/balance"), null, 2) }] };
|
|
204
|
+
case "list_trades": {
|
|
205
|
+
const params = new URLSearchParams();
|
|
206
|
+
params.set("book", String(args?.book));
|
|
207
|
+
if (args?.limit)
|
|
208
|
+
params.set("limit", String(args.limit));
|
|
209
|
+
if (args?.marker)
|
|
210
|
+
params.set("marker", String(args.marker));
|
|
211
|
+
if (args?.sort)
|
|
212
|
+
params.set("sort", String(args.sort));
|
|
213
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", `/user_trades?${params}`), null, 2) }] };
|
|
214
|
+
}
|
|
215
|
+
case "list_funding_sources":
|
|
216
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", "/funding_destination"), null, 2) }] };
|
|
217
|
+
case "create_withdrawal":
|
|
218
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("POST", "/withdrawals", args), null, 2) }] };
|
|
219
|
+
default:
|
|
220
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
async function main() {
|
|
228
|
+
if (!API_KEY) {
|
|
229
|
+
console.error("BITSO_API_KEY environment variable is required");
|
|
230
|
+
process.exit(1);
|
|
231
|
+
}
|
|
232
|
+
if (!API_SECRET) {
|
|
233
|
+
console.error("BITSO_API_SECRET environment variable is required");
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
const transport = new StdioServerTransport();
|
|
237
|
+
await server.connect(transport);
|
|
238
|
+
}
|
|
239
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@codespar/mcp-bitso",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Bitso — Latin American crypto exchange, trading, funding, withdrawals",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcp-bitso": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^22.0.0",
|
|
19
|
+
"typescript": "^5.8.0"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"bitso",
|
|
25
|
+
"crypto",
|
|
26
|
+
"exchange",
|
|
27
|
+
"trading",
|
|
28
|
+
"latam"
|
|
29
|
+
]
|
|
30
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Server for Bitso — Latin American cryptocurrency exchange.
|
|
5
|
+
*
|
|
6
|
+
* Tools:
|
|
7
|
+
* - get_ticker: Get ticker data for a trading pair
|
|
8
|
+
* - list_orderbook: Get order book for a trading pair
|
|
9
|
+
* - create_order: Create a buy or sell order
|
|
10
|
+
* - get_order: Get order details by ID
|
|
11
|
+
* - cancel_order: Cancel an open order
|
|
12
|
+
* - list_orders: List orders with filters
|
|
13
|
+
* - get_balances: Get account balances
|
|
14
|
+
* - list_trades: List executed trades
|
|
15
|
+
* - list_funding_sources: List available funding sources
|
|
16
|
+
* - create_withdrawal: Create a withdrawal request
|
|
17
|
+
*
|
|
18
|
+
* Environment:
|
|
19
|
+
* BITSO_API_KEY — API key from https://bitso.com/
|
|
20
|
+
* BITSO_API_SECRET — API secret for HMAC signature
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
24
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
25
|
+
import {
|
|
26
|
+
CallToolRequestSchema,
|
|
27
|
+
ListToolsRequestSchema,
|
|
28
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
29
|
+
import * as crypto from "node:crypto";
|
|
30
|
+
|
|
31
|
+
const API_KEY = process.env.BITSO_API_KEY || "";
|
|
32
|
+
const API_SECRET = process.env.BITSO_API_SECRET || "";
|
|
33
|
+
const BASE_URL = "https://api.bitso.com/v3";
|
|
34
|
+
|
|
35
|
+
function generateAuthHeader(method: string, path: string, body?: string): string {
|
|
36
|
+
const nonce = Date.now().toString();
|
|
37
|
+
const payload = nonce + method.toUpperCase() + path + (body || "");
|
|
38
|
+
const signature = crypto.createHmac("sha256", API_SECRET).update(payload).digest("hex");
|
|
39
|
+
return `Bitso ${API_KEY}:${nonce}:${signature}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function bitsoRequest(method: string, path: string, body?: unknown): Promise<unknown> {
|
|
43
|
+
const bodyStr = body ? JSON.stringify(body) : undefined;
|
|
44
|
+
const res = await fetch(`${BASE_URL}${path}`, {
|
|
45
|
+
method,
|
|
46
|
+
headers: {
|
|
47
|
+
"Content-Type": "application/json",
|
|
48
|
+
"Authorization": generateAuthHeader(method, `/v3${path}`, bodyStr),
|
|
49
|
+
},
|
|
50
|
+
body: bodyStr,
|
|
51
|
+
});
|
|
52
|
+
if (!res.ok) {
|
|
53
|
+
const err = await res.text();
|
|
54
|
+
throw new Error(`Bitso API ${res.status}: ${err}`);
|
|
55
|
+
}
|
|
56
|
+
return res.json();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const server = new Server(
|
|
60
|
+
{ name: "mcp-bitso", version: "0.1.0" },
|
|
61
|
+
{ capabilities: { tools: {} } }
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
65
|
+
tools: [
|
|
66
|
+
{
|
|
67
|
+
name: "get_ticker",
|
|
68
|
+
description: "Get ticker data for a trading pair (price, volume, VWAP, etc.)",
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: "object",
|
|
71
|
+
properties: {
|
|
72
|
+
book: { type: "string", description: "Order book symbol (e.g. btc_mxn, eth_mxn, usdc_mxn)" },
|
|
73
|
+
},
|
|
74
|
+
required: ["book"],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "list_orderbook",
|
|
79
|
+
description: "Get order book (bids and asks) for a trading pair",
|
|
80
|
+
inputSchema: {
|
|
81
|
+
type: "object",
|
|
82
|
+
properties: {
|
|
83
|
+
book: { type: "string", description: "Order book symbol (e.g. btc_mxn)" },
|
|
84
|
+
aggregate: { type: "boolean", description: "Aggregate orders at same price level (default true)" },
|
|
85
|
+
},
|
|
86
|
+
required: ["book"],
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "create_order",
|
|
91
|
+
description: "Create a buy or sell order",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
properties: {
|
|
95
|
+
book: { type: "string", description: "Order book symbol (e.g. btc_mxn)" },
|
|
96
|
+
side: { type: "string", enum: ["buy", "sell"], description: "Order side" },
|
|
97
|
+
type: { type: "string", enum: ["limit", "market"], description: "Order type" },
|
|
98
|
+
major: { type: "string", description: "Amount of major currency (e.g. BTC quantity)" },
|
|
99
|
+
minor: { type: "string", description: "Amount of minor currency (e.g. MXN amount)" },
|
|
100
|
+
price: { type: "string", description: "Limit price (required for limit orders)" },
|
|
101
|
+
},
|
|
102
|
+
required: ["book", "side", "type"],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "get_order",
|
|
107
|
+
description: "Get order details by ID",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
oid: { type: "string", description: "Order ID" },
|
|
112
|
+
},
|
|
113
|
+
required: ["oid"],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "cancel_order",
|
|
118
|
+
description: "Cancel an open order",
|
|
119
|
+
inputSchema: {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
oid: { type: "string", description: "Order ID to cancel" },
|
|
123
|
+
},
|
|
124
|
+
required: ["oid"],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "list_orders",
|
|
129
|
+
description: "List orders with optional filters",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
book: { type: "string", description: "Filter by order book symbol" },
|
|
134
|
+
status: { type: "string", enum: ["open", "partially_filled", "completed", "cancelled"], description: "Filter by status" },
|
|
135
|
+
limit: { type: "number", description: "Number of results (default 25, max 100)" },
|
|
136
|
+
marker: { type: "string", description: "Pagination marker" },
|
|
137
|
+
sort: { type: "string", enum: ["asc", "desc"], description: "Sort direction" },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "get_balances",
|
|
143
|
+
description: "Get account balances for all assets",
|
|
144
|
+
inputSchema: { type: "object", properties: {} },
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: "list_trades",
|
|
148
|
+
description: "List executed trades for an order book",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: "object",
|
|
151
|
+
properties: {
|
|
152
|
+
book: { type: "string", description: "Order book symbol (e.g. btc_mxn)" },
|
|
153
|
+
limit: { type: "number", description: "Number of results (default 25, max 100)" },
|
|
154
|
+
marker: { type: "string", description: "Pagination marker" },
|
|
155
|
+
sort: { type: "string", enum: ["asc", "desc"], description: "Sort direction" },
|
|
156
|
+
},
|
|
157
|
+
required: ["book"],
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: "list_funding_sources",
|
|
162
|
+
description: "List available funding sources (bank accounts, etc.)",
|
|
163
|
+
inputSchema: { type: "object", properties: {} },
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "create_withdrawal",
|
|
167
|
+
description: "Create a withdrawal request (crypto or fiat)",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: {
|
|
171
|
+
currency: { type: "string", description: "Currency to withdraw (e.g. btc, eth, mxn)" },
|
|
172
|
+
amount: { type: "string", description: "Amount to withdraw" },
|
|
173
|
+
address: { type: "string", description: "Destination address (for crypto)" },
|
|
174
|
+
destination_tag: { type: "string", description: "Destination tag (for XRP, etc.)" },
|
|
175
|
+
network: { type: "string", description: "Blockchain network" },
|
|
176
|
+
},
|
|
177
|
+
required: ["currency", "amount"],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
184
|
+
const { name, arguments: args } = request.params;
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
switch (name) {
|
|
188
|
+
case "get_ticker":
|
|
189
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", `/ticker?book=${args?.book}`), null, 2) }] };
|
|
190
|
+
case "list_orderbook": {
|
|
191
|
+
const params = new URLSearchParams();
|
|
192
|
+
params.set("book", String(args?.book));
|
|
193
|
+
if (args?.aggregate !== undefined) params.set("aggregate", String(args.aggregate));
|
|
194
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", `/order_book?${params}`), null, 2) }] };
|
|
195
|
+
}
|
|
196
|
+
case "create_order":
|
|
197
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("POST", "/orders", args), null, 2) }] };
|
|
198
|
+
case "get_order":
|
|
199
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", `/orders/${args?.oid}`), null, 2) }] };
|
|
200
|
+
case "cancel_order":
|
|
201
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("DELETE", `/orders/${args?.oid}`), null, 2) }] };
|
|
202
|
+
case "list_orders": {
|
|
203
|
+
const params = new URLSearchParams();
|
|
204
|
+
if (args?.book) params.set("book", String(args.book));
|
|
205
|
+
if (args?.status) params.set("status", String(args.status));
|
|
206
|
+
if (args?.limit) params.set("limit", String(args.limit));
|
|
207
|
+
if (args?.marker) params.set("marker", String(args.marker));
|
|
208
|
+
if (args?.sort) params.set("sort", String(args.sort));
|
|
209
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", `/open_orders?${params}`), null, 2) }] };
|
|
210
|
+
}
|
|
211
|
+
case "get_balances":
|
|
212
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", "/balance"), null, 2) }] };
|
|
213
|
+
case "list_trades": {
|
|
214
|
+
const params = new URLSearchParams();
|
|
215
|
+
params.set("book", String(args?.book));
|
|
216
|
+
if (args?.limit) params.set("limit", String(args.limit));
|
|
217
|
+
if (args?.marker) params.set("marker", String(args.marker));
|
|
218
|
+
if (args?.sort) params.set("sort", String(args.sort));
|
|
219
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", `/user_trades?${params}`), null, 2) }] };
|
|
220
|
+
}
|
|
221
|
+
case "list_funding_sources":
|
|
222
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("GET", "/funding_destination"), null, 2) }] };
|
|
223
|
+
case "create_withdrawal":
|
|
224
|
+
return { content: [{ type: "text", text: JSON.stringify(await bitsoRequest("POST", "/withdrawals", args), null, 2) }] };
|
|
225
|
+
default:
|
|
226
|
+
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
227
|
+
}
|
|
228
|
+
} catch (err) {
|
|
229
|
+
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
async function main() {
|
|
234
|
+
if (!API_KEY) {
|
|
235
|
+
console.error("BITSO_API_KEY environment variable is required");
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
if (!API_SECRET) {
|
|
239
|
+
console.error("BITSO_API_SECRET environment variable is required");
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
const transport = new StdioServerTransport();
|
|
243
|
+
await server.connect(transport);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
main().catch(console.error);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|