From 3571b6441f7997ce839b4e2278c06d3b3dda7f14 Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj Date: Tue, 19 Aug 2025 09:20:13 +0530 Subject: [PATCH 01/12] update auth --- src/lib/upbit-auth.ts | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib/upbit-auth.ts b/src/lib/upbit-auth.ts index e2c4e31..b95d0df 100644 --- a/src/lib/upbit-auth.ts +++ b/src/lib/upbit-auth.ts @@ -16,21 +16,23 @@ export function ensurePrivateEnabled(): void { } export function signJwtToken( - queryParams?: Record, + params?: Record, ): string { const payload: Record = { access_key: config.upbit.accessKey, nonce: crypto.randomUUID(), }; - if (queryParams && Object.keys(queryParams).length > 0) { - const queryString = Object.entries(queryParams) - .map(([k, v]) => `${k}=${v}`) - .join("&"); - const queryHash = crypto - .createHash("sha512") - .update(queryString) - .digest("hex"); + if (params && Object.keys(params).length > 0) { + const searchParams = new URLSearchParams(); + const sortedKeys = Object.keys(params).sort(); + for (const key of sortedKeys) { + const value = params[key]; + if (value === undefined) continue; + searchParams.append(key, String(value)); + } + const encoded = searchParams.toString(); + const queryHash = crypto.createHash("sha512").update(encoded).digest("hex"); payload.query_hash = queryHash; payload.query_hash_alg = "SHA512"; } From 34cfee7ad7fceb67e20f543bf6319acb61bd1ab4 Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj Date: Tue, 19 Aug 2025 09:20:28 +0530 Subject: [PATCH 02/12] update params --- src/tools/create-order.ts | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/tools/create-order.ts b/src/tools/create-order.ts index eb503b1..5d80468 100644 --- a/src/tools/create-order.ts +++ b/src/tools/create-order.ts @@ -10,6 +10,9 @@ const paramsSchema = z ord_type: z.enum(["limit", "price", "market"]), volume: z.string().optional(), price: z.string().optional(), + time_in_force: z.enum(["ioc", "fok", "post_only"]).optional(), + smp_type: z.enum(["cancel_maker", "cancel_taker", "reduce"]).optional(), + identifier: z.string().optional(), }) .refine((p) => (p.ord_type === "limit" ? p.volume && p.price : true), { message: "Limit orders require both volume and price", @@ -19,6 +22,9 @@ const paramsSchema = z }) .refine((p) => (p.ord_type === "market" ? !!p.volume : true), { message: "Market sell (market) requires volume", + }) + .refine((p) => !(p.time_in_force === "post_only" && p.smp_type), { + message: "post_only cannot be used with smp_type", }); type Params = z.infer; @@ -27,17 +33,29 @@ export const createOrderTool = { name: "CREATE_ORDER", description: "Create an Upbit order (requires private API)", parameters: paramsSchema, - execute: async ({ market, side, ord_type, volume, price }: Params) => { + execute: async ({ + market, + side, + ord_type, + volume, + price, + time_in_force, + smp_type, + identifier, + }: Params) => { ensurePrivateEnabled(); const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; const client = createHttpClient(baseURL); - const query: Record = { market, side, ord_type }; - if (volume) query.volume = volume; - if (price) query.price = price; - const token = signJwtToken(query); + const body: Record = { market, side, ord_type }; + if (volume) body.volume = volume; + if (price) body.price = price; + if (time_in_force) body.time_in_force = time_in_force; + if (smp_type) body.smp_type = smp_type; + if (identifier) body.identifier = identifier; + const token = signJwtToken(body); const data = await fetchJson(client, "/orders", { method: "POST", - params: query, + data: body, headers: { Authorization: `Bearer ${token}` }, }); return JSON.stringify(data, null, 2); From 890850a12c4c218005366f27390dc73331fd17e4 Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj Date: Tue, 19 Aug 2025 09:20:46 +0530 Subject: [PATCH 03/12] create new withdraw & deposit tools --- src/tools/cancel-withdrawal.ts | 28 +++++++++++++++++++ src/tools/create-deposit-address.ts | 31 +++++++++++++++++++++ src/tools/create-withdrawal.ts | 35 ++++++++++++++++++++++++ src/tools/get-deposit-address.ts | 32 ++++++++++++++++++++++ src/tools/get-deposit-chance.ts | 32 ++++++++++++++++++++++ src/tools/get-deposit.ts | 26 ++++++++++++++++++ src/tools/get-withdrawal.ts | 26 ++++++++++++++++++ src/tools/list-deposit-addresses.ts | 21 ++++++++++++++ src/tools/list-deposits.ts | 38 ++++++++++++++++++++++++++ src/tools/list-withdrawal-addresses.ts | 21 ++++++++++++++ src/tools/list-withdrawals.ts | 38 ++++++++++++++++++++++++++ 11 files changed, 328 insertions(+) create mode 100644 src/tools/cancel-withdrawal.ts create mode 100644 src/tools/create-deposit-address.ts create mode 100644 src/tools/create-withdrawal.ts create mode 100644 src/tools/get-deposit-address.ts create mode 100644 src/tools/get-deposit-chance.ts create mode 100644 src/tools/get-deposit.ts create mode 100644 src/tools/get-withdrawal.ts create mode 100644 src/tools/list-deposit-addresses.ts create mode 100644 src/tools/list-deposits.ts create mode 100644 src/tools/list-withdrawal-addresses.ts create mode 100644 src/tools/list-withdrawals.ts diff --git a/src/tools/cancel-withdrawal.ts b/src/tools/cancel-withdrawal.ts new file mode 100644 index 0000000..a950421 --- /dev/null +++ b/src/tools/cancel-withdrawal.ts @@ -0,0 +1,28 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +const paramsSchema = z.object({ uuid: z.string().min(1) }); + +type Params = z.infer; + +export const cancelWithdrawalTool = { + name: "CANCEL_WITHDRAWAL", + description: + "Cancel a digital asset withdrawal by UUID (requires private API)", + parameters: paramsSchema, + execute: async ({ uuid }: Params) => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const query = { uuid }; + const token = signJwtToken(query); + const data = await fetchJson(client, "/withdraw", { + method: "DELETE", + params: query, + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; diff --git a/src/tools/create-deposit-address.ts b/src/tools/create-deposit-address.ts new file mode 100644 index 0000000..879cdf5 --- /dev/null +++ b/src/tools/create-deposit-address.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +const paramsSchema = z + .object({ + currency: z.string(), + net_type: z.string(), + }) + .strict(); + +type Params = z.infer; + +export const createDepositAddressTool = { + name: "CREATE_DEPOSIT_ADDRESS", + description: "Request creation of a deposit address (requires private API)", + parameters: paramsSchema, + execute: async (params: Params) => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const token = signJwtToken(params); + const data = await fetchJson(client, "/deposits/coin_address", { + method: "POST", + data: params, + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; diff --git a/src/tools/create-withdrawal.ts b/src/tools/create-withdrawal.ts new file mode 100644 index 0000000..881d48d --- /dev/null +++ b/src/tools/create-withdrawal.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +const paramsSchema = z + .object({ + currency: z.string(), + amount: z.string(), + address: z.string(), + net_type: z.string(), + secondary_address: z.string().optional(), + transaction_type: z.enum(["default", "internal"]).optional(), + }) + .strict(); + +type Params = z.infer; + +export const createWithdrawalTool = { + name: "CREATE_WITHDRAWAL", + description: "Request a digital asset withdrawal (requires private API)", + parameters: paramsSchema, + execute: async (params: Params) => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const token = signJwtToken(params); + const data = await fetchJson(client, "/withdraws/coin", { + method: "POST", + data: params, + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; diff --git a/src/tools/get-deposit-address.ts b/src/tools/get-deposit-address.ts new file mode 100644 index 0000000..a08afcc --- /dev/null +++ b/src/tools/get-deposit-address.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +const paramsSchema = z + .object({ + currency: z.string(), + net_type: z.string(), + }) + .strict(); + +type Params = z.infer; + +export const getDepositAddressTool = { + name: "GET_DEPOSIT_ADDRESS", + description: + "Get a single deposit address for a currency and net_type (private)", + parameters: paramsSchema, + execute: async ({ currency, net_type }: Params) => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const query = { currency, net_type }; + const token = signJwtToken(query); + const data = await fetchJson(client, "/deposit", { + params: query, + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; diff --git a/src/tools/get-deposit-chance.ts b/src/tools/get-deposit-chance.ts new file mode 100644 index 0000000..e3033cb --- /dev/null +++ b/src/tools/get-deposit-chance.ts @@ -0,0 +1,32 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +const paramsSchema = z + .object({ + currency: z.string(), + net_type: z.string().optional(), + }) + .strict(); + +type Params = z.infer; + +export const getDepositChanceTool = { + name: "GET_DEPOSIT_CHANCE", + description: "Get deposit availability information for a currency (private)", + parameters: paramsSchema, + execute: async ({ currency, net_type }: Params) => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const query: Record = { currency }; + if (net_type) query.net_type = net_type; + const token = signJwtToken(query); + const data = await fetchJson(client, "/deposits/chance/coin", { + params: query, + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; diff --git a/src/tools/get-deposit.ts b/src/tools/get-deposit.ts new file mode 100644 index 0000000..43161c1 --- /dev/null +++ b/src/tools/get-deposit.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +const paramsSchema = z.object({ uuid: z.string().min(1) }); + +type Params = z.infer; + +export const getDepositTool = { + name: "GET_DEPOSIT", + description: "Get a single deposit by UUID (requires private API)", + parameters: paramsSchema, + execute: async ({ uuid }: Params) => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const query = { uuid }; + const token = signJwtToken(query); + const data = await fetchJson(client, "/deposit", { + params: query, + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; diff --git a/src/tools/get-withdrawal.ts b/src/tools/get-withdrawal.ts new file mode 100644 index 0000000..beed037 --- /dev/null +++ b/src/tools/get-withdrawal.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +const paramsSchema = z.object({ uuid: z.string().min(1) }); + +type Params = z.infer; + +export const getWithdrawalTool = { + name: "GET_WITHDRAWAL", + description: "Get a single withdrawal by UUID (requires private API)", + parameters: paramsSchema, + execute: async ({ uuid }: Params) => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const query = { uuid }; + const token = signJwtToken(query); + const data = await fetchJson(client, "/withdraw", { + params: query, + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; diff --git a/src/tools/list-deposit-addresses.ts b/src/tools/list-deposit-addresses.ts new file mode 100644 index 0000000..09687ab --- /dev/null +++ b/src/tools/list-deposit-addresses.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +export const listDepositAddressesTool = { + name: "LIST_DEPOSIT_ADDRESSES", + description: + "List deposit addresses for all currencies (requires private API)", + parameters: z.object({}), + execute: async () => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const token = signJwtToken(); + const data = await fetchJson(client, "/deposits/coin_addresses", { + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; diff --git a/src/tools/list-deposits.ts b/src/tools/list-deposits.ts new file mode 100644 index 0000000..df0d9f7 --- /dev/null +++ b/src/tools/list-deposits.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +const paramsSchema = z + .object({ + currency: z.string().optional(), + state: z.string().optional(), + page: z.number().int().min(1).default(1), + limit: z.number().int().min(1).max(100).default(50), + }) + .strict(); + +type Params = z.infer; + +export const listDepositsTool = { + name: "LIST_DEPOSITS", + description: "List deposits (requires private API)", + parameters: paramsSchema, + execute: async ({ currency, state, page, limit }: Params) => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const query: Record = { + page: String(page), + limit: String(limit), + }; + if (currency) query.currency = currency; + if (state) query.state = state; + const token = signJwtToken(query); + const data = await fetchJson(client, "/deposits", { + params: query, + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; diff --git a/src/tools/list-withdrawal-addresses.ts b/src/tools/list-withdrawal-addresses.ts new file mode 100644 index 0000000..b9b9547 --- /dev/null +++ b/src/tools/list-withdrawal-addresses.ts @@ -0,0 +1,21 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +export const listWithdrawalAddressesTool = { + name: "LIST_WITHDRAWAL_ADDRESSES", + description: + "List registered withdrawal-allowed addresses (requires private API)", + parameters: z.object({}), + execute: async () => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const token = signJwtToken(); + const data = await fetchJson(client, "/withdraws/coin_addresses", { + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; diff --git a/src/tools/list-withdrawals.ts b/src/tools/list-withdrawals.ts new file mode 100644 index 0000000..57df63b --- /dev/null +++ b/src/tools/list-withdrawals.ts @@ -0,0 +1,38 @@ +import { z } from "zod"; +import { config } from "../lib/config.js"; +import { createHttpClient, fetchJson } from "../lib/http.js"; +import { ensurePrivateEnabled, signJwtToken } from "../lib/upbit-auth.js"; + +const paramsSchema = z + .object({ + currency: z.string().optional(), + state: z.string().optional(), + page: z.number().int().min(1).default(1), + limit: z.number().int().min(1).max(100).default(50), + }) + .strict(); + +type Params = z.infer; + +export const listWithdrawalsTool = { + name: "LIST_WITHDRAWALS", + description: "List withdrawals (requires private API)", + parameters: paramsSchema, + execute: async ({ currency, state, page, limit }: Params) => { + ensurePrivateEnabled(); + const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; + const client = createHttpClient(baseURL); + const query: Record = { + page: String(page), + limit: String(limit), + }; + if (currency) query.currency = currency; + if (state) query.state = state; + const token = signJwtToken(query); + const data = await fetchJson(client, "/withdraws", { + params: query, + headers: { Authorization: `Bearer ${token}` }, + }); + return JSON.stringify(data, null, 2); + }, +} as const; From a51e6c19cdf44510c7be17673e53874fb4c4c805 Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj Date: Tue, 19 Aug 2025 09:20:58 +0530 Subject: [PATCH 04/12] update index --- src/index.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/index.ts b/src/index.ts index 638938e..8a6841d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,17 @@ import { getOrderbookTool } from "./tools/get-orderbook.js"; import { getOrdersTool } from "./tools/get-orders.js"; import { getTickerTool } from "./tools/get-ticker.js"; import { getTradesTool } from "./tools/get-trades.js"; +import { listWithdrawalAddressesTool } from "./tools/list-withdrawal-addresses.js"; +import { createWithdrawalTool } from "./tools/create-withdrawal.js"; +import { getWithdrawalTool } from "./tools/get-withdrawal.js"; +import { listWithdrawalsTool } from "./tools/list-withdrawals.js"; +import { cancelWithdrawalTool } from "./tools/cancel-withdrawal.js"; +import { getDepositChanceTool } from "./tools/get-deposit-chance.js"; +import { createDepositAddressTool } from "./tools/create-deposit-address.js"; +import { getDepositAddressTool } from "./tools/get-deposit-address.js"; +import { listDepositAddressesTool } from "./tools/list-deposit-addresses.js"; +import { getDepositTool } from "./tools/get-deposit.js"; +import { listDepositsTool } from "./tools/list-deposits.js"; async function main() { console.log("Initializing Upbit MCP Server..."); @@ -25,6 +36,17 @@ async function main() { server.addTool(getOrdersTool); server.addTool(getOrderTool); server.addTool(cancelOrderTool); + server.addTool(listWithdrawalAddressesTool); + server.addTool(createWithdrawalTool); + server.addTool(getWithdrawalTool); + server.addTool(listWithdrawalsTool); + server.addTool(cancelWithdrawalTool); + server.addTool(getDepositChanceTool); + server.addTool(createDepositAddressTool); + server.addTool(getDepositAddressTool); + server.addTool(listDepositAddressesTool); + server.addTool(getDepositTool); + server.addTool(listDepositsTool); try { await server.start({ From 4afe191570644c4d57f935b357c46439394a04f7 Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj Date: Tue, 19 Aug 2025 09:21:04 +0530 Subject: [PATCH 05/12] update readme --- README.md | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/README.md b/README.md index 73ad242..cc1f554 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,22 @@ UPBIT_ENABLE_TRADING=false Private tools require `UPBIT_ACCESS_KEY`, `UPBIT_SECRET_KEY`, and `UPBIT_ENABLE_TRADING=true`. +### Security & permissions + +- Keep your `UPBIT_SECRET_KEY` private and IP-allowlist your server in Upbit. +- Set `UPBIT_ENABLE_TRADING=true` only when you intend to place/cancel orders or create withdrawals/deposit addresses. +- Upbit permission mapping (high-level): + - Orders: create/cancel → 주문하기, query/list → 주문조회 + - Accounts/balances → 자산조회 + - Withdrawals: create/cancel → 출금하기, query/list/address list → 출금조회 + - Deposits: create deposit address → 입금하기, query/list/chance/address read → 입금조회 + - Auth follows Upbit JWT with `query_hash` (sorted URL-encoded params). POSTs send JSON bodies and are signed based on that body. + - See docs: 인증, 주문 생성, 출금/입금 레퍼런스 + - Auth: [docs.upbit.com/kr/reference/auth](https://docs.upbit.com/kr/reference/auth) + - Orders: [docs.upbit.com/kr/reference/new-order](https://docs.upbit.com/kr/reference/new-order) + - Withdrawals: [list-withdrawal-addresses](https://docs.upbit.com/kr/reference/list-withdrawal-addresses), [withdraw](https://docs.upbit.com/kr/reference/withdraw) + - Deposits: [available-deposit-information](https://docs.upbit.com/kr/reference/available-deposit-information), [create-deposit-address](https://docs.upbit.com/kr/reference/create-deposit-address) + ### Where to get Upbit API keys Before you begin, you need to get your Upbit API keys: @@ -79,9 +95,67 @@ Private (requires env and enable flag): - `GET_ORDER` — `{ uuid? , identifier? }` - `CREATE_ORDER` — `{ market, side, ord_type, volume?, price? }` - `CANCEL_ORDER` — `{ uuid }` +- `LIST_WITHDRAWAL_ADDRESSES` +- `CREATE_WITHDRAWAL` — `{ currency, amount, address, net_type, secondary_address? , transaction_type? }` +- `GET_WITHDRAWAL` — `{ uuid }` +- `LIST_WITHDRAWALS` — `{ currency?, state?, page?, limit? }` +- `CANCEL_WITHDRAWAL` — `{ uuid }` +- `GET_DEPOSIT_CHANCE` — `{ currency, net_type? }` +- `CREATE_DEPOSIT_ADDRESS` — `{ currency, net_type }` +- `GET_DEPOSIT_ADDRESS` — `{ currency, net_type }` +- `LIST_DEPOSIT_ADDRESSES` +- `GET_DEPOSIT` — `{ uuid }` +- `LIST_DEPOSITS` — `{ currency?, state?, page?, limit? }` All tool outputs are JSON strings for easy display. +### Examples + +```json +// CREATE_ORDER (market buy 10,000 KRW) +{ + "name": "CREATE_ORDER", + "arguments": { + "market": "KRW-BTC", + "side": "bid", + "ord_type": "price", + "price": "10000" + } +} +``` + +```json +// CREATE_ORDER (limit sell with post_only) +{ + "name": "CREATE_ORDER", + "arguments": { + "market": "KRW-BTC", + "side": "ask", + "ord_type": "limit", + "volume": "0.01", + "price": "100000000", + "time_in_force": "post_only", + "identifier": "my-unique-id-001" + } +} +``` + +```json +// LIST_WITHDRAWALS (page 1, 50 items) +{ + "name": "LIST_WITHDRAWALS", + "arguments": { "page": 1, "limit": 50 } +} +``` + +```json +// GET_DEPOSIT_CHANCE +{ + "name": "GET_DEPOSIT_CHANCE", + "arguments": { "currency": "BTC" } +} +``` + ## License MIT From 4b6a5e585f35a57935b2448285a766dc97225d17 Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj Date: Tue, 19 Aug 2025 09:21:33 +0530 Subject: [PATCH 06/12] create --- .changeset/modern-rabbits-visit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/modern-rabbits-visit.md diff --git a/.changeset/modern-rabbits-visit.md b/.changeset/modern-rabbits-visit.md new file mode 100644 index 0000000..3230965 --- /dev/null +++ b/.changeset/modern-rabbits-visit.md @@ -0,0 +1,5 @@ +--- +"@iqai/mcp-upbit": patch +--- + +add new withdraw & deposit tools From c2f82b75bdae488e088b84688d892ab302199e29 Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj Date: Tue, 19 Aug 2025 10:02:04 +0530 Subject: [PATCH 07/12] linting & upgrade to biome v2 --- biome.json | 20 ++++++++-- package.json | 8 ++-- pnpm-lock.yaml | 76 +++++++++++++++++++------------------- src/index.ts | 16 ++++---- src/tools/get-orderbook.ts | 2 +- src/tools/get-ticker.ts | 2 +- 6 files changed, 69 insertions(+), 55 deletions(-) diff --git a/biome.json b/biome.json index 37afd11..5da2cff 100644 --- a/biome.json +++ b/biome.json @@ -1,7 +1,15 @@ { - "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "$schema": "https://biomejs.dev/schemas/2.2.0/schema.json", "files": { - "ignore": ["node_modules/**", "dist/**"] + "includes": [ + "**/*.ts", + "**/*.tsx", + "**/*.js", + "**/*.jsx", + "**/*.json", + "!**/dist" + ], + "ignoreUnknown": false }, "linter": { "enabled": true, @@ -12,7 +20,11 @@ "formatter": { "enabled": true }, - "organizeImports": { - "enabled": true + "assist": { + "actions": { + "source": { + "organizeImports": "on" + } + } } } diff --git a/package.json b/package.json index e275faa..63639e5 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,9 @@ "bin": { "mcp-upbit-server": "dist/index.js" }, - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "build": "tsc && shx chmod +x dist/index.js", "prepare": "husky", @@ -39,7 +41,7 @@ "zod": "^3.25.7" }, "devDependencies": { - "@biomejs/biome": "*", + "@biomejs/biome": "^2.2.0", "@changesets/cli": "^2.29.4", "@types/node": "^22.15.19", "husky": "^9.0.0", @@ -49,7 +51,7 @@ }, "lint-staged": { "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}": [ - "biome check --write --organize-imports-enabled=false --no-errors-on-unmatched" + "biome check --write --no-errors-on-unmatched" ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bdd38f9..eda619e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,8 +34,8 @@ importers: version: 3.25.7 devDependencies: '@biomejs/biome': - specifier: '*' - version: 1.9.4 + specifier: ^2.2.0 + version: 2.2.0 '@changesets/cli': specifier: ^2.29.4 version: 2.29.4 @@ -61,55 +61,55 @@ packages: resolution: {integrity: sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==} engines: {node: '>=6.9.0'} - '@biomejs/biome@1.9.4': - resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} + '@biomejs/biome@2.2.0': + resolution: {integrity: sha512-3On3RSYLsX+n9KnoSgfoYlckYBoU6VRM22cw1gB4Y0OuUVSYd/O/2saOJMrA4HFfA1Ff0eacOvMN1yAAvHtzIw==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@1.9.4': - resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} + '@biomejs/cli-darwin-arm64@2.2.0': + resolution: {integrity: sha512-zKbwUUh+9uFmWfS8IFxmVD6XwqFcENjZvEyfOxHs1epjdH3wyyMQG80FGDsmauPwS2r5kXdEM0v/+dTIA9FXAg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@1.9.4': - resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} + '@biomejs/cli-darwin-x64@2.2.0': + resolution: {integrity: sha512-+OmT4dsX2eTfhD5crUOPw3RPhaR+SKVspvGVmSdZ9y9O/AgL8pla6T4hOn1q+VAFBHuHhsdxDRJgFCSC7RaMOw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@1.9.4': - resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} + '@biomejs/cli-linux-arm64-musl@2.2.0': + resolution: {integrity: sha512-egKpOa+4FL9YO+SMUMLUvf543cprjevNc3CAgDNFLcjknuNMcZ0GLJYa3EGTCR2xIkIUJDVneBV3O9OcIlCEZQ==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@1.9.4': - resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} + '@biomejs/cli-linux-arm64@2.2.0': + resolution: {integrity: sha512-6eoRdF2yW5FnW9Lpeivh7Mayhq0KDdaDMYOJnH9aT02KuSIX5V1HmWJCQQPwIQbhDh68Zrcpl8inRlTEan0SXw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@1.9.4': - resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} + '@biomejs/cli-linux-x64-musl@2.2.0': + resolution: {integrity: sha512-I5J85yWwUWpgJyC1CcytNSGusu2p9HjDnOPAFG4Y515hwRD0jpR9sT9/T1cKHtuCvEQ/sBvx+6zhz9l9wEJGAg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@1.9.4': - resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} + '@biomejs/cli-linux-x64@2.2.0': + resolution: {integrity: sha512-5UmQx/OZAfJfi25zAnAGHUMuOd+LOsliIt119x2soA2gLggQYrVPA+2kMUxR6Mw5M1deUF/AWWP2qpxgH7Nyfw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@1.9.4': - resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} + '@biomejs/cli-win32-arm64@2.2.0': + resolution: {integrity: sha512-n9a1/f2CwIDmNMNkFs+JI0ZjFnMO0jdOyGNtihgUNFnlmd84yIYY2KMTBmMV58ZlVHjgmY5Y6E1hVTnSRieggA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@1.9.4': - resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} + '@biomejs/cli-win32-x64@2.2.0': + resolution: {integrity: sha512-Nawu5nHjP/zPKTIryh2AavzTc/KEg4um/MxWdXW0A6P/RZOyIpa7+QSjeXwAwX/utJGaCoXRPWtF3m5U/bB3Ww==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] @@ -1330,39 +1330,39 @@ snapshots: '@babel/runtime@7.27.1': {} - '@biomejs/biome@1.9.4': + '@biomejs/biome@2.2.0': optionalDependencies: - '@biomejs/cli-darwin-arm64': 1.9.4 - '@biomejs/cli-darwin-x64': 1.9.4 - '@biomejs/cli-linux-arm64': 1.9.4 - '@biomejs/cli-linux-arm64-musl': 1.9.4 - '@biomejs/cli-linux-x64': 1.9.4 - '@biomejs/cli-linux-x64-musl': 1.9.4 - '@biomejs/cli-win32-arm64': 1.9.4 - '@biomejs/cli-win32-x64': 1.9.4 - - '@biomejs/cli-darwin-arm64@1.9.4': + '@biomejs/cli-darwin-arm64': 2.2.0 + '@biomejs/cli-darwin-x64': 2.2.0 + '@biomejs/cli-linux-arm64': 2.2.0 + '@biomejs/cli-linux-arm64-musl': 2.2.0 + '@biomejs/cli-linux-x64': 2.2.0 + '@biomejs/cli-linux-x64-musl': 2.2.0 + '@biomejs/cli-win32-arm64': 2.2.0 + '@biomejs/cli-win32-x64': 2.2.0 + + '@biomejs/cli-darwin-arm64@2.2.0': optional: true - '@biomejs/cli-darwin-x64@1.9.4': + '@biomejs/cli-darwin-x64@2.2.0': optional: true - '@biomejs/cli-linux-arm64-musl@1.9.4': + '@biomejs/cli-linux-arm64-musl@2.2.0': optional: true - '@biomejs/cli-linux-arm64@1.9.4': + '@biomejs/cli-linux-arm64@2.2.0': optional: true - '@biomejs/cli-linux-x64-musl@1.9.4': + '@biomejs/cli-linux-x64-musl@2.2.0': optional: true - '@biomejs/cli-linux-x64@1.9.4': + '@biomejs/cli-linux-x64@2.2.0': optional: true - '@biomejs/cli-win32-arm64@1.9.4': + '@biomejs/cli-win32-arm64@2.2.0': optional: true - '@biomejs/cli-win32-x64@1.9.4': + '@biomejs/cli-win32-x64@2.2.0': optional: true '@changesets/apply-release-plan@7.0.12': diff --git a/src/index.ts b/src/index.ts index 8a6841d..022c078 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,24 +1,24 @@ #!/usr/bin/env node import { FastMCP } from "fastmcp"; import { cancelOrderTool } from "./tools/cancel-order.js"; +import { cancelWithdrawalTool } from "./tools/cancel-withdrawal.js"; +import { createDepositAddressTool } from "./tools/create-deposit-address.js"; import { createOrderTool } from "./tools/create-order.js"; +import { createWithdrawalTool } from "./tools/create-withdrawal.js"; import { getAccountsTool } from "./tools/get-accounts.js"; +import { getDepositTool } from "./tools/get-deposit.js"; +import { getDepositAddressTool } from "./tools/get-deposit-address.js"; +import { getDepositChanceTool } from "./tools/get-deposit-chance.js"; import { getOrderTool } from "./tools/get-order.js"; import { getOrderbookTool } from "./tools/get-orderbook.js"; import { getOrdersTool } from "./tools/get-orders.js"; import { getTickerTool } from "./tools/get-ticker.js"; import { getTradesTool } from "./tools/get-trades.js"; -import { listWithdrawalAddressesTool } from "./tools/list-withdrawal-addresses.js"; -import { createWithdrawalTool } from "./tools/create-withdrawal.js"; import { getWithdrawalTool } from "./tools/get-withdrawal.js"; -import { listWithdrawalsTool } from "./tools/list-withdrawals.js"; -import { cancelWithdrawalTool } from "./tools/cancel-withdrawal.js"; -import { getDepositChanceTool } from "./tools/get-deposit-chance.js"; -import { createDepositAddressTool } from "./tools/create-deposit-address.js"; -import { getDepositAddressTool } from "./tools/get-deposit-address.js"; import { listDepositAddressesTool } from "./tools/list-deposit-addresses.js"; -import { getDepositTool } from "./tools/get-deposit.js"; import { listDepositsTool } from "./tools/list-deposits.js"; +import { listWithdrawalAddressesTool } from "./tools/list-withdrawal-addresses.js"; +import { listWithdrawalsTool } from "./tools/list-withdrawals.js"; async function main() { console.log("Initializing Upbit MCP Server..."); diff --git a/src/tools/get-orderbook.ts b/src/tools/get-orderbook.ts index 7ad7a55..1d4b566 100644 --- a/src/tools/get-orderbook.ts +++ b/src/tools/get-orderbook.ts @@ -18,7 +18,7 @@ export const getOrderbookTool = { const data = await fetchJson(client, "/orderbook", { params: { markets: market }, }); - // biome-ignore lint/suspicious/noExplicitAny: + /** biome-ignore lint/suspicious/noExplicitAny: */ const item = Array.isArray(data) ? (data as any[])[0] : (data as any); return JSON.stringify(item, null, 2); }, diff --git a/src/tools/get-ticker.ts b/src/tools/get-ticker.ts index 657aab4..ae42211 100644 --- a/src/tools/get-ticker.ts +++ b/src/tools/get-ticker.ts @@ -1,3 +1,4 @@ +/** biome-ignore-all lint/suspicious/noExplicitAny: */ import { z } from "zod"; import { config } from "../lib/config.js"; import { createHttpClient, fetchJson } from "../lib/http.js"; @@ -18,7 +19,6 @@ export const getTickerTool = { const data = await fetchJson(client, "/ticker", { params: { markets: market }, }); - // biome-ignore lint/suspicious/noExplicitAny: const item = Array.isArray(data) ? (data as any[])[0] : (data as any); return JSON.stringify(item, null, 2); }, From fe78f6296fee0c339106598c9f6ee59d2bde72ba Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj <86603322+GPSxtreme@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:16:42 +0530 Subject: [PATCH 08/12] Update src/tools/get-deposit-address.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/tools/get-deposit-address.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/get-deposit-address.ts b/src/tools/get-deposit-address.ts index a08afcc..a8f1bd9 100644 --- a/src/tools/get-deposit-address.ts +++ b/src/tools/get-deposit-address.ts @@ -23,7 +23,7 @@ export const getDepositAddressTool = { const client = createHttpClient(baseURL); const query = { currency, net_type }; const token = signJwtToken(query); - const data = await fetchJson(client, "/deposit", { + const data = await fetchJson(client, "/deposits/coin_address", { params: query, headers: { Authorization: `Bearer ${token}` }, }); From c33a7a844c21296a9f29fcdf75dab7a401e65c44 Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj <86603322+GPSxtreme@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:17:12 +0530 Subject: [PATCH 09/12] Update src/tools/list-deposits.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/tools/list-deposits.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/list-deposits.ts b/src/tools/list-deposits.ts index df0d9f7..1ed4f56 100644 --- a/src/tools/list-deposits.ts +++ b/src/tools/list-deposits.ts @@ -22,12 +22,12 @@ export const listDepositsTool = { ensurePrivateEnabled(); const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; const client = createHttpClient(baseURL); - const query: Record = { - page: String(page), - limit: String(limit), + const query = { + page, + limit, + currency, + state, }; - if (currency) query.currency = currency; - if (state) query.state = state; const token = signJwtToken(query); const data = await fetchJson(client, "/deposits", { params: query, From e19ddee6ce736a4af3994068c898d0e151a536fc Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj <86603322+GPSxtreme@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:17:18 +0530 Subject: [PATCH 10/12] Update src/tools/list-withdrawals.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/tools/list-withdrawals.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tools/list-withdrawals.ts b/src/tools/list-withdrawals.ts index 57df63b..96f098b 100644 --- a/src/tools/list-withdrawals.ts +++ b/src/tools/list-withdrawals.ts @@ -22,12 +22,12 @@ export const listWithdrawalsTool = { ensurePrivateEnabled(); const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; const client = createHttpClient(baseURL); - const query: Record = { - page: String(page), - limit: String(limit), + const query = { + page, + limit, + currency, + state, }; - if (currency) query.currency = currency; - if (state) query.state = state; const token = signJwtToken(query); const data = await fetchJson(client, "/withdraws", { params: query, From 08cd46ec1acfc02601b2e9d55566fd0b70727160 Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj <86603322+GPSxtreme@users.noreply.github.com> Date: Tue, 19 Aug 2025 10:17:28 +0530 Subject: [PATCH 11/12] Update src/tools/get-deposit-chance.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- src/tools/get-deposit-chance.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tools/get-deposit-chance.ts b/src/tools/get-deposit-chance.ts index e3033cb..007bb9c 100644 --- a/src/tools/get-deposit-chance.ts +++ b/src/tools/get-deposit-chance.ts @@ -20,8 +20,7 @@ export const getDepositChanceTool = { ensurePrivateEnabled(); const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; const client = createHttpClient(baseURL); - const query: Record = { currency }; - if (net_type) query.net_type = net_type; + const query = { currency, net_type }; const token = signJwtToken(query); const data = await fetchJson(client, "/deposits/chance/coin", { params: query, From 35362d7fe7cd6fd51061a32b711f5866be2db51e Mon Sep 17 00:00:00 2001 From: Prudhvi Suraaj Date: Tue, 19 Aug 2025 10:18:22 +0530 Subject: [PATCH 12/12] Refactor createOrderTool to simplify body construction in create-order.ts --- src/tools/create-order.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/tools/create-order.ts b/src/tools/create-order.ts index 5d80468..89bae9b 100644 --- a/src/tools/create-order.ts +++ b/src/tools/create-order.ts @@ -46,12 +46,16 @@ export const createOrderTool = { ensurePrivateEnabled(); const baseURL = `${config.upbit.baseUrl}${config.upbit.apiBasePath}`; const client = createHttpClient(baseURL); - const body: Record = { market, side, ord_type }; - if (volume) body.volume = volume; - if (price) body.price = price; - if (time_in_force) body.time_in_force = time_in_force; - if (smp_type) body.smp_type = smp_type; - if (identifier) body.identifier = identifier; + const body = { + market, + side, + ord_type, + volume, + price, + time_in_force, + smp_type, + identifier, + }; const token = signJwtToken(body); const data = await fetchJson(client, "/orders", { method: "POST",