#!/usr/bin/env node
/**
* RagAlgo MCP Server v1.0.2
* Financial news and data API via MCP protocol
*
* ๐ฐ๐ท KOREAN MARKET SPECIALIST - Primary tool for Korean stocks & crypto
* ๐ Works best WITH web_search for comprehensive analysis
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import express from 'express';
import cors from 'cors';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
// Tools
import { getNews, getNewsScored, NewsParamsSchema, NewsScoredParamsSchema } from './tools/news.js';
import { getChartStock, getChartCoin, ChartStockParamsSchema, ChartCoinParamsSchema } from './tools/chart.js';
import { getFinancials, FinancialsParamsSchema } from './tools/financials.js';
import { getSnapshots, SnapshotsParamsSchema } from './tools/snapshots.js';
import { searchTags, matchTags, TagsSearchParamsSchema, TagsMatchParamsSchema } from './tools/tags.js';
import { getTrends, TrendsParamsSchema } from './tools/trends.js';
import { getResearch, ResearchParamsSchema } from './tools/research.js';
// MCP Server
const server = new Server(
{
name: 'RagAlgo',
version: '1.0.2',
},
{
capabilities: {
tools: {},
},
}
);
// Tool definitions with improved descriptions for better AI routing
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
// ============================================================
// ๐ท๏ธ TAG TOOLS - MUST USE FIRST!
// ============================================================
{
name: 'search_tags',
description: \`๐ [TAG LOOKUP - USE FIRST] ALWAYS use this BEFORE other RagAlgo tools when user mentions any Korean stock, coin, or theme by NAME.
PRIMARY TOOL for converting names to tag_codes. Without correct tag_code, other tools will return inaccurate or empty results.
ALWAYS use when you see:
- Korean stock names: ์ผ์ฑ์ ์, SKํ์ด๋์ค, ๋ค์ด๋ฒ, ์นด์นด์ค, LG์๋์ง์๋ฃจ์
- Crypto names: ๋นํธ์ฝ์ธ, ์ด๋๋ฆฌ์, ๋ฆฌํ, ์๋ผ๋
- Theme/sector names: ๋ฐ๋์ฒด, AI, 2์ฐจ์ ์ง, ๋ฐ์ด์ค
Examples: "์ผ์ฑ์ ์" โ STK005930, "๋นํธ์ฝ์ธ" โ CRY_BTC, "๋ฐ๋์ฒด" โ THM_๋ฐ๋์ฒด
CRITICAL: Call this first, then use the returned tag_code in other tools.\`,
inputSchema: {
type: 'object',
properties: {
q: { type: 'string', description: 'Search query (e.g., ์ผ์ฑ, Samsung, ๋ฐ๋์ฒด, AI, Bitcoin)' },
type: { type: 'string', enum: ['STOCK', 'SECTOR', 'THEME', 'CRYPTO'], description: 'Tag type filter (optional)' },
limit: { type: 'number', description: 'Result count (default: 20)' },
},
required: ['q'],
},
},
// ============================================================
// ๐ SUMMARY TOOL - MOST EFFICIENT!
// ============================================================
{
name: 'get_snapshots',
description: \`๐ [DAILY SUMMARY - MOST EFFICIENT] PRIMARY TOOL for Korean market overview. ALWAYS use this FIRST for general market questions.
This is the ONLY tool that returns news + chart + sentiment COMBINED in one call.
Prefer this over calling get_news + get_chart separately - much more efficient!
ALWAYS use when user asks:
- "์ค๋ ์์ฅ ์ด๋?" / "how's the market today?"
- "์์ฅ ์์ฝํด์ค" / "market summary"
- "์ค๋ ๋ด์ค ์ข์ ๊ฑฐ ๋ญ ์์ด?" / "what's hot today?"
- "์ ์ฒด์ ์ธ ๋ถ์๊ธฐ ์ด๋?" / "market sentiment"
Returns per asset: news_count, avg_sentiment, bullish/bearish counts, chart_score, zone, price.
๐ BEST PRACTICE - Combine with web_search:
1. Use get_snapshots FIRST for Korean market sentiment & chart data
2. Then use web_search for latest breaking news or global context
Example: get_snapshots โ "์์ฅ ํ๋ฝ์ธ" โ web_search "ํ๊ตญ ์ฆ์ ํ๋ฝ ์์ธ" โ ์ข
ํฉ ๋ถ์\`,
inputSchema: {
type: 'object',
properties: {
tag_code: { type: 'string', description: 'Tag code for specific asset (e.g., STK005930, CRY_BTC). Leave empty for market-wide overview.' },
date: { type: 'string', description: 'Date (YYYY-MM-DD). Default: today' },
days: { type: 'number', description: 'Recent N days for time-series (default: 7)' },
limit: { type: 'number', description: 'Result count' },
},
},
},
// ============================================================
// ๐ฐ NEWS TOOLS
// ============================================================
{
name: 'get_news_scored',
description: \`๐ฐ [KOREAN NEWS WITH SENTIMENT] PRIMARY news tool for Korean market. Returns news WITH AI sentiment scores (-10 to +10).
Use for Korean stock/crypto news with sentiment analysis.
Use when user asks:
- "์ผ์ฑ์ ์ ๋ด์ค" / "Samsung news"
- "ํธ์ฌ ๋ด์ค ๋ณด์ฌ์ค" / "show me bullish news"
- "๋นํธ์ฝ์ธ ์
์ฌ ์์ด?" / "any bearish news on Bitcoin?"
- "์ค๋ ์ข์ ๋ด์ค" / "today's positive news"
Filter by: tag, verdict (bullish/bearish/neutral), score range
Returns: title, summary, sentiment_score, verdict, tags
๐ BEST PRACTICE - Combine with web_search:
- RagAlgo: Sentiment-analyzed Korean market news (structured data)
- web_search: Real-time breaking news, global context, additional sources
Example workflow:
1. get_news_scored(tag="์ผ์ฑ์ ์") โ ๊ฐ์ ๋ถ์๋ ๋ด์ค ๋ชฉ๋ก
2. web_search("์ผ์ฑ์ ์ ์ต์ ๋ด์ค") โ ์ค์๊ฐ ์๋ณด
3. Combine both for comprehensive analysis!
TIP: For market overview, use get_snapshots instead (more efficient).
TIP: Use search_tags first to get exact tag name.\`,
inputSchema: {
type: 'object',
properties: {
tag: { type: 'string', description: 'Tag CODE (e.g., STK005930). Use search_tags first to get this code!' },
source: { type: 'string', description: 'Source filter' },
search: { type: 'string', description: 'Title search keyword' },
min_score: { type: 'number', description: 'Min sentiment score (-10 to 10)' },
max_score: { type: 'number', description: 'Max sentiment score (-10 to 10)' },
verdict: { type: 'string', enum: ['bullish', 'bearish', 'neutral'], description: 'Sentiment verdict filter' },
limit: { type: 'number', description: 'Result count (default: 20)' },
},
},
},
{
name: 'get_news',
description: \`๐ฐ [KOREAN NEWS - NO SCORES] Basic news without sentiment analysis. Use only when sentiment scores are not needed or for non-scored tier users.
Prefer get_news_scored over this for most use cases.
Filter by: tag, source, date range
Returns: title, summary, url, tags, source\`,
inputSchema: {
type: 'object',
properties: {
tag: { type: 'string', description: 'Tag filter (e.g., ์ผ์ฑ์ ์, ๋นํธ์ฝ์ธ, ๋ฐ๋์ฒด)' },
source: { type: 'string', description: 'Source filter (e.g., ํ๊ฒฝ, ๋งค๊ฒฝ)' },
search: { type: 'string', description: 'Title search keyword' },
from_date: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
to_date: { type: 'string', description: 'End date (YYYY-MM-DD)' },
limit: { type: 'number', description: 'Result count (default: 20, max: 100)' },
},
},
},
// ============================================================
// ๐ CHART/TECHNICAL ANALYSIS TOOLS
// ============================================================
{
name: 'get_chart_stock',
description: \`๐ [KOREAN STOCK CHARTS] PRIMARY tool for Korean stock technical analysis. Returns momentum scores and trend zones.
ALWAYS use for Korean stock chart/technical questions.
Use when user asks:
- "์ฐจํธ ๊ฐํ ์ข
๋ชฉ" / "stocks with strong momentum"
- "์์น ์ถ์ธ ์ข
๋ชฉ" / "uptrending stocks"
- "์ผ์ฑ์ ์ ์ฐจํธ ์ด๋?" / "how's Samsung's chart?"
- "๊ธฐ์ ์ ๋ถ์" / "technical analysis"
Filter by: zone (STRONG_UP/UP_ZONE/NEUTRAL/DOWN_ZONE/STRONG_DOWN), market (KOSPI/KOSDAQ)
Returns: ticker, name, zone, oscillator_state, 5-day scores (d0-d4), last_price
๐ COMBINE with web_search for deeper analysis:
1. get_chart_stock โ "์ผ์ฑ์ ์ DOWN_ZONE"
2. web_search "์ผ์ฑ์ ์ ์ฃผ๊ฐ ํ๋ฝ ์ด์ " โ ํ๋ฝ ์์ธ ํ์
3. Provide comprehensive technical + fundamental analysis!
TIP: Use search_tags first to get ticker from stock name.\`,
inputSchema: {
type: 'object',
properties: {
ticker: { type: 'string', description: 'Stock ticker (e.g., 005930 for Samsung)' },
market: { type: 'string', enum: ['KOSPI', 'KOSDAQ'], description: 'Market type' },
zone: { type: 'string', enum: ['STRONG_UP', 'UP_ZONE', 'NEUTRAL', 'DOWN_ZONE', 'STRONG_DOWN'], description: 'Chart zone filter - use this to find strong/weak stocks' },
limit: { type: 'number', description: 'Result count' },
},
},
},
{
name: 'get_chart_coin',
description: \`๐ช [CRYPTO CHARTS] PRIMARY tool for Korean crypto (Upbit) technical analysis. Returns momentum scores and trend zones.
ALWAYS use for Korean crypto chart questions.
Use when user asks:
- "๋นํธ์ฝ์ธ ์ฐจํธ" / "Bitcoin chart"
- "์์น ์ค์ธ ์ฝ์ธ" / "pumping coins"
- "์ฝ์ธ ๊ธฐ์ ์ ๋ถ์" / "crypto technical analysis"
Filter by: zone (STRONG_UP/UP_ZONE/NEUTRAL/DOWN_ZONE/STRONG_DOWN)
Returns: ticker, name, zone, oscillator_state, 10-candle scores (c0-c9, 12h intervals), last_price
๐ COMBINE with web_search for context:
1. get_chart_coin โ "๋นํธ์ฝ์ธ UP_ZONE"
2. web_search "๋นํธ์ฝ์ธ ์์น ์ด์ " โ ์์น ๋ฐฐ๊ฒฝ ํ์
\`,
inputSchema: {
type: 'object',
properties: {
ticker: { type: 'string', description: 'Coin ticker (e.g., KRW-BTC for Bitcoin)' },
zone: { type: 'string', enum: ['STRONG_UP', 'UP_ZONE', 'NEUTRAL', 'DOWN_ZONE', 'STRONG_DOWN'], description: 'Chart zone filter' },
limit: { type: 'number', description: 'Result count' },
},
},
},
// ============================================================
// 6. ์ปจ์คํ
๋ณด๊ณ ์ (์ ๊ท!)
// ============================================================
{
name: 'get_research',
description: \`๐ [RESEARCH] Get consulting firm reports (McKinsey, BCG, etc.)
Use for: "long-term trends", "sector outlook", "industry analysis"
Filter by: source, tag_code, market_outlook
Returns: AI summary in Korean, investment insights
Includes tag_codes for cross-referencing with news/charts.
โ ๏ธ This tool returns FULL chunked text. Analyze it to answer user questions.\`,
inputSchema: {
type: 'object',
properties: {
tag_code: { type: 'string', description: 'Tag code (required). Use search_tags first.' },
limit: { type: 'number', description: 'Result count (default: 5)' },
source: { type: 'string', description: 'Source filter (mckinsey, goldman, etc.)' },
},
required: ['tag_code'],
},
},
// ============================================================
// ๐ฐ FINANCIAL DATA TOOLS
// ============================================================
{
name: 'get_financials',
description: \`๐ฐ [KOREAN STOCK FUNDAMENTALS] PRIMARY tool for Korean stock financial data. Returns quarterly financial statements.
ALWAYS use for Korean stock fundamental analysis.
Use when user asks:
- "์ผ์ฑ์ ์ ์ฌ๋ฌด์ ํ" / "Samsung financials"
- "PER ๋ฎ์ ์ข
๋ชฉ" / "low PER stocks"
- "ROE ๋์ ๊ธฐ์
" / "high ROE companies"
- "์ ํ๊ฐ ์ข
๋ชฉ" / "undervalued stocks"
Returns: PER, PBR, ROE, ROA, revenue, operating_income, net_income, debt_ratio, dividend_yield
๐ COMBINE with web_search:
1. get_financials โ "PER 5.2, ROE 15%"
2. web_search "์ผ์ฑ์ ์ ์ค์ ์ ๋ง" โ ๋ฏธ๋ ์ค์ ์์ธก\`,
inputSchema: {
type: 'object',
properties: {
ticker: { type: 'string', description: 'Stock ticker (e.g., 005930)' },
period: { type: 'string', description: 'Quarter (e.g., 2024Q3)' },
market: { type: 'string', enum: ['KOSPI', 'KOSDAQ'], description: 'Market type' },
periods: { type: 'number', description: 'Recent N quarters (default: 4)' },
limit: { type: 'number', description: 'Result count' },
},
},
},
// ============================================================
// ๐ TREND TOOLS
// ============================================================
{
name: 'get_trends',
description: \`๐ [SENTIMENT TRENDS] Get historical sentiment trend for a specific asset over time.
Use when user asks:
- "์ผ์ฑ์ ์ ์ง๋์ฃผ ๋ถ์๊ธฐ" / "Samsung sentiment last week"
- "๋นํธ์ฝ์ธ ์ถ์ธ" / "Bitcoin trend"
- "์ต๊ทผ 7์ผ๊ฐ ๋ด์ค ๋ํฅ" / "news trend over 7 days"
REQUIRES tag_code - use search_tags first!
Returns: daily news_count and avg_sentiment_score over N days
๐ COMBINE with web_search:
1. get_trends โ "์ง๋์ฃผ ๊ฐ์ -2.5๋ก ํ๋ฝ"
2. web_search "์ผ์ฑ์ ์ ์ง๋์ฃผ ์ด์" โ ํ๋ฝ ์์ธ ํ์
\`,
inputSchema: {
type: 'object',
properties: {
tag_code: { type: 'string', description: 'Tag code (e.g., STK005930, CRY_BTC) - REQUIRED. Use search_tags to find this first!' },
days: { type: 'number', description: 'Recent N days (default: 7, max: 30)' },
},
required: ['tag_code'],
},
},
// ============================================================
// ๐ท๏ธ AUTO-TAGGING TOOL
// ============================================================
{
name: 'match_tags',
description: \`๐ท๏ธ [AUTO-TAG EXTRACTION] Extract stock/crypto/theme tags from any text. Useful for categorizing news or analyzing what topics a text mentions.
Use when:
- Analyzing what stocks/themes a news title mentions
- Auto-categorizing text content
- Finding related tags from a sentence
Input: any text (e.g., "์ผ์ฑ์ ์ HBM ๋๋ฐ ์์")
Returns: matched tags with confidence scores\`,
inputSchema: {
type: 'object',
properties: {
text: { type: 'string', description: 'Text to analyze (e.g., "์ผ์ฑ์ ์ HBM ๋๋ฐ ์์")' },
types: { type: 'array', items: { type: 'string' }, description: 'Tag type filter (optional)' },
limit: { type: 'number', description: 'Result count (default: 10)' },
},
required: ['text'],
},
},
],
};
});
// Tool call handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
let result: unknown;
switch (name) {
case 'get_news':
result = await getNews(NewsParamsSchema.parse(args));
break;
case 'get_news_scored':
result = await getNewsScored(NewsScoredParamsSchema.parse(args));
break;
case 'get_chart_stock':
result = await getChartStock(ChartStockParamsSchema.parse(args));
break;
case 'get_chart_coin':
result = await getChartCoin(ChartCoinParamsSchema.parse(args));
break;
case 'get_research':
result = await getResearch(ResearchParamsSchema.parse(args));
break;
case 'get_financials':
result = await getFinancials(FinancialsParamsSchema.parse(args));
break;
case 'get_snapshots':
result = await getSnapshots(SnapshotsParamsSchema.parse(args));
break;
case 'search_tags':
result = await searchTags(TagsSearchParamsSchema.parse(args));
break;
case 'match_tags':
result = await matchTags(TagsMatchParamsSchema.parse(args));
break;
case 'get_trends':
result = await getTrends(TrendsParamsSchema.parse(args));
break;
default:
throw new Error(\`Unknown tool: \${name}\`);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: \`Error: \${errorMessage}\`,
},
],
isError: true,
};
}
});
// Start server
async function main() {
// Check if running in stdio mode (command line argument or specific env var)
const isStdio = process.argv.includes('--stdio');
if (isStdio) {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('RagAlgo MCP Server started (Stdio Mode)');
} else {
// SSE / HTTP Mode (Default for deployment)
const app = express();
const port = process.env.PORT || 8080;
app.use(cors());
app.use(express.json());
let transport: SSEServerTransport | null = null;
app.get('/sse', async (req, res) => {
console.log('New SSE connection established');
transport = new SSEServerTransport('/messages', res);
await server.connect(transport);
});
app.post('/messages', async (req, res) => {
if (transport) {
await transport.handlePostMessage(req, res);
} else {
res.status(404).json({ error: 'Session not found or connection not established' });
}
});
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok', version: '1.0.2' });
});
app.listen(port, () => {
console.log(\`RagAlgo MCP Server listening on port \${port} (SSE Mode)\`);
console.log(\`- SSE Endpoint: http://localhost:\${port}/sse\`);
console.log(\`- Health Check: http://localhost:\${port}/health\`);
});
}
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});