finhay-mcp-server 1.0.8 → 1.0.9
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.en.md +6 -4
- package/README.md +6 -4
- package/dist/client/AccountContext.d.ts +8 -3
- package/dist/client/AccountContext.js +26 -18
- package/dist/tools/index.js +2 -2
- package/dist/tools/market.js +41 -0
- package/dist/tools/portfolio.js +8 -25
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -88,7 +88,7 @@ Open Claude and ask:
|
|
|
88
88
|
|
|
89
89
|
## Tools
|
|
90
90
|
|
|
91
|
-
### Market data (
|
|
91
|
+
### Market data (20 tools)
|
|
92
92
|
|
|
93
93
|
| Tool | Description |
|
|
94
94
|
|------|-------------|
|
|
@@ -103,17 +103,20 @@ Open Claude and ask:
|
|
|
103
103
|
| `get_gold_providers` | Gold prices by provider |
|
|
104
104
|
| `get_silver_prices` | Silver prices |
|
|
105
105
|
| `get_silver_chart` | Silver price chart |
|
|
106
|
+
| `get_metal_providers` | Gold + silver prices by provider |
|
|
106
107
|
| `get_all_financial_data` | All-in-one: gold, silver, crypto, rates, FX |
|
|
107
108
|
| `get_bank_interest_rates` | Bank savings interest rates |
|
|
108
109
|
| `get_crypto_top_trending` | Top trending cryptocurrencies |
|
|
110
|
+
| `get_company_financial_overview` | Company financial overview (PE, PB, ROE, EPS...) |
|
|
111
|
+
| `get_company_financial_analysis` | Financial analysis by year/quarter |
|
|
112
|
+
| `get_financial_statement` | Financial statements (income, balance sheet, cash flow) |
|
|
109
113
|
| `get_macro_data` | Macro indicators (CPI, PMI, IIP, FED rate...) |
|
|
110
114
|
| `get_market_session` | Trading session status |
|
|
111
115
|
|
|
112
|
-
### Account (
|
|
116
|
+
### Account (8 tools)
|
|
113
117
|
|
|
114
118
|
| Tool | Description |
|
|
115
119
|
|------|-------------|
|
|
116
|
-
| `get_owner_info` | Account owner info |
|
|
117
120
|
| `get_account_summary` | Balance: cash, securities, margin |
|
|
118
121
|
| `get_asset_summary` | Total assets |
|
|
119
122
|
| `get_portfolio` | Stock portfolio with P/L |
|
|
@@ -122,7 +125,6 @@ Open Claude and ask:
|
|
|
122
125
|
| `get_order_book` | Today's order book |
|
|
123
126
|
| `get_order_detail` | Single order detail |
|
|
124
127
|
| `get_user_rights` | Shareholder rights: dividends, rights issues... |
|
|
125
|
-
| `get_trade_info` | Buying power / sellable quantity |
|
|
126
128
|
|
|
127
129
|
|
|
128
130
|
## Requirements
|
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ Mở Claude, hỏi:
|
|
|
88
88
|
|
|
89
89
|
## Tools
|
|
90
90
|
|
|
91
|
-
### Thị trường (
|
|
91
|
+
### Thị trường (20 tools)
|
|
92
92
|
|
|
93
93
|
| Tool | Mô tả |
|
|
94
94
|
|------|-------|
|
|
@@ -103,17 +103,20 @@ Mở Claude, hỏi:
|
|
|
103
103
|
| `get_gold_providers` | Giá vàng theo nhà cung cấp |
|
|
104
104
|
| `get_silver_prices` | Giá bạc |
|
|
105
105
|
| `get_silver_chart` | Biểu đồ giá bạc |
|
|
106
|
+
| `get_metal_providers` | Giá vàng + bạc theo nhà cung cấp |
|
|
106
107
|
| `get_all_financial_data` | Tổng hợp: vàng, bạc, crypto, lãi suất, tỷ giá |
|
|
107
108
|
| `get_bank_interest_rates` | Lãi suất tiết kiệm ngân hàng |
|
|
108
109
|
| `get_crypto_top_trending` | Crypto xu hướng |
|
|
110
|
+
| `get_company_financial_overview` | Tổng quan tài chính doanh nghiệp (PE, PB, ROE, EPS...) |
|
|
111
|
+
| `get_company_financial_analysis` | Phân tích tài chính theo năm/quý |
|
|
112
|
+
| `get_financial_statement` | Báo cáo tài chính (BCTC, CDKT, LCTT) |
|
|
109
113
|
| `get_macro_data` | Chỉ số vĩ mô (CPI, PMI, IIP, FED rate...) |
|
|
110
114
|
| `get_market_session` | Trạng thái phiên giao dịch |
|
|
111
115
|
|
|
112
|
-
### Tài khoản (
|
|
116
|
+
### Tài khoản (8 tools)
|
|
113
117
|
|
|
114
118
|
| Tool | Mô tả |
|
|
115
119
|
|------|-------|
|
|
116
|
-
| `get_owner_info` | Thông tin chủ tài khoản |
|
|
117
120
|
| `get_account_summary` | Số dư: tiền mặt, chứng khoán, ký quỹ |
|
|
118
121
|
| `get_asset_summary` | Tổng tài sản |
|
|
119
122
|
| `get_portfolio` | Danh mục cổ phiếu với lãi/lỗ |
|
|
@@ -122,7 +125,6 @@ Mở Claude, hỏi:
|
|
|
122
125
|
| `get_order_book` | Sổ lệnh trong ngày |
|
|
123
126
|
| `get_order_detail` | Chi tiết 1 lệnh |
|
|
124
127
|
| `get_user_rights` | Quyền cổ đông: cổ tức, quyền mua... |
|
|
125
|
-
| `get_trade_info` | Sức mua / số lượng bán được |
|
|
126
128
|
|
|
127
129
|
|
|
128
130
|
## Yêu cầu
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { FinhayClient } from './FinhayClient.js';
|
|
2
|
+
export interface SubAccount {
|
|
3
|
+
id: string;
|
|
4
|
+
type: string;
|
|
5
|
+
subAccountExt: string;
|
|
6
|
+
}
|
|
2
7
|
export interface AccountInfo {
|
|
3
8
|
userId: string;
|
|
4
|
-
|
|
5
|
-
subAccountIds: string[];
|
|
9
|
+
subAccounts: SubAccount[];
|
|
6
10
|
}
|
|
7
11
|
/**
|
|
8
|
-
* Fetches and caches account info (userId,
|
|
12
|
+
* Fetches and caches account info (userId, subAccounts) on startup.
|
|
9
13
|
* Tools use this as default when user does not provide subAccountId.
|
|
10
14
|
*/
|
|
11
15
|
export declare class AccountContext {
|
|
@@ -14,6 +18,7 @@ export declare class AccountContext {
|
|
|
14
18
|
constructor(client: FinhayClient);
|
|
15
19
|
init(): Promise<void>;
|
|
16
20
|
getUserId(): string | undefined;
|
|
21
|
+
getSubAccountByType(type: string): SubAccount | undefined;
|
|
17
22
|
getDefaultSubAccountId(): string | undefined;
|
|
18
23
|
getSubAccountIds(): string[];
|
|
19
24
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fetches and caches account info (userId,
|
|
2
|
+
* Fetches and caches account info (userId, subAccounts) on startup.
|
|
3
3
|
* Tools use this as default when user does not provide subAccountId.
|
|
4
4
|
*/
|
|
5
5
|
export class AccountContext {
|
|
@@ -10,24 +10,27 @@ export class AccountContext {
|
|
|
10
10
|
}
|
|
11
11
|
async init() {
|
|
12
12
|
try {
|
|
13
|
-
// Step 1: get userId
|
|
13
|
+
// Step 1: get userId from .data.user_id
|
|
14
14
|
const meData = await this.client.get('/users/v1/users/me');
|
|
15
|
-
const
|
|
16
|
-
const userId = meResult?.user_id ?? meResult?.userId ?? meResult?.id ?? '';
|
|
15
|
+
const userId = meData.data?.user_id ?? '';
|
|
17
16
|
if (!userId) {
|
|
18
|
-
throw new Error('Could not extract
|
|
17
|
+
throw new Error('Could not extract user_id from /users/v1/users/me');
|
|
19
18
|
}
|
|
20
|
-
// Step 2: get sub-accounts
|
|
19
|
+
// Step 2: get sub-accounts with type and sub_account_ext
|
|
21
20
|
const subData = await this.client.get(`/users/v1/users/${userId}/sub-accounts`);
|
|
22
|
-
const subResult = subData.result
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
const subResult = Array.isArray(subData.result) ? subData.result
|
|
22
|
+
: Array.isArray(subData.data) ? subData.data
|
|
23
|
+
: [];
|
|
24
|
+
const subAccounts = subResult
|
|
25
|
+
.map((sa) => ({
|
|
26
|
+
id: sa.id ?? '',
|
|
27
|
+
type: (sa.type ?? '').toUpperCase(),
|
|
28
|
+
subAccountExt: sa.sub_account_ext ?? '',
|
|
29
|
+
}))
|
|
30
|
+
.filter((sa) => sa.id);
|
|
31
|
+
this.info = { userId: String(userId), subAccounts };
|
|
32
|
+
const summary = subAccounts.map((sa) => `${sa.type}=${sa.id}`).join(', ');
|
|
33
|
+
console.error(`[finhay-mcp] Account loaded: userId=${this.info.userId}, subAccounts=[${summary}]`);
|
|
31
34
|
}
|
|
32
35
|
catch (err) {
|
|
33
36
|
console.error(`[finhay-mcp] Warning: could not fetch account info: ${err.message}`);
|
|
@@ -37,18 +40,23 @@ export class AccountContext {
|
|
|
37
40
|
getUserId() {
|
|
38
41
|
return this.info?.userId || undefined;
|
|
39
42
|
}
|
|
43
|
+
getSubAccountByType(type) {
|
|
44
|
+
return this.info?.subAccounts.find((sa) => sa.type === type.toUpperCase());
|
|
45
|
+
}
|
|
40
46
|
getDefaultSubAccountId() {
|
|
41
|
-
|
|
47
|
+
// Prefer NORMAL, fallback to first available
|
|
48
|
+
const normal = this.getSubAccountByType('NORMAL');
|
|
49
|
+
return normal?.id || this.info?.subAccounts[0]?.id || undefined;
|
|
42
50
|
}
|
|
43
51
|
getSubAccountIds() {
|
|
44
|
-
return this.info?.
|
|
52
|
+
return this.info?.subAccounts.map((sa) => sa.id) ?? [];
|
|
45
53
|
}
|
|
46
54
|
/**
|
|
47
55
|
* Returns the given subAccountId if provided, otherwise falls back to default.
|
|
48
56
|
* Throws if neither is available.
|
|
49
57
|
*/
|
|
50
58
|
resolveSubAccountId(subAccountId) {
|
|
51
|
-
const resolved = subAccountId || this.
|
|
59
|
+
const resolved = subAccountId || this.getDefaultSubAccountId();
|
|
52
60
|
if (!resolved) {
|
|
53
61
|
throw new Error('subAccountId is required. Could not auto-detect — provide it explicitly or check your API credentials.');
|
|
54
62
|
}
|
package/dist/tools/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { registerMarketTools } from './market.js';
|
|
2
2
|
import { registerPortfolioTools } from './portfolio.js';
|
|
3
3
|
export function registerAllTools(server, client, account) {
|
|
4
|
-
registerMarketTools(server, client); //
|
|
5
|
-
registerPortfolioTools(server, client, account); //
|
|
4
|
+
registerMarketTools(server, client); // 20 tools: stock, funds, gold, silver, metals, crypto, macro, company financials, etc.
|
|
5
|
+
registerPortfolioTools(server, client, account); // 8 tools: account, portfolio, orders, pnl, rights, etc.
|
|
6
6
|
}
|
package/dist/tools/market.js
CHANGED
|
@@ -78,6 +78,11 @@ export function registerMarketTools(server, client) {
|
|
|
78
78
|
const data = await client.get('/market/financial-data/silver-chart', { days: String(days) });
|
|
79
79
|
return JSON.stringify(data.data, null, 2);
|
|
80
80
|
}));
|
|
81
|
+
// --- Metal providers ---
|
|
82
|
+
server.tool('get_metal_providers', 'Get gold and silver prices grouped by provider (superset of gold-providers and silver data)', {}, safeHandler(async () => {
|
|
83
|
+
const data = await client.get('/market/financial-data/metal-providers');
|
|
84
|
+
return JSON.stringify(data.data, null, 2);
|
|
85
|
+
}));
|
|
81
86
|
// --- Financial data ---
|
|
82
87
|
server.tool('get_all_financial_data', 'Get all financial data: gold, silver, crypto, bank rates, USD exchange rate', {}, safeHandler(async () => {
|
|
83
88
|
const data = await client.get('/market/financial-data');
|
|
@@ -91,6 +96,42 @@ export function registerMarketTools(server, client) {
|
|
|
91
96
|
const data = await client.get('/market/financial-data/cryptos/top-trending');
|
|
92
97
|
return JSON.stringify(data.data, null, 2);
|
|
93
98
|
}));
|
|
99
|
+
// --- Company financials ---
|
|
100
|
+
server.tool('get_company_financial_overview', 'Get financial overview for a company: PE, PB, EV/EBITDA, gross margin, ROE, EPS, dividend yield, NIM, ROA, and industry averages', {
|
|
101
|
+
symbol: z.string().describe('Stock symbol (e.g., VNM)'),
|
|
102
|
+
}, safeHandler(async ({ symbol }) => {
|
|
103
|
+
const data = await client.get('/market/company-financial/overview', {
|
|
104
|
+
symbol: symbol.toUpperCase(),
|
|
105
|
+
});
|
|
106
|
+
return JSON.stringify(data.data, null, 2);
|
|
107
|
+
}));
|
|
108
|
+
server.tool('get_company_financial_analysis', 'Get financial analysis entries by year or quarter for a company', {
|
|
109
|
+
symbol: z.string().describe('Stock symbol (e.g., VNM)'),
|
|
110
|
+
period: z.enum(['annual', 'quarterly']).optional().describe('Period type (default: annual)'),
|
|
111
|
+
}, safeHandler(async ({ symbol, period }) => {
|
|
112
|
+
const query = { symbol: symbol.toUpperCase() };
|
|
113
|
+
if (period)
|
|
114
|
+
query.period = period;
|
|
115
|
+
const data = await client.get('/market/company-financial/analysis', query);
|
|
116
|
+
return JSON.stringify(data.data, null, 2);
|
|
117
|
+
}));
|
|
118
|
+
server.tool('get_financial_statement', 'Get financial statements (income statement, balance sheet, or cash flow) for a company', {
|
|
119
|
+
symbol: z.string().describe('Stock symbol (e.g., VNM)'),
|
|
120
|
+
type: z.enum(['income-statement', 'balance-sheet', 'cash-flow']).describe('Statement type'),
|
|
121
|
+
period: z.enum(['annual', 'quarterly']).optional().describe('Period type'),
|
|
122
|
+
limit: z.number().min(1).max(5).optional().describe('Number of periods to return (1-5, default: 5)'),
|
|
123
|
+
}, safeHandler(async ({ symbol, type, period, limit }) => {
|
|
124
|
+
const query = {
|
|
125
|
+
symbol: symbol.toUpperCase(),
|
|
126
|
+
type,
|
|
127
|
+
};
|
|
128
|
+
if (period)
|
|
129
|
+
query.period = period;
|
|
130
|
+
if (limit)
|
|
131
|
+
query.limit = String(limit);
|
|
132
|
+
const data = await client.get('/market/v2/financial-statement/statement', query);
|
|
133
|
+
return JSON.stringify(data.data, null, 2);
|
|
134
|
+
}));
|
|
94
135
|
// --- Macro ---
|
|
95
136
|
server.tool('get_macro_data', 'Get macroeconomic indicators for Vietnam or US (CPI, PMI, IIP, FED rate, etc.)', {
|
|
96
137
|
type: z.enum([
|
package/dist/tools/portfolio.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { safeHandler } from '../utils/safeTool.js';
|
|
3
3
|
export function registerPortfolioTools(server, client, account) {
|
|
4
|
-
// --- Owner ---
|
|
5
|
-
server.tool('get_owner_info', 'Get owner identity info (name, accounts, sub-account IDs, etc.)', {}, safeHandler(async () => {
|
|
6
|
-
const data = await client.get('/users/v1/users/me');
|
|
7
|
-
return JSON.stringify(data.result, null, 2);
|
|
8
|
-
}));
|
|
9
4
|
// --- Account summary ---
|
|
10
5
|
server.tool('get_account_summary', 'Get account balance summary: cash, securities value, margin, net asset value', {
|
|
11
6
|
subAccountId: z.string().optional().describe('Sub-account ID (auto-detected if omitted)'),
|
|
@@ -16,10 +11,10 @@ export function registerPortfolioTools(server, client, account) {
|
|
|
16
11
|
}));
|
|
17
12
|
// --- Asset summary ---
|
|
18
13
|
server.tool('get_asset_summary', 'Get asset summary with total valuation', {
|
|
19
|
-
|
|
20
|
-
}, safeHandler(async ({
|
|
21
|
-
const
|
|
22
|
-
const data = await client.get(`/
|
|
14
|
+
userId: z.string().optional().describe('User ID (auto-detected if omitted)'),
|
|
15
|
+
}, safeHandler(async ({ userId }) => {
|
|
16
|
+
const uid = account.resolveUserId(userId);
|
|
17
|
+
const data = await client.get(`/users/v4/users/${uid}/assets/summary`);
|
|
23
18
|
return JSON.stringify(data.data, null, 2);
|
|
24
19
|
}));
|
|
25
20
|
// --- Portfolio ---
|
|
@@ -85,9 +80,10 @@ export function registerPortfolioTools(server, client, account) {
|
|
|
85
80
|
fromDate: z.string().optional().describe('Start date (YYYY-MM-DD)'),
|
|
86
81
|
toDate: z.string().optional().describe('End date (YYYY-MM-DD)'),
|
|
87
82
|
catType: z.string().optional().describe('Category type, comma-separated or ALL (default: ALL)'),
|
|
83
|
+
isCom: z.string().optional().describe('Commission filter (default: ALL)'),
|
|
88
84
|
symbol: z.string().optional().describe('Filter by symbol (default: ALL)'),
|
|
89
85
|
status: z.string().optional().describe('Status filter, comma-separated or ALL (default: ALL)'),
|
|
90
|
-
}, safeHandler(async ({ subAccountId, fromDate, toDate, catType, symbol, status }) => {
|
|
86
|
+
}, safeHandler(async ({ subAccountId, fromDate, toDate, catType, isCom, symbol, status }) => {
|
|
91
87
|
const id = account.resolveSubAccountId(subAccountId);
|
|
92
88
|
const query = {};
|
|
93
89
|
if (fromDate)
|
|
@@ -96,6 +92,8 @@ export function registerPortfolioTools(server, client, account) {
|
|
|
96
92
|
query.toDate = toDate;
|
|
97
93
|
if (catType)
|
|
98
94
|
query.catType = catType;
|
|
95
|
+
if (isCom)
|
|
96
|
+
query.isCom = isCom;
|
|
99
97
|
if (symbol)
|
|
100
98
|
query.symbol = symbol.toUpperCase();
|
|
101
99
|
if (status)
|
|
@@ -103,19 +101,4 @@ export function registerPortfolioTools(server, client, account) {
|
|
|
103
101
|
const data = await client.get(`/trading/v5/account/${id}/user-rights`, query);
|
|
104
102
|
return JSON.stringify(data.result, null, 2);
|
|
105
103
|
}));
|
|
106
|
-
// --- Trade info (pre-order check) ---
|
|
107
|
-
server.tool('get_trade_info', 'Get buying power (BUY) or available quantity (SELL) before placing an order', {
|
|
108
|
-
subAccountId: z.string().optional().describe('Sub-account ID (auto-detected if omitted)'),
|
|
109
|
-
symbol: z.string().describe('Stock symbol'),
|
|
110
|
-
side: z.enum(['BUY', 'SELL']).describe('Order side'),
|
|
111
|
-
quotePrice: z.number().describe('Quote price in VND'),
|
|
112
|
-
}, safeHandler(async ({ subAccountId, symbol, side, quotePrice }) => {
|
|
113
|
-
const id = account.resolveSubAccountId(subAccountId);
|
|
114
|
-
const data = await client.get(`/trading/sub-accounts/${id}/trade-info`, {
|
|
115
|
-
symbol: symbol.toUpperCase(),
|
|
116
|
-
side,
|
|
117
|
-
quote_price: String(quotePrice),
|
|
118
|
-
});
|
|
119
|
-
return JSON.stringify(data.result, null, 2);
|
|
120
|
-
}));
|
|
121
104
|
}
|