@codespar/mcp-foxbit 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 ADDED
@@ -0,0 +1,130 @@
1
+ # @codespar/mcp-foxbit
2
+
3
+ > MCP server for **Foxbit** — Brazilian cryptocurrency exchange with trading, orderbook, and market data
4
+
5
+ [![npm](https://img.shields.io/npm/v/@codespar/mcp-foxbit)](https://www.npmjs.com/package/@codespar/mcp-foxbit)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](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
+ "foxbit": {
18
+ "command": "npx",
19
+ "args": ["-y", "@codespar/mcp-foxbit"],
20
+ "env": {
21
+ "FOXBIT_API_KEY": "your-key",
22
+ "FOXBIT_API_SECRET": "your-secret"
23
+ }
24
+ }
25
+ }
26
+ }
27
+ ```
28
+
29
+ ### Claude Code
30
+
31
+ ```bash
32
+ claude mcp add foxbit -- npx @codespar/mcp-foxbit
33
+ ```
34
+
35
+ ### Cursor / VS Code
36
+
37
+ Add to `.cursor/mcp.json` or `.vscode/mcp.json`:
38
+
39
+ ```json
40
+ {
41
+ "servers": {
42
+ "foxbit": {
43
+ "command": "npx",
44
+ "args": ["-y", "@codespar/mcp-foxbit"],
45
+ "env": {
46
+ "FOXBIT_API_KEY": "your-key",
47
+ "FOXBIT_API_SECRET": "your-secret"
48
+ }
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ## Tools
55
+
56
+ | Tool | Description |
57
+ |------|-------------|
58
+ | `list_markets` | List all available trading pairs |
59
+ | `get_ticker` | Get 24h ticker data for a market |
60
+ | `get_orderbook` | Get order book for a market |
61
+ | `get_account_balances` | Get account balances |
62
+ | `create_order` | Create a buy or sell order (limit/market) |
63
+ | `get_order` | Get order details by ID |
64
+ | `list_orders` | List orders with filters |
65
+ | `cancel_order` | Cancel an open order |
66
+ | `list_trades` | List executed trades |
67
+ | `list_deposits_withdrawals` | List deposits and withdrawals for a currency |
68
+
69
+ ## Authentication
70
+
71
+ Foxbit uses HMAC-SHA256 request signing. Each request includes three headers:
72
+
73
+ - `X-FB-ACCESS-KEY` — API key
74
+ - `X-FB-ACCESS-TIMESTAMP` — UNIX timestamp in milliseconds
75
+ - `X-FB-ACCESS-SIGNATURE` — hex HMAC-SHA256 of `timestamp + method + path + queryString + body` using API secret
76
+
77
+ Base URL: `https://api.foxbit.com.br/rest/v3`
78
+
79
+ ### Get your credentials
80
+
81
+ 1. Go to [Foxbit](https://app.foxbit.com.br)
82
+ 2. Create an account (KYC required for Brazilian residents)
83
+ 3. Navigate to API settings to generate key and secret
84
+ 4. Set the environment variables
85
+
86
+ ## Environment Variables
87
+
88
+ | Variable | Required | Description |
89
+ |----------|----------|-------------|
90
+ | `FOXBIT_API_KEY` | Yes | API key from Foxbit |
91
+ | `FOXBIT_API_SECRET` | Yes | API secret for HMAC-SHA256 |
92
+
93
+ ## Brazilian Crypto Exchanges in CodeSpar
94
+
95
+ Hedge liquidity across multiple BR venues:
96
+
97
+ - **[Mercado Bitcoin](../mercado-bitcoin)** — biggest BR exchange, 200+ tokens, deep altcoin coverage
98
+ - **Foxbit (this)** — 2nd BR exchange, focus on BTC / ETH / LTC, strong institutional desk
99
+
100
+ Merchants and traders use both for best execution and redundancy.
101
+
102
+ ## Roadmap
103
+
104
+ ### v0.2 (planned)
105
+ - `get_candles` — OHLCV candlestick data
106
+ - `create_withdrawal` — Initiate crypto/PIX withdrawal
107
+ - `list_currencies` — Available currencies and networks
108
+ - `get_fees` — Trading fees for account tier
109
+ - `create_stop_order` — Stop-limit / stop-market orders
110
+
111
+ ### v0.3 (planned)
112
+ - Institutional / OTC desk integrations
113
+ - WebSocket market data streams (where MCP transport allows)
114
+
115
+ 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).
116
+
117
+ ## Links
118
+
119
+ - [Foxbit Website](https://foxbit.com.br)
120
+ - [Foxbit API Documentation](https://docs.foxbit.com.br)
121
+ - [MCP Dev Brasil](https://github.com/codespar/mcp-dev-brasil)
122
+ - [Landing Page](https://codespar.dev/mcp)
123
+
124
+ ## Enterprise
125
+
126
+ Need governance, budget limits, and audit trails for agent payments? [CodeSpar Enterprise](https://codespar.dev/enterprise) adds policy engine, payment routing, and compliance templates on top of these MCP servers.
127
+
128
+ ## License
129
+
130
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * MCP Server for Foxbit — Brazilian cryptocurrency exchange.
4
+ *
5
+ * Tools:
6
+ * - list_markets: List all available trading pairs
7
+ * - get_ticker: Get 24h ticker data for a market
8
+ * - get_orderbook: Get order book for a market
9
+ * - get_account_balances: Get account balances
10
+ * - create_order: Create a buy or sell order (limit/market)
11
+ * - get_order: Get order details by ID
12
+ * - list_orders: List orders with filters
13
+ * - cancel_order: Cancel an open order
14
+ * - list_trades: List executed trades
15
+ * - list_deposits_withdrawals: List deposits and withdrawals for a currency
16
+ *
17
+ * Environment:
18
+ * FOXBIT_API_KEY — API key from https://app.foxbit.com.br/
19
+ * FOXBIT_API_SECRET — API secret for HMAC-SHA256 signature
20
+ */
21
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
23
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
24
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
25
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
26
+ import * as crypto from "node:crypto";
27
+ const API_KEY = process.env.FOXBIT_API_KEY || "";
28
+ const API_SECRET = process.env.FOXBIT_API_SECRET || "";
29
+ const BASE_URL = "https://api.foxbit.com.br";
30
+ const PATH_PREFIX = "/rest/v3";
31
+ async function foxbitRequest(method, path, query, body) {
32
+ const timestamp = Date.now().toString();
33
+ const bodyStr = body ? JSON.stringify(body) : "";
34
+ const params = new URLSearchParams();
35
+ if (query) {
36
+ for (const [k, v] of Object.entries(query)) {
37
+ if (v !== undefined && v !== null && v !== "")
38
+ params.set(k, String(v));
39
+ }
40
+ }
41
+ const queryString = params.toString();
42
+ const fullPath = `${PATH_PREFIX}${path}`;
43
+ const prehash = timestamp + method.toUpperCase() + fullPath + queryString + bodyStr;
44
+ const signature = crypto.createHmac("sha256", API_SECRET).update(prehash).digest("hex");
45
+ const url = `${BASE_URL}${fullPath}${queryString ? `?${queryString}` : ""}`;
46
+ const res = await fetch(url, {
47
+ method,
48
+ headers: {
49
+ "Content-Type": "application/json",
50
+ "X-FB-ACCESS-KEY": API_KEY,
51
+ "X-FB-ACCESS-TIMESTAMP": timestamp,
52
+ "X-FB-ACCESS-SIGNATURE": signature,
53
+ },
54
+ body: bodyStr || undefined,
55
+ });
56
+ if (!res.ok) {
57
+ const err = await res.text();
58
+ throw new Error(`Foxbit API ${res.status}: ${err}`);
59
+ }
60
+ return res.json();
61
+ }
62
+ const server = new Server({ name: "mcp-foxbit", version: "0.1.0" }, { capabilities: { tools: {} } });
63
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
64
+ tools: [
65
+ {
66
+ name: "list_markets",
67
+ description: "List all available trading pairs / markets on Foxbit",
68
+ inputSchema: { type: "object", properties: {} },
69
+ },
70
+ {
71
+ name: "get_ticker",
72
+ description: "Get 24h ticker data for a market (price, volume, high/low)",
73
+ inputSchema: {
74
+ type: "object",
75
+ properties: {
76
+ symbol: { type: "string", description: "Market symbol (e.g. btcbrl, ethbrl, ltcbrl)" },
77
+ },
78
+ required: ["symbol"],
79
+ },
80
+ },
81
+ {
82
+ name: "get_orderbook",
83
+ description: "Get order book (bids and asks) for a market",
84
+ inputSchema: {
85
+ type: "object",
86
+ properties: {
87
+ symbol: { type: "string", description: "Market symbol (e.g. btcbrl)" },
88
+ depth: { type: "number", description: "Number of price levels per side" },
89
+ },
90
+ required: ["symbol"],
91
+ },
92
+ },
93
+ {
94
+ name: "get_account_balances",
95
+ description: "Get account balances for all currencies",
96
+ inputSchema: { type: "object", properties: {} },
97
+ },
98
+ {
99
+ name: "create_order",
100
+ description: "Create a buy or sell order (limit or market)",
101
+ inputSchema: {
102
+ type: "object",
103
+ properties: {
104
+ market_symbol: { type: "string", description: "Market symbol (e.g. btcbrl)" },
105
+ side: { type: "string", enum: ["BUY", "SELL"], description: "Order side" },
106
+ type: { type: "string", enum: ["LIMIT", "MARKET", "STOP_LIMIT", "STOP_MARKET"], description: "Order type" },
107
+ quantity: { type: "string", description: "Base asset quantity (e.g. BTC)" },
108
+ price: { type: "string", description: "Limit price (required for LIMIT orders)" },
109
+ client_order_id: { type: "string", description: "Client-supplied order ID" },
110
+ time_in_force: { type: "string", enum: ["GTC", "IOC", "FOK"], description: "Time in force" },
111
+ },
112
+ required: ["market_symbol", "side", "type", "quantity"],
113
+ },
114
+ },
115
+ {
116
+ name: "get_order",
117
+ description: "Get order details by ID",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ id: { type: "string", description: "Order ID" },
122
+ },
123
+ required: ["id"],
124
+ },
125
+ },
126
+ {
127
+ name: "list_orders",
128
+ description: "List orders with optional filters",
129
+ inputSchema: {
130
+ type: "object",
131
+ properties: {
132
+ market_symbol: { type: "string", description: "Filter by market symbol" },
133
+ state: { type: "string", enum: ["ACTIVE", "FILLED", "CANCELED", "PARTIALLY_FILLED", "PARTIALLY_CANCELED"], description: "Filter by order state" },
134
+ side: { type: "string", enum: ["BUY", "SELL"], description: "Filter by side" },
135
+ start_time: { type: "string", description: "Start time (ISO 8601)" },
136
+ end_time: { type: "string", description: "End time (ISO 8601)" },
137
+ page_size: { type: "number", description: "Results per page" },
138
+ page: { type: "number", description: "Page number" },
139
+ },
140
+ },
141
+ },
142
+ {
143
+ name: "cancel_order",
144
+ description: "Cancel an open order by ID",
145
+ inputSchema: {
146
+ type: "object",
147
+ properties: {
148
+ id: { type: "string", description: "Order ID to cancel" },
149
+ },
150
+ required: ["id"],
151
+ },
152
+ },
153
+ {
154
+ name: "list_trades",
155
+ description: "List user's executed trades",
156
+ inputSchema: {
157
+ type: "object",
158
+ properties: {
159
+ market_symbol: { type: "string", description: "Filter by market symbol" },
160
+ start_time: { type: "string", description: "Start time (ISO 8601)" },
161
+ end_time: { type: "string", description: "End time (ISO 8601)" },
162
+ page_size: { type: "number", description: "Results per page" },
163
+ page: { type: "number", description: "Page number" },
164
+ },
165
+ },
166
+ },
167
+ {
168
+ name: "list_deposits_withdrawals",
169
+ description: "List deposits and withdrawals (transactions) for a currency",
170
+ inputSchema: {
171
+ type: "object",
172
+ properties: {
173
+ currency_symbol: { type: "string", description: "Currency symbol (e.g. brl, btc, eth)" },
174
+ type: { type: "string", enum: ["deposit", "withdraw"], description: "Filter by transaction type" },
175
+ start_time: { type: "string", description: "Start time (ISO 8601)" },
176
+ end_time: { type: "string", description: "End time (ISO 8601)" },
177
+ page_size: { type: "number", description: "Results per page" },
178
+ page: { type: "number", description: "Page number" },
179
+ },
180
+ required: ["currency_symbol"],
181
+ },
182
+ },
183
+ ],
184
+ }));
185
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
186
+ const { name, arguments: args } = request.params;
187
+ try {
188
+ switch (name) {
189
+ case "list_markets":
190
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", "/markets"), null, 2) }] };
191
+ case "get_ticker":
192
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", `/markets/${args?.symbol}/ticker/24hr`), null, 2) }] };
193
+ case "get_orderbook":
194
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", `/markets/${args?.symbol}/orderbook`, { depth: args?.depth }), null, 2) }] };
195
+ case "get_account_balances":
196
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", "/accounts"), null, 2) }] };
197
+ case "create_order":
198
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("POST", "/orders", undefined, args), null, 2) }] };
199
+ case "get_order":
200
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", `/orders/${args?.id}`), null, 2) }] };
201
+ case "list_orders":
202
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", "/orders", args), null, 2) }] };
203
+ case "cancel_order":
204
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("DELETE", `/orders/${args?.id}`), null, 2) }] };
205
+ case "list_trades":
206
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", "/trades", args), null, 2) }] };
207
+ case "list_deposits_withdrawals": {
208
+ const { currency_symbol, ...rest } = (args || {});
209
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", `/accounts/${currency_symbol}/transactions`, rest), null, 2) }] };
210
+ }
211
+ default:
212
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
213
+ }
214
+ }
215
+ catch (err) {
216
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
217
+ }
218
+ });
219
+ async function main() {
220
+ if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
221
+ const { default: express } = await import("express");
222
+ const { randomUUID } = await import("node:crypto");
223
+ const app = express();
224
+ app.use(express.json());
225
+ const transports = new Map();
226
+ app.get("/health", (_req, res) => res.json({ status: "ok", sessions: transports.size }));
227
+ app.post("/mcp", async (req, res) => {
228
+ const sid = req.headers["mcp-session-id"];
229
+ if (sid && transports.has(sid)) {
230
+ await transports.get(sid).handleRequest(req, res, req.body);
231
+ return;
232
+ }
233
+ if (!sid && isInitializeRequest(req.body)) {
234
+ const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
235
+ t.onclose = () => { if (t.sessionId)
236
+ transports.delete(t.sessionId); };
237
+ const s = new Server({ name: "mcp-foxbit", version: "0.1.0" }, { capabilities: { tools: {} } });
238
+ server._requestHandlers.forEach((v, k) => s._requestHandlers.set(k, v));
239
+ server._notificationHandlers?.forEach((v, k) => s._notificationHandlers.set(k, v));
240
+ await s.connect(t);
241
+ await t.handleRequest(req, res, req.body);
242
+ return;
243
+ }
244
+ res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
245
+ });
246
+ app.get("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
247
+ await transports.get(sid).handleRequest(req, res);
248
+ else
249
+ res.status(400).send("Invalid session"); });
250
+ app.delete("/mcp", async (req, res) => { const sid = req.headers["mcp-session-id"]; if (sid && transports.has(sid))
251
+ await transports.get(sid).handleRequest(req, res);
252
+ else
253
+ res.status(400).send("Invalid session"); });
254
+ const port = Number(process.env.MCP_PORT) || 3000;
255
+ app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
256
+ }
257
+ else {
258
+ const transport = new StdioServerTransport();
259
+ await server.connect(transport);
260
+ }
261
+ }
262
+ main().catch(console.error);
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@codespar/mcp-foxbit",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for Foxbit — Brazilian crypto exchange, trading, orderbook, institutional",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "mcp-foxbit": "./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
+ "foxbit",
25
+ "crypto",
26
+ "exchange",
27
+ "trading",
28
+ "brazil"
29
+ ],
30
+ "mcpName": "io.github.codespar/mcp-foxbit"
31
+ }
package/server.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.codespar/mcp-foxbit",
4
+ "description": "MCP server for Foxbit — Brazilian crypto exchange, trading, orderbook, institutional",
5
+ "repository": {
6
+ "url": "https://github.com/codespar/mcp-dev-brasil",
7
+ "source": "github",
8
+ "subfolder": "packages/crypto/foxbit"
9
+ },
10
+ "version": "0.1.0",
11
+ "packages": [
12
+ {
13
+ "registryType": "npm",
14
+ "identifier": "@codespar/mcp-foxbit",
15
+ "version": "0.1.0",
16
+ "transport": {
17
+ "type": "stdio"
18
+ },
19
+ "environmentVariables": [
20
+ {
21
+ "name": "FOXBIT_API_KEY",
22
+ "description": "API key for Foxbit",
23
+ "isRequired": true,
24
+ "format": "string",
25
+ "isSecret": true
26
+ },
27
+ {
28
+ "name": "FOXBIT_API_SECRET",
29
+ "description": "API secret for Foxbit HMAC-SHA256 signature",
30
+ "isRequired": true,
31
+ "format": "string",
32
+ "isSecret": true
33
+ }
34
+ ]
35
+ }
36
+ ]
37
+ }
package/src/index.ts ADDED
@@ -0,0 +1,269 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * MCP Server for Foxbit — Brazilian cryptocurrency exchange.
5
+ *
6
+ * Tools:
7
+ * - list_markets: List all available trading pairs
8
+ * - get_ticker: Get 24h ticker data for a market
9
+ * - get_orderbook: Get order book for a market
10
+ * - get_account_balances: Get account balances
11
+ * - create_order: Create a buy or sell order (limit/market)
12
+ * - get_order: Get order details by ID
13
+ * - list_orders: List orders with filters
14
+ * - cancel_order: Cancel an open order
15
+ * - list_trades: List executed trades
16
+ * - list_deposits_withdrawals: List deposits and withdrawals for a currency
17
+ *
18
+ * Environment:
19
+ * FOXBIT_API_KEY — API key from https://app.foxbit.com.br/
20
+ * FOXBIT_API_SECRET — API secret for HMAC-SHA256 signature
21
+ */
22
+
23
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
24
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
25
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
26
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
27
+ import {
28
+ CallToolRequestSchema,
29
+ ListToolsRequestSchema,
30
+ } from "@modelcontextprotocol/sdk/types.js";
31
+ import * as crypto from "node:crypto";
32
+
33
+ const API_KEY = process.env.FOXBIT_API_KEY || "";
34
+ const API_SECRET = process.env.FOXBIT_API_SECRET || "";
35
+ const BASE_URL = "https://api.foxbit.com.br";
36
+ const PATH_PREFIX = "/rest/v3";
37
+
38
+ async function foxbitRequest(
39
+ method: string,
40
+ path: string,
41
+ query?: Record<string, string | number | boolean | undefined>,
42
+ body?: unknown,
43
+ ): Promise<unknown> {
44
+ const timestamp = Date.now().toString();
45
+ const bodyStr = body ? JSON.stringify(body) : "";
46
+
47
+ const params = new URLSearchParams();
48
+ if (query) {
49
+ for (const [k, v] of Object.entries(query)) {
50
+ if (v !== undefined && v !== null && v !== "") params.set(k, String(v));
51
+ }
52
+ }
53
+ const queryString = params.toString();
54
+ const fullPath = `${PATH_PREFIX}${path}`;
55
+
56
+ const prehash = timestamp + method.toUpperCase() + fullPath + queryString + bodyStr;
57
+ const signature = crypto.createHmac("sha256", API_SECRET).update(prehash).digest("hex");
58
+
59
+ const url = `${BASE_URL}${fullPath}${queryString ? `?${queryString}` : ""}`;
60
+ const res = await fetch(url, {
61
+ method,
62
+ headers: {
63
+ "Content-Type": "application/json",
64
+ "X-FB-ACCESS-KEY": API_KEY,
65
+ "X-FB-ACCESS-TIMESTAMP": timestamp,
66
+ "X-FB-ACCESS-SIGNATURE": signature,
67
+ },
68
+ body: bodyStr || undefined,
69
+ });
70
+ if (!res.ok) {
71
+ const err = await res.text();
72
+ throw new Error(`Foxbit API ${res.status}: ${err}`);
73
+ }
74
+ return res.json();
75
+ }
76
+
77
+ const server = new Server(
78
+ { name: "mcp-foxbit", version: "0.1.0" },
79
+ { capabilities: { tools: {} } },
80
+ );
81
+
82
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
83
+ tools: [
84
+ {
85
+ name: "list_markets",
86
+ description: "List all available trading pairs / markets on Foxbit",
87
+ inputSchema: { type: "object", properties: {} },
88
+ },
89
+ {
90
+ name: "get_ticker",
91
+ description: "Get 24h ticker data for a market (price, volume, high/low)",
92
+ inputSchema: {
93
+ type: "object",
94
+ properties: {
95
+ symbol: { type: "string", description: "Market symbol (e.g. btcbrl, ethbrl, ltcbrl)" },
96
+ },
97
+ required: ["symbol"],
98
+ },
99
+ },
100
+ {
101
+ name: "get_orderbook",
102
+ description: "Get order book (bids and asks) for a market",
103
+ inputSchema: {
104
+ type: "object",
105
+ properties: {
106
+ symbol: { type: "string", description: "Market symbol (e.g. btcbrl)" },
107
+ depth: { type: "number", description: "Number of price levels per side" },
108
+ },
109
+ required: ["symbol"],
110
+ },
111
+ },
112
+ {
113
+ name: "get_account_balances",
114
+ description: "Get account balances for all currencies",
115
+ inputSchema: { type: "object", properties: {} },
116
+ },
117
+ {
118
+ name: "create_order",
119
+ description: "Create a buy or sell order (limit or market)",
120
+ inputSchema: {
121
+ type: "object",
122
+ properties: {
123
+ market_symbol: { type: "string", description: "Market symbol (e.g. btcbrl)" },
124
+ side: { type: "string", enum: ["BUY", "SELL"], description: "Order side" },
125
+ type: { type: "string", enum: ["LIMIT", "MARKET", "STOP_LIMIT", "STOP_MARKET"], description: "Order type" },
126
+ quantity: { type: "string", description: "Base asset quantity (e.g. BTC)" },
127
+ price: { type: "string", description: "Limit price (required for LIMIT orders)" },
128
+ client_order_id: { type: "string", description: "Client-supplied order ID" },
129
+ time_in_force: { type: "string", enum: ["GTC", "IOC", "FOK"], description: "Time in force" },
130
+ },
131
+ required: ["market_symbol", "side", "type", "quantity"],
132
+ },
133
+ },
134
+ {
135
+ name: "get_order",
136
+ description: "Get order details by ID",
137
+ inputSchema: {
138
+ type: "object",
139
+ properties: {
140
+ id: { type: "string", description: "Order ID" },
141
+ },
142
+ required: ["id"],
143
+ },
144
+ },
145
+ {
146
+ name: "list_orders",
147
+ description: "List orders with optional filters",
148
+ inputSchema: {
149
+ type: "object",
150
+ properties: {
151
+ market_symbol: { type: "string", description: "Filter by market symbol" },
152
+ state: { type: "string", enum: ["ACTIVE", "FILLED", "CANCELED", "PARTIALLY_FILLED", "PARTIALLY_CANCELED"], description: "Filter by order state" },
153
+ side: { type: "string", enum: ["BUY", "SELL"], description: "Filter by side" },
154
+ start_time: { type: "string", description: "Start time (ISO 8601)" },
155
+ end_time: { type: "string", description: "End time (ISO 8601)" },
156
+ page_size: { type: "number", description: "Results per page" },
157
+ page: { type: "number", description: "Page number" },
158
+ },
159
+ },
160
+ },
161
+ {
162
+ name: "cancel_order",
163
+ description: "Cancel an open order by ID",
164
+ inputSchema: {
165
+ type: "object",
166
+ properties: {
167
+ id: { type: "string", description: "Order ID to cancel" },
168
+ },
169
+ required: ["id"],
170
+ },
171
+ },
172
+ {
173
+ name: "list_trades",
174
+ description: "List user's executed trades",
175
+ inputSchema: {
176
+ type: "object",
177
+ properties: {
178
+ market_symbol: { type: "string", description: "Filter by market symbol" },
179
+ start_time: { type: "string", description: "Start time (ISO 8601)" },
180
+ end_time: { type: "string", description: "End time (ISO 8601)" },
181
+ page_size: { type: "number", description: "Results per page" },
182
+ page: { type: "number", description: "Page number" },
183
+ },
184
+ },
185
+ },
186
+ {
187
+ name: "list_deposits_withdrawals",
188
+ description: "List deposits and withdrawals (transactions) for a currency",
189
+ inputSchema: {
190
+ type: "object",
191
+ properties: {
192
+ currency_symbol: { type: "string", description: "Currency symbol (e.g. brl, btc, eth)" },
193
+ type: { type: "string", enum: ["deposit", "withdraw"], description: "Filter by transaction type" },
194
+ start_time: { type: "string", description: "Start time (ISO 8601)" },
195
+ end_time: { type: "string", description: "End time (ISO 8601)" },
196
+ page_size: { type: "number", description: "Results per page" },
197
+ page: { type: "number", description: "Page number" },
198
+ },
199
+ required: ["currency_symbol"],
200
+ },
201
+ },
202
+ ],
203
+ }));
204
+
205
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
206
+ const { name, arguments: args } = request.params;
207
+
208
+ try {
209
+ switch (name) {
210
+ case "list_markets":
211
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", "/markets"), null, 2) }] };
212
+ case "get_ticker":
213
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", `/markets/${args?.symbol}/ticker/24hr`), null, 2) }] };
214
+ case "get_orderbook":
215
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", `/markets/${args?.symbol}/orderbook`, { depth: args?.depth as number | undefined }), null, 2) }] };
216
+ case "get_account_balances":
217
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", "/accounts"), null, 2) }] };
218
+ case "create_order":
219
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("POST", "/orders", undefined, args), null, 2) }] };
220
+ case "get_order":
221
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", `/orders/${args?.id}`), null, 2) }] };
222
+ case "list_orders":
223
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", "/orders", args as Record<string, string | number | undefined>), null, 2) }] };
224
+ case "cancel_order":
225
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("DELETE", `/orders/${args?.id}`), null, 2) }] };
226
+ case "list_trades":
227
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", "/trades", args as Record<string, string | number | undefined>), null, 2) }] };
228
+ case "list_deposits_withdrawals": {
229
+ const { currency_symbol, ...rest } = (args || {}) as Record<string, string | number | undefined>;
230
+ return { content: [{ type: "text", text: JSON.stringify(await foxbitRequest("GET", `/accounts/${currency_symbol}/transactions`, rest), null, 2) }] };
231
+ }
232
+ default:
233
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
234
+ }
235
+ } catch (err) {
236
+ return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
237
+ }
238
+ });
239
+
240
+ async function main() {
241
+ if (process.argv.includes("--http") || process.env.MCP_HTTP === "true") {
242
+ const { default: express } = await import("express");
243
+ const { randomUUID } = await import("node:crypto");
244
+ const app = express();
245
+ app.use(express.json());
246
+ const transports = new Map<string, StreamableHTTPServerTransport>();
247
+ app.get("/health", (_req: any, res: any) => res.json({ status: "ok", sessions: transports.size }));
248
+ app.post("/mcp", async (req: any, res: any) => {
249
+ const sid = req.headers["mcp-session-id"] as string | undefined;
250
+ if (sid && transports.has(sid)) { await transports.get(sid)!.handleRequest(req, res, req.body); return; }
251
+ if (!sid && isInitializeRequest(req.body)) {
252
+ const t = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (id) => { transports.set(id, t); } });
253
+ t.onclose = () => { if (t.sessionId) transports.delete(t.sessionId); };
254
+ const s = new Server({ name: "mcp-foxbit", version: "0.1.0" }, { capabilities: { tools: {} } }); (server as any)._requestHandlers.forEach((v: any, k: any) => (s as any)._requestHandlers.set(k, v)); (server as any)._notificationHandlers?.forEach((v: any, k: any) => (s as any)._notificationHandlers.set(k, v)); await s.connect(t);
255
+ await t.handleRequest(req, res, req.body); return;
256
+ }
257
+ res.status(400).json({ jsonrpc: "2.0", error: { code: -32000, message: "Bad Request" }, id: null });
258
+ });
259
+ app.get("/mcp", async (req: any, res: any) => { const sid = req.headers["mcp-session-id"] as string; if (sid && transports.has(sid)) await transports.get(sid)!.handleRequest(req, res); else res.status(400).send("Invalid session"); });
260
+ app.delete("/mcp", async (req: any, res: any) => { const sid = req.headers["mcp-session-id"] as string; if (sid && transports.has(sid)) await transports.get(sid)!.handleRequest(req, res); else res.status(400).send("Invalid session"); });
261
+ const port = Number(process.env.MCP_PORT) || 3000;
262
+ app.listen(port, () => { console.error(`MCP HTTP server on http://localhost:${port}/mcp`); });
263
+ } else {
264
+ const transport = new StdioServerTransport();
265
+ await server.connect(transport);
266
+ }
267
+ }
268
+
269
+ 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
+ }