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 CHANGED
@@ -88,7 +88,7 @@ Open Claude and ask:
88
88
 
89
89
  ## Tools
90
90
 
91
- ### Market data (16 tools)
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 (10 tools)
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 (16 tools)
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 (10 tools)
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
- defaultSubAccountId: string;
5
- subAccountIds: string[];
9
+ subAccounts: SubAccount[];
6
10
  }
7
11
  /**
8
- * Fetches and caches account info (userId, subAccountIds) on startup.
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, subAccountIds) on startup.
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 meResult = meData.result ?? meData.data;
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 userId from /users/v1/users/me');
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 ?? subData.data ?? [];
23
- const subAccounts = Array.isArray(subResult) ? subResult : subResult?.subAccounts ?? subResult?.sub_accounts ?? [];
24
- const subAccountIds = subAccounts.map((sa) => sa.subAccountId ?? sa.sub_account_id ?? sa.id ?? '').filter(Boolean);
25
- this.info = {
26
- userId: String(userId),
27
- defaultSubAccountId: subAccountIds[0] ?? '',
28
- subAccountIds,
29
- };
30
- console.error(`[finhay-mcp] Account loaded: userId=${this.info.userId}, subAccounts=[${subAccountIds.join(', ')}]`);
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
- return this.info?.defaultSubAccountId || undefined;
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?.subAccountIds ?? [];
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.info?.defaultSubAccountId;
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
  }
@@ -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); // 16 tools: stock, funds, gold, silver, crypto, macro, etc.
5
- registerPortfolioTools(server, client, account); // 10 tools: account, portfolio, orders, pnl, rights, etc.
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
  }
@@ -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([
@@ -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
- subAccountId: z.string().optional().describe('Sub-account ID (auto-detected if omitted)'),
20
- }, safeHandler(async ({ subAccountId }) => {
21
- const id = account.resolveSubAccountId(subAccountId);
22
- const data = await client.get(`/trading/sub-accounts/${id}/asset-summary`);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "finhay-mcp-server",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "mcpName": "io.github.finhay/mcp-server",
5
5
  "description": "Finhay MCP Server — xem gia co phieu, danh muc dau tu qua Claude AI",
6
6
  "type": "module",