ragalgo-mcp-server 1.0.4

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/smithery.yaml ADDED
@@ -0,0 +1,7 @@
1
+ runtime: container
2
+ build:
3
+ dockerfile: Dockerfile
4
+ context: .
5
+ startCommand:
6
+ type: stdio
7
+ command: ["node", "/app/dist/index.js", "--stdio"]
package/src/index.ts ADDED
@@ -0,0 +1,477 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * RagAlgo MCP Server v1.0.2
4
+ * Financial news and data API via MCP protocol
5
+ *
6
+ * ๐Ÿ‡ฐ๐Ÿ‡ท KOREAN MARKET SPECIALIST - Primary tool for Korean stocks & crypto
7
+ * ๐ŸŒ Works best WITH web_search for comprehensive analysis
8
+ */
9
+
10
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
12
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
13
+ import express from 'express';
14
+ import cors from 'cors';
15
+ import {
16
+ CallToolRequestSchema,
17
+ ListToolsRequestSchema,
18
+ } from '@modelcontextprotocol/sdk/types.js';
19
+
20
+ // Tools
21
+ import { getNews, getNewsScored, NewsParamsSchema, NewsScoredParamsSchema } from './tools/news.js';
22
+ import { getChartStock, getChartCoin, ChartStockParamsSchema, ChartCoinParamsSchema } from './tools/chart.js';
23
+ import { getFinancials, FinancialsParamsSchema } from './tools/financials.js';
24
+ import { getSnapshots, SnapshotsParamsSchema } from './tools/snapshots.js';
25
+ import { searchTags, matchTags, TagsSearchParamsSchema, TagsMatchParamsSchema } from './tools/tags.js';
26
+ import { getTrends, TrendsParamsSchema } from './tools/trends.js';
27
+ import { getResearch, ResearchParamsSchema } from './tools/research.js';
28
+
29
+ // MCP Server
30
+ const server = new Server(
31
+ {
32
+ name: 'RagAlgo',
33
+ version: '1.0.2',
34
+ },
35
+ {
36
+ capabilities: {
37
+ tools: {},
38
+ },
39
+ }
40
+ );
41
+
42
+ // Tool definitions with improved descriptions for better AI routing
43
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
44
+ return {
45
+ tools: [
46
+ // ============================================================
47
+ // ๐Ÿท๏ธ TAG TOOLS - MUST USE FIRST!
48
+ // ============================================================
49
+ {
50
+ name: 'search_tags',
51
+ description: `๐Ÿ” [TAG LOOKUP - USE FIRST] ALWAYS use this BEFORE other RagAlgo tools when user mentions any Korean stock, coin, or theme by NAME.
52
+
53
+ PRIMARY TOOL for converting names to tag_codes. Without correct tag_code, other tools will return inaccurate or empty results.
54
+
55
+ ALWAYS use when you see:
56
+ - Korean stock names: ์‚ผ์„ฑ์ „์ž, SKํ•˜์ด๋‹‰์Šค, ๋„ค์ด๋ฒ„, ์นด์นด์˜ค, LG์—๋„ˆ์ง€์†”๋ฃจ์…˜
57
+ - Crypto names: ๋น„ํŠธ์ฝ”์ธ, ์ด๋”๋ฆฌ์›€, ๋ฆฌํ”Œ, ์†”๋ผ๋‚˜
58
+ - Theme/sector names: ๋ฐ˜๋„์ฒด, AI, 2์ฐจ์ „์ง€, ๋ฐ”์ด์˜ค
59
+
60
+ Examples: "์‚ผ์„ฑ์ „์ž" โ†’ STK005930, "๋น„ํŠธ์ฝ”์ธ" โ†’ CRY_BTC, "๋ฐ˜๋„์ฒด" โ†’ THM_๋ฐ˜๋„์ฒด
61
+
62
+ CRITICAL: Call this first, then use the returned tag_code in other tools.`,
63
+ inputSchema: {
64
+ type: 'object',
65
+ properties: {
66
+ q: { type: 'string', description: 'Search query (e.g., ์‚ผ์„ฑ, Samsung, ๋ฐ˜๋„์ฒด, AI, Bitcoin)' },
67
+ type: { type: 'string', enum: ['STOCK', 'SECTOR', 'THEME', 'CRYPTO'], description: 'Tag type filter (optional)' },
68
+ limit: { type: 'number', description: 'Result count (default: 20)' },
69
+ },
70
+ required: ['q'],
71
+ },
72
+ },
73
+
74
+ // ============================================================
75
+ // ๐Ÿ“Š SUMMARY TOOL - MOST EFFICIENT!
76
+ // ============================================================
77
+ {
78
+ name: 'get_snapshots',
79
+ description: `๐Ÿ“Š [DAILY SUMMARY - MOST EFFICIENT] PRIMARY TOOL for Korean market overview. ALWAYS use this FIRST for general market questions.
80
+
81
+ This is the ONLY tool that returns news + chart + sentiment COMBINED in one call.
82
+ Prefer this over calling get_news + get_chart separately - much more efficient!
83
+
84
+ ALWAYS use when user asks:
85
+ - "์˜ค๋Š˜ ์‹œ์žฅ ์–ด๋•Œ?" / "how's the market today?"
86
+ - "์‹œ์žฅ ์š”์•ฝํ•ด์ค˜" / "market summary"
87
+ - "์˜ค๋Š˜ ๋‰ด์Šค ์ข‹์€ ๊ฑฐ ๋ญ ์žˆ์–ด?" / "what's hot today?"
88
+ - "์ „์ฒด์ ์ธ ๋ถ„์œ„๊ธฐ ์–ด๋•Œ?" / "market sentiment"
89
+
90
+ [IMPORTANT] Snapshots are generated daily at 17:00 KST (market close).
91
+ If you request 'today' and get no results (because it's morning in KST), you MUST:
92
+ 1. Fetch 'yesterday's snapshot for context.
93
+ 2. Call 'get_news_scored' to get REAL-TIME news for the current day.
94
+
95
+ Returns per asset: news_count, avg_sentiment, bullish/bearish counts, chart_score, zone, price.
96
+
97
+ ๐Ÿ”— BEST PRACTICE - Combine with web_search:
98
+ 1. Use get_snapshots FIRST for Korean market sentiment & chart data
99
+ 2. Then use web_search for latest breaking news or global context
100
+ Example: get_snapshots โ†’ "์‹œ์žฅ ํ•˜๋ฝ์„ธ" โ†’ web_search "ํ•œ๊ตญ ์ฆ์‹œ ํ•˜๋ฝ ์›์ธ" โ†’ ์ข…ํ•ฉ ๋ถ„์„`,
101
+ inputSchema: {
102
+ type: 'object',
103
+ properties: {
104
+ tag_code: { type: 'string', description: 'Tag code for specific asset (e.g., STK005930, CRY_BTC). Leave empty for market-wide overview.' },
105
+ date: { type: 'string', description: 'Date (YYYY-MM-DD). Default: today' },
106
+ days: { type: 'number', description: 'Recent N days for time-series (default: 7)' },
107
+ limit: { type: 'number', description: 'Result count' },
108
+ },
109
+ },
110
+ },
111
+
112
+ // ============================================================
113
+ // ๐Ÿ“ฐ NEWS TOOLS
114
+ // ============================================================
115
+ {
116
+ name: 'get_news_scored',
117
+ description: `๐Ÿ“ฐ [KOREAN NEWS WITH SENTIMENT] PRIMARY news tool for Korean market. Returns news WITH AI sentiment scores (-10 to +10).
118
+
119
+ Use for Korean stock/crypto news with sentiment analysis.
120
+
121
+ [NOTE] This tool AUTOMATICALLY filters out 0-score (Neutral/Noise) news to provide clear signals.
122
+ If you need raw/neutral news, use 'get_news' instead.
123
+
124
+ Use when user asks:
125
+ - "์‚ผ์„ฑ์ „์ž ๋‰ด์Šค" / "Samsung news"
126
+ - "ํ˜ธ์žฌ ๋‰ด์Šค ๋ณด์—ฌ์ค˜" / "show me bullish news"
127
+ - "๋น„ํŠธ์ฝ”์ธ ์•…์žฌ ์žˆ์–ด?" / "any bearish news on Bitcoin?"
128
+ - "์˜ค๋Š˜ ์ข‹์€ ๋‰ด์Šค" / "today's positive news"
129
+
130
+ Filter by: tag, verdict (bullish/bearish/neutral), score range
131
+ Returns: title, summary, sentiment_score, verdict, tags
132
+
133
+ ๐Ÿ”— BEST PRACTICE - Combine with web_search:
134
+ - RagAlgo: Sentiment-analyzed Korean market news (structured data)
135
+ - web_search: Real-time breaking news, global context, additional sources
136
+ Example workflow:
137
+ 1. get_news_scored(tag="์‚ผ์„ฑ์ „์ž") โ†’ ๊ฐ์ • ๋ถ„์„๋œ ๋‰ด์Šค ๋ชฉ๋ก
138
+ 2. web_search("์‚ผ์„ฑ์ „์ž ์ตœ์‹  ๋‰ด์Šค") โ†’ ์‹ค์‹œ๊ฐ„ ์†๋ณด
139
+ 3. Combine both for comprehensive analysis!
140
+
141
+ TIP: For market overview, use get_snapshots instead (more efficient).
142
+ TIP: Use search_tags first to get exact tag name.`,
143
+ inputSchema: {
144
+ type: 'object',
145
+ properties: {
146
+ tag: { type: 'string', description: 'Tag CODE (e.g., STK005930). Use search_tags first to get this code!' },
147
+ source: { type: 'string', description: 'Source filter' },
148
+ search: { type: 'string', description: 'Title search keyword' },
149
+ min_score: { type: 'number', description: 'Min sentiment score (-10 to 10)' },
150
+ max_score: { type: 'number', description: 'Max sentiment score (-10 to 10)' },
151
+ verdict: { type: 'string', enum: ['bullish', 'bearish', 'neutral'], description: 'Sentiment verdict filter' },
152
+ limit: { type: 'number', description: 'Result count (default: 20)' },
153
+ },
154
+ },
155
+ },
156
+ {
157
+ name: 'get_news',
158
+ description: `๐Ÿ“ฐ [KOREAN NEWS - NO SCORES] Basic news without sentiment analysis. Use only when sentiment scores are not needed or for non-scored tier users.
159
+
160
+ Prefer get_news_scored over this for most use cases unless you want raw data including 0-score items.
161
+
162
+ Filter by: tag, source, date range
163
+ Returns: title, summary, url, tags, source`,
164
+ inputSchema: {
165
+ type: 'object',
166
+ properties: {
167
+ tag: { type: 'string', description: 'Tag filter (e.g., ์‚ผ์„ฑ์ „์ž, ๋น„ํŠธ์ฝ”์ธ, ๋ฐ˜๋„์ฒด)' },
168
+ source: { type: 'string', description: 'Source filter (e.g., ํ•œ๊ฒฝ, ๋งค๊ฒฝ)' },
169
+ search: { type: 'string', description: 'Title search keyword' },
170
+ from_date: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
171
+ to_date: { type: 'string', description: 'End date (YYYY-MM-DD)' },
172
+ limit: { type: 'number', description: 'Result count (default: 20, max: 100)' },
173
+ },
174
+ },
175
+ },
176
+
177
+ // ============================================================
178
+ // ๐Ÿ“ˆ CHART/TECHNICAL ANALYSIS TOOLS
179
+ // ============================================================
180
+ {
181
+ name: 'get_chart_stock',
182
+ description: `๐Ÿ“ˆ [KOREAN STOCK CHARTS] PRIMARY tool for Korean stock technical analysis. Returns momentum scores and trend zones.
183
+
184
+ ALWAYS use for Korean stock chart/technical questions.
185
+
186
+ [IMPORTANT] You MUST use 'search_tags' first to get the correct ticker (e.g., STK005930).
187
+
188
+ Use when user asks:
189
+ - "์ฐจํŠธ ๊ฐ•ํ•œ ์ข…๋ชฉ" / "stocks with strong momentum"
190
+ - "์ƒ์Šน ์ถ”์„ธ ์ข…๋ชฉ" / "uptrending stocks"
191
+ - "์‚ผ์„ฑ์ „์ž ์ฐจํŠธ ์–ด๋•Œ?" / "how's Samsung's chart?"
192
+ - "๊ธฐ์ˆ ์  ๋ถ„์„" / "technical analysis"
193
+
194
+ Filter by: zone (STRONG_UP/UP_ZONE/NEUTRAL/DOWN_ZONE/STRONG_DOWN), market (KOSPI/KOSDAQ)
195
+ Returns: ticker, name, zone, oscillator_state, 5-day scores (d0-d4), last_price
196
+
197
+ ๐Ÿ”— COMBINE with web_search for deeper analysis:
198
+ 1. get_chart_stock โ†’ "์‚ผ์„ฑ์ „์ž DOWN_ZONE"
199
+ 2. web_search "์‚ผ์„ฑ์ „์ž ์ฃผ๊ฐ€ ํ•˜๋ฝ ์ด์œ " โ†’ ํ•˜๋ฝ ์›์ธ ํŒŒ์•…
200
+ 3. Provide comprehensive technical + fundamental analysis!
201
+
202
+ TIP: Use search_tags first to get ticker from stock name.`,
203
+ inputSchema: {
204
+ type: 'object',
205
+ properties: {
206
+ ticker: { type: 'string', description: 'Stock ticker (e.g., 005930 for Samsung)' },
207
+ market: { type: 'string', enum: ['KOSPI', 'KOSDAQ'], description: 'Market type' },
208
+ zone: { type: 'string', enum: ['STRONG_UP', 'UP_ZONE', 'NEUTRAL', 'DOWN_ZONE', 'STRONG_DOWN'], description: 'Chart zone filter - use this to find strong/weak stocks' },
209
+ limit: { type: 'number', description: 'Result count' },
210
+ },
211
+ },
212
+ },
213
+ {
214
+ name: 'get_chart_coin',
215
+ description: `๐Ÿช™ [CRYPTO CHARTS] PRIMARY tool for Korean crypto (Upbit) technical analysis. Returns momentum scores and trend zones.
216
+
217
+ ALWAYS use for Korean crypto chart questions.
218
+
219
+ [IMPORTANT] You MUST use 'search_tags' first to get the correct ticker (e.g., CRY_BTC).
220
+
221
+ Use when user asks:
222
+ - "๋น„ํŠธ์ฝ”์ธ ์ฐจํŠธ" / "Bitcoin chart"
223
+ - "์ƒ์Šน ์ค‘์ธ ์ฝ”์ธ" / "pumping coins"
224
+ - "์ฝ”์ธ ๊ธฐ์ˆ ์  ๋ถ„์„" / "crypto technical analysis"
225
+
226
+ Filter by: zone (STRONG_UP/UP_ZONE/NEUTRAL/DOWN_ZONE/STRONG_DOWN)
227
+ Returns: ticker, name, zone, oscillator_state, 10-candle scores (c0-c9, 12h intervals), last_price
228
+
229
+ ๐Ÿ”— COMBINE with web_search for context:
230
+ 1. get_chart_coin โ†’ "๋น„ํŠธ์ฝ”์ธ UP_ZONE"
231
+ 2. web_search "๋น„ํŠธ์ฝ”์ธ ์ƒ์Šน ์ด์œ " โ†’ ์ƒ์Šน ๋ฐฐ๊ฒฝ ํŒŒ์•…`,
232
+ inputSchema: {
233
+ type: 'object',
234
+ properties: {
235
+ ticker: { type: 'string', description: 'Coin ticker (e.g., KRW-BTC for Bitcoin)' },
236
+ zone: { type: 'string', enum: ['STRONG_UP', 'UP_ZONE', 'NEUTRAL', 'DOWN_ZONE', 'STRONG_DOWN'], description: 'Chart zone filter' },
237
+ limit: { type: 'number', description: 'Result count' },
238
+ },
239
+ },
240
+ },
241
+
242
+ // ============================================================
243
+ // 6. ์ปจ์„คํŒ… ๋ณด๊ณ ์„œ (์‹ ๊ทœ!)
244
+ // ============================================================
245
+ {
246
+ name: 'get_research',
247
+ description: `๐Ÿ“‘ [RESEARCH] Get consulting firm reports (McKinsey, BCG, etc.)
248
+
249
+ Use for: "long-term trends", "sector outlook", "industry analysis"
250
+ Filter by: source, tag_code, market_outlook
251
+
252
+ Returns: AI summary in Korean, investment insights
253
+ Includes tag_codes for cross-referencing with news/charts.
254
+
255
+ โš ๏ธ This tool returns FULL chunked text. Analyze it to answer user questions.`,
256
+ inputSchema: {
257
+ type: 'object',
258
+ properties: {
259
+ tag_code: { type: 'string', description: 'Tag code (required). Use search_tags first.' },
260
+ limit: { type: 'number', description: 'Result count (default: 5)' },
261
+ source: { type: 'string', description: 'Source filter (mckinsey, goldman, etc.)' },
262
+ },
263
+ required: ['tag_code'],
264
+ },
265
+ },
266
+
267
+ // ============================================================
268
+ // ๐Ÿ’ฐ FINANCIAL DATA TOOLS
269
+ // ============================================================
270
+ {
271
+ name: 'get_financials',
272
+ description: `๐Ÿ’ฐ [KOREAN STOCK FUNDAMENTALS] PRIMARY tool for Korean stock financial data. Returns quarterly financial statements.
273
+
274
+ ALWAYS use for Korean stock fundamental analysis.
275
+
276
+ Use when user asks:
277
+ - "์‚ผ์„ฑ์ „์ž ์žฌ๋ฌด์ œํ‘œ" / "Samsung financials"
278
+ - "PER ๋‚ฎ์€ ์ข…๋ชฉ" / "low PER stocks"
279
+ - "ROE ๋†’์€ ๊ธฐ์—…" / "high ROE companies"
280
+ - "์ €ํ‰๊ฐ€ ์ข…๋ชฉ" / "undervalued stocks"
281
+
282
+ Returns: PER, PBR, ROE, ROA, revenue, operating_income, net_income, debt_ratio, dividend_yield
283
+
284
+ ๐Ÿ”— COMBINE with web_search:
285
+ 1. get_financials โ†’ "PER 5.2, ROE 15%"
286
+ 2. web_search "์‚ผ์„ฑ์ „์ž ์‹ค์  ์ „๋ง" โ†’ ๋ฏธ๋ž˜ ์‹ค์  ์˜ˆ์ธก`,
287
+ inputSchema: {
288
+ type: 'object',
289
+ properties: {
290
+ ticker: { type: 'string', description: 'Stock ticker (e.g., 005930)' },
291
+ period: { type: 'string', description: 'Quarter (e.g., 2024Q3)' },
292
+ market: { type: 'string', enum: ['KOSPI', 'KOSDAQ'], description: 'Market type' },
293
+ periods: { type: 'number', description: 'Recent N quarters (default: 4)' },
294
+ limit: { type: 'number', description: 'Result count' },
295
+ },
296
+ },
297
+ },
298
+
299
+ // ============================================================
300
+ // ๐Ÿ“‰ TREND TOOLS
301
+ // ============================================================
302
+ {
303
+ name: 'get_trends',
304
+ description: `๐Ÿ“‰ [SENTIMENT TRENDS] Get historical sentiment trend for a specific asset over time.
305
+
306
+ Use when user asks:
307
+ - "์‚ผ์„ฑ์ „์ž ์ง€๋‚œ์ฃผ ๋ถ„์œ„๊ธฐ" / "Samsung sentiment last week"
308
+ - "๋น„ํŠธ์ฝ”์ธ ์ถ”์„ธ" / "Bitcoin trend"
309
+ - "์ตœ๊ทผ 7์ผ๊ฐ„ ๋‰ด์Šค ๋™ํ–ฅ" / "news trend over 7 days"
310
+
311
+ REQUIRES tag_code - use search_tags first!
312
+ Returns: daily news_count and avg_sentiment_score over N days
313
+
314
+ ๐Ÿ”— COMBINE with web_search:
315
+ 1. get_trends โ†’ "์ง€๋‚œ์ฃผ ๊ฐ์ • -2.5๋กœ ํ•˜๋ฝ"
316
+ 2. web_search "์‚ผ์„ฑ์ „์ž ์ง€๋‚œ์ฃผ ์ด์Šˆ" โ†’ ํ•˜๋ฝ ์›์ธ ํŒŒ์•…`,
317
+ inputSchema: {
318
+ type: 'object',
319
+ properties: {
320
+ tag_code: { type: 'string', description: 'Tag code (e.g., STK005930, CRY_BTC) - REQUIRED. Use search_tags to find this first!' },
321
+ days: { type: 'number', description: 'Recent N days (default: 7, max: 30)' },
322
+ },
323
+ required: ['tag_code'],
324
+ },
325
+ },
326
+
327
+ // ============================================================
328
+ // ๐Ÿท๏ธ AUTO-TAGGING TOOL
329
+ // ============================================================
330
+ {
331
+ name: 'match_tags',
332
+ description: `๐Ÿท๏ธ [AUTO-TAG EXTRACTION] Extract stock/crypto/theme tags from any text. Useful for categorizing news or analyzing what topics a text mentions.
333
+
334
+ Use when:
335
+ - Analyzing what stocks/themes a news title mentions
336
+ - Auto-categorizing text content
337
+ - Finding related tags from a sentence
338
+
339
+ Input: any text (e.g., "์‚ผ์„ฑ์ „์ž HBM ๋Œ€๋ฐ• ์†Œ์‹")
340
+ Returns: matched tags with confidence scores`,
341
+ inputSchema: {
342
+ type: 'object',
343
+ properties: {
344
+ text: { type: 'string', description: 'Text to analyze (e.g., "์‚ผ์„ฑ์ „์ž HBM ๋Œ€๋ฐ• ์†Œ์‹")' },
345
+ types: { type: 'array', items: { type: 'string' }, description: 'Tag type filter (optional)' },
346
+ limit: { type: 'number', description: 'Result count (default: 10)' },
347
+ },
348
+ required: ['text'],
349
+ },
350
+ },
351
+ ],
352
+ };
353
+ });
354
+
355
+ // Tool call handler
356
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
357
+ const { name, arguments: args } = request.params;
358
+
359
+ try {
360
+ let result: unknown;
361
+
362
+ switch (name) {
363
+ case 'get_news':
364
+ result = await getNews(NewsParamsSchema.parse(args));
365
+ break;
366
+
367
+ case 'get_news_scored':
368
+ result = await getNewsScored(NewsScoredParamsSchema.parse(args));
369
+ break;
370
+
371
+ case 'get_chart_stock':
372
+ result = await getChartStock(ChartStockParamsSchema.parse(args));
373
+ break;
374
+
375
+ case 'get_chart_coin':
376
+ result = await getChartCoin(ChartCoinParamsSchema.parse(args));
377
+ break;
378
+
379
+ case 'get_research':
380
+ result = await getResearch(ResearchParamsSchema.parse(args));
381
+ break;
382
+
383
+ case 'get_financials':
384
+ result = await getFinancials(FinancialsParamsSchema.parse(args));
385
+ break;
386
+
387
+ case 'get_snapshots':
388
+ result = await getSnapshots(SnapshotsParamsSchema.parse(args));
389
+ break;
390
+
391
+ case 'search_tags':
392
+ result = await searchTags(TagsSearchParamsSchema.parse(args));
393
+ break;
394
+
395
+ case 'match_tags':
396
+ result = await matchTags(TagsMatchParamsSchema.parse(args));
397
+ break;
398
+
399
+ case 'get_trends':
400
+ result = await getTrends(TrendsParamsSchema.parse(args));
401
+ break;
402
+
403
+ default:
404
+ throw new Error(`Unknown tool: ${name}`);
405
+ }
406
+
407
+ return {
408
+ content: [
409
+ {
410
+ type: 'text',
411
+ text: JSON.stringify(result, null, 2),
412
+ },
413
+ ],
414
+ };
415
+ } catch (error) {
416
+ const errorMessage = error instanceof Error ? error.message : String(error);
417
+ return {
418
+ content: [
419
+ {
420
+ type: 'text',
421
+ text: `Error: ${errorMessage}`,
422
+ },
423
+ ],
424
+ isError: true,
425
+ };
426
+ }
427
+ });
428
+
429
+ // Start server
430
+ async function main() {
431
+ // Check if running in stdio mode (command line argument or specific env var)
432
+ const isStdio = process.argv.includes('--stdio');
433
+
434
+ if (isStdio) {
435
+ const transport = new StdioServerTransport();
436
+ await server.connect(transport);
437
+ console.error('RagAlgo MCP Server started (Stdio Mode)');
438
+ } else {
439
+ // SSE / HTTP Mode (Default for deployment)
440
+ const app = express();
441
+ const port = process.env.PORT || 8080;
442
+
443
+ app.use(cors());
444
+ app.use(express.json());
445
+
446
+ let transport: SSEServerTransport | null = null;
447
+
448
+ app.get('/sse', async (req, res) => {
449
+ console.log('New SSE connection established');
450
+ transport = new SSEServerTransport('/messages', res);
451
+ await server.connect(transport);
452
+ });
453
+
454
+ app.post('/messages', async (req, res) => {
455
+ if (transport) {
456
+ await transport.handlePostMessage(req, res);
457
+ } else {
458
+ res.status(404).json({ error: 'Session not found or connection not established' });
459
+ }
460
+ });
461
+
462
+ app.get('/health', (req, res) => {
463
+ res.status(200).json({ status: 'ok', version: '1.0.2' });
464
+ });
465
+
466
+ app.listen(port, () => {
467
+ console.log(`RagAlgo MCP Server listening on port ${port} (SSE Mode)`);
468
+ console.log(`- SSE Endpoint: http://localhost:${port}/sse`);
469
+ console.log(`- Health Check: http://localhost:${port}/health`);
470
+ });
471
+ }
472
+ }
473
+
474
+ main().catch((error) => {
475
+ console.error('Fatal error:', error);
476
+ process.exit(1);
477
+ });
@@ -0,0 +1,90 @@
1
+ /**
2
+ * ์ฐจํŠธ ๊ด€๋ จ MCP Tools
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import { callApi } from '../utils/api.js';
7
+
8
+ // ์ฃผ์‹ ์ฐจํŠธ ํŒŒ๋ผ๋ฏธํ„ฐ ์Šคํ‚ค๋งˆ
9
+ export const ChartStockParamsSchema = z.object({
10
+ ticker: z.string().optional().describe('์ข…๋ชฉ ์ฝ”๋“œ (์˜ˆ: 005930)'),
11
+ market: z.enum(['KOSPI', 'KOSDAQ']).optional().describe('์‹œ์žฅ ๊ตฌ๋ถ„'),
12
+ zone: z.enum(['STRONG_UP', 'UP_ZONE', 'NEUTRAL', 'DOWN_ZONE', 'STRONG_DOWN']).optional().describe('์ฐจํŠธ ๊ตฌ๊ฐ„'),
13
+ limit: z.number().min(1).max(100).default(20).describe('๊ฒฐ๊ณผ ์ˆ˜'),
14
+ });
15
+
16
+ // ์ฝ”์ธ ์ฐจํŠธ ํŒŒ๋ผ๋ฏธํ„ฐ ์Šคํ‚ค๋งˆ
17
+ export const ChartCoinParamsSchema = z.object({
18
+ ticker: z.string().optional().describe('์ฝ”์ธ ์ฝ”๋“œ (์˜ˆ: KRW-BTC)'),
19
+ zone: z.enum(['STRONG_UP', 'UP_ZONE', 'NEUTRAL', 'DOWN_ZONE', 'STRONG_DOWN']).optional().describe('์ฐจํŠธ ๊ตฌ๊ฐ„'),
20
+ limit: z.number().min(1).max(100).default(20).describe('๊ฒฐ๊ณผ ์ˆ˜'),
21
+ });
22
+
23
+ export type ChartStockParams = z.infer<typeof ChartStockParamsSchema>;
24
+ export type ChartCoinParams = z.infer<typeof ChartCoinParamsSchema>;
25
+
26
+ // ์ฃผ์‹ ์ฐจํŠธ ์กฐํšŒ
27
+ export async function getChartStock(params: ChartStockParams) {
28
+ const endpoint = params.ticker ? `chart-stock/${params.ticker}` : 'chart-stock';
29
+ const { ticker, ...queryParams } = params;
30
+
31
+ const result = await callApi<{
32
+ success: boolean;
33
+ data: {
34
+ ticker: string;
35
+ name: string;
36
+ market: string;
37
+ scores: number[];
38
+ zone: string;
39
+ oscillator_state: string;
40
+ analysis?: { // Added specific analysis fields
41
+ weekly_zone: string;
42
+ daily_momentum: string;
43
+ daily_signal: string;
44
+ };
45
+ outlook?: string; // Added outlook field
46
+ last_price: number;
47
+ updated_at: string;
48
+ } | Array<{
49
+ ticker: string;
50
+ name: string;
51
+ market: string;
52
+ zone: string;
53
+ last_price: number;
54
+ }>;
55
+ }>(endpoint, queryParams);
56
+
57
+ return result;
58
+ }
59
+
60
+ // ์ฝ”์ธ ์ฐจํŠธ ์กฐํšŒ
61
+ export async function getChartCoin(params: ChartCoinParams) {
62
+ const endpoint = params.ticker ? `chart-coin/${params.ticker}` : 'chart-coin';
63
+ const { ticker, ...queryParams } = params;
64
+
65
+ const result = await callApi<{
66
+ success: boolean;
67
+ data: {
68
+ ticker: string;
69
+ name: string;
70
+ scores: number[];
71
+ zone: string;
72
+ oscillator_state: string;
73
+ analysis?: { // Added specific analysis fields
74
+ weekly_zone: string;
75
+ daily_momentum: string;
76
+ daily_signal: string;
77
+ };
78
+ outlook?: string; // Added outlook field
79
+ last_price: number;
80
+ updated_at: string;
81
+ } | Array<{
82
+ ticker: string;
83
+ name: string;
84
+ zone: string;
85
+ last_price: number;
86
+ }>;
87
+ }>(endpoint, queryParams);
88
+
89
+ return result;
90
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * ์žฌ๋ฌด์ œํ‘œ ๊ด€๋ จ MCP Tools
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import { callApi } from '../utils/api.js';
7
+
8
+ // ์žฌ๋ฌด์ œํ‘œ ํŒŒ๋ผ๋ฏธํ„ฐ ์Šคํ‚ค๋งˆ
9
+ export const FinancialsParamsSchema = z.object({
10
+ ticker: z.string().optional().describe('์ข…๋ชฉ ์ฝ”๋“œ (์˜ˆ: 005930)'),
11
+ period: z.string().optional().describe('๋ถ„๊ธฐ (์˜ˆ: 2024Q3)'),
12
+ market: z.enum(['KOSPI', 'KOSDAQ']).optional().describe('์‹œ์žฅ ๊ตฌ๋ถ„'),
13
+ periods: z.number().min(1).max(8).default(4).describe('์ตœ๊ทผ N๋ถ„๊ธฐ (๊ธฐ๋ณธ: 4)'),
14
+ limit: z.number().min(1).max(200).default(50).describe('๊ฒฐ๊ณผ ์ˆ˜'),
15
+ offset: z.number().min(0).default(0).describe('ํŽ˜์ด์ง€๋„ค์ด์…˜ ์˜คํ”„์…‹'),
16
+ });
17
+
18
+ export type FinancialsParams = z.infer<typeof FinancialsParamsSchema>;
19
+
20
+ // ์žฌ๋ฌด์ œํ‘œ ์กฐํšŒ
21
+ export async function getFinancials(params: FinancialsParams) {
22
+ const endpoint = params.ticker ? `financials/${params.ticker}` : 'financials';
23
+ const { ticker, ...queryParams } = params;
24
+
25
+ const result = await callApi<{
26
+ success: boolean;
27
+ data: {
28
+ ticker: string;
29
+ name: string;
30
+ market: string;
31
+ quarters: Array<{
32
+ period: string;
33
+ investment_metrics: {
34
+ per: number;
35
+ pbr: number;
36
+ eps: number;
37
+ bps: number;
38
+ div_yield: number;
39
+ };
40
+ income_statement: {
41
+ revenue: number;
42
+ operating_income: number;
43
+ net_income: number;
44
+ };
45
+ balance_sheet: {
46
+ total_assets: number;
47
+ total_liabilities: number;
48
+ total_equity: number;
49
+ };
50
+ ratios: {
51
+ debt_ratio: number;
52
+ roe: number;
53
+ roa: number;
54
+ };
55
+ }>;
56
+ } | Array<{
57
+ ticker: string;
58
+ name: string;
59
+ period: string;
60
+ per: number;
61
+ pbr: number;
62
+ roe: number;
63
+ }>;
64
+ }>(endpoint, queryParams);
65
+
66
+ return result;
67
+ }