twse-mcp
Version:
MCP server for Taiwan Stock Exchange (TWSE) market data API
529 lines • 19.6 kB
JavaScript
#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
const twse_client_js_1 = require("./api/twse-client.js");
const server = new index_js_1.Server({
name: 'twse-mcp',
version: '0.1.0',
}, {
capabilities: {
tools: {},
},
});
const twseClient = new twse_client_js_1.TWSEClient();
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'getStockDaily',
description: 'Get daily trading information for all listed stocks on TWSE',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getStockPERatios',
description: 'Get P/E ratios, dividend yields, and price-to-book ratios for all stocks',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getMarketIndex',
description: 'Get daily market index statistics including TAIEX and other indices',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getTopForeignHoldings',
description: 'Get top 20 foreign and mainland investor stock holdings',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getMonthlyStats',
description: 'Get monthly stock trading statistics including high/low prices and volumes',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'searchStock',
description: 'Search for specific stock information by code or name',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'Stock code (e.g., 2330) or partial name to search',
},
dataType: {
type: 'string',
enum: ['daily', 'peratio', 'monthly'],
description: 'Type of data to retrieve',
default: 'daily',
},
},
required: ['query'],
},
},
{
name: 'getStockDayAvg',
description: 'Get daily closing prices and monthly average prices for all stocks',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getMarginTrading',
description: 'Get margin trading and short selling statistics',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getTop20Volume',
description: 'Get top 20 stocks by daily trading volume',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getFiveSecondStats',
description: 'Get real-time 5-second bid/ask and transaction statistics',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getHolidaySchedule',
description: 'Get TWSE market holiday schedule and trading days',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getNewListings',
description: 'Get recently listed companies on TWSE',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getSuspendedListings',
description: 'Get companies that have been delisted or suspended',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getETFRanking',
description: 'Get top ETFs by regular investment accounts',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getForeignCategoryHoldings',
description: 'Get foreign investor holdings by industry category',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getIndexHistory',
description: 'Get historical TAIEX index data (open, high, low, close)',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getMonthlyRevenue',
description: 'Get monthly revenue reports for all listed companies',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getIncomeStatement',
description: 'Get quarterly income statements for all listed companies (general industry)',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getBalanceSheet',
description: 'Get quarterly balance sheets for all listed companies (general industry)',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getIndustryEPS',
description: 'Get earnings per share (EPS) statistics by industry',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'getProfitAnalysis',
description: 'Get comprehensive profitability analysis including ROE, ROA, profit margins',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'searchFinancials',
description: 'Search financial reports for specific company',
inputSchema: {
type: 'object',
properties: {
stockCode: {
type: 'string',
description: 'Stock code to search (e.g., 2330)',
},
reportType: {
type: 'string',
enum: ['revenue', 'income', 'balance', 'profit'],
description: 'Type of financial report',
default: 'revenue',
},
},
required: ['stockCode'],
},
},
],
};
});
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'getStockDaily': {
const data = await twseClient.getStockDayAll();
return {
content: [
{
type: 'text',
text: JSON.stringify(data.slice(0, 100), null, 2),
},
],
};
}
case 'getStockPERatios': {
const data = await twseClient.getStockPERatios();
return {
content: [
{
type: 'text',
text: JSON.stringify(data.slice(0, 100), null, 2),
},
],
};
}
case 'getMarketIndex': {
const data = await twseClient.getMarketIndex();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getTopForeignHoldings': {
const data = await twseClient.getTopForeignHoldings();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getMonthlyStats': {
const data = await twseClient.getMonthlyStats();
return {
content: [
{
type: 'text',
text: JSON.stringify(data.slice(0, 100), null, 2),
},
],
};
}
case 'searchStock': {
const query = args?.query?.toUpperCase() || '';
const dataType = args?.dataType || 'daily';
let data = [];
switch (dataType) {
case 'daily':
const dailyData = await twseClient.getStockDayAll();
data = dailyData.filter(stock => stock.Code.includes(query) ||
stock.Name.includes(query));
break;
case 'peratio':
const peData = await twseClient.getStockPERatios();
data = peData.filter(stock => stock.Code.includes(query) ||
stock.Name.includes(query));
break;
case 'monthly':
const monthlyData = await twseClient.getMonthlyStats();
data = monthlyData.filter(stock => stock.Code.includes(query) ||
stock.Name.includes(query));
break;
}
return {
content: [
{
type: 'text',
text: data.length > 0
? JSON.stringify(data, null, 2)
: `No stocks found matching "${query}"`,
},
],
};
}
case 'getStockDayAvg': {
const data = await twseClient.getStockDayAvg();
return {
content: [
{
type: 'text',
text: JSON.stringify(data.slice(0, 100), null, 2),
},
],
};
}
case 'getMarginTrading': {
const data = await twseClient.getMarginTrading();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getTop20Volume': {
const data = await twseClient.getTop20Volume();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getFiveSecondStats': {
const data = await twseClient.getFiveSecondStats();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getHolidaySchedule': {
const data = await twseClient.getHolidaySchedule();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getNewListings': {
const data = await twseClient.getNewListings();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getSuspendedListings': {
const data = await twseClient.getSuspendedListings();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getETFRanking': {
const data = await twseClient.getETFRanking();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getForeignCategoryHoldings': {
const data = await twseClient.getForeignCategoryHoldings();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getIndexHistory': {
const data = await twseClient.getIndexHistory();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getMonthlyRevenue': {
const data = await twseClient.getMonthlyRevenue();
return {
content: [
{
type: 'text',
text: JSON.stringify(data.slice(0, 100), null, 2),
},
],
};
}
case 'getIncomeStatement': {
const data = await twseClient.getIncomeStatement();
return {
content: [
{
type: 'text',
text: JSON.stringify(data.slice(0, 100), null, 2),
},
],
};
}
case 'getBalanceSheet': {
const data = await twseClient.getBalanceSheet();
return {
content: [
{
type: 'text',
text: JSON.stringify(data.slice(0, 100), null, 2),
},
],
};
}
case 'getIndustryEPS': {
const data = await twseClient.getIndustryEPS();
return {
content: [
{
type: 'text',
text: JSON.stringify(data, null, 2),
},
],
};
}
case 'getProfitAnalysis': {
const data = await twseClient.getProfitAnalysis();
return {
content: [
{
type: 'text',
text: JSON.stringify(data.slice(0, 100), null, 2),
},
],
};
}
case 'searchFinancials': {
const stockCode = args?.stockCode?.toUpperCase() || '';
const reportType = args?.reportType || 'revenue';
let data = [];
switch (reportType) {
case 'revenue':
const revenue = await twseClient.getMonthlyRevenue();
data = revenue.filter(item => item.公司代號 === stockCode);
break;
case 'income':
const income = await twseClient.getIncomeStatement();
data = income.filter(item => item.公司代號 === stockCode);
break;
case 'balance':
const balance = await twseClient.getBalanceSheet();
data = balance.filter(item => item.公司代號 === stockCode);
break;
case 'profit':
const profit = await twseClient.getProfitAnalysis();
data = profit.filter(item => item.公司代號 === stockCode);
break;
}
return {
content: [
{
type: 'text',
text: data.length > 0
? JSON.stringify(data, null, 2)
: `No financial data found for stock ${stockCode}`,
},
],
};
}
default:
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
}
catch (error) {
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, `Error executing tool ${name}: ${error}`);
}
});
async function main() {
const transport = new stdio_js_1.StdioServerTransport();
await server.connect(transport);
process.stderr.write('TWSE MCP Server running on stdio\\n');
}
main().catch((error) => {
console.error('Server error:', error);
process.exit(1);
});
//# sourceMappingURL=index.js.map