ragalgo-mcp-server 1.0.4 → 1.0.6
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.md +150 -31
- package/dist/check_sdk.d.ts +1 -0
- package/dist/check_sdk.js +11 -0
- package/dist/check_sse.d.ts +1 -0
- package/dist/check_sse.js +10 -0
- package/dist/index.d.ts +0 -7
- package/dist/index.js +470 -405
- package/dist/tools/chart.d.ts +5 -3
- package/dist/tools/chart.js +6 -7
- package/dist/tools/financials.d.ts +3 -3
- package/dist/tools/financials.js +1 -1
- package/dist/tools/news.d.ts +21 -6
- package/dist/tools/news.js +25 -6
- package/dist/tools/research.d.ts +20 -15
- package/dist/tools/research.js +9 -9
- package/dist/tools/rooms.d.ts +33 -0
- package/dist/tools/rooms.js +43 -0
- package/dist/tools/snapshots.d.ts +4 -1
- package/dist/tools/snapshots.js +6 -2
- package/dist/tools/tags.d.ts +40 -6
- package/dist/tools/tags.js +13 -2
- package/dist/utils/api.js +63 -21
- package/package.json +12 -4
- package/server.json +31 -34
- package/.dockerignore +0 -4
- package/.mcpregistry_github_token +0 -1
- package/.mcpregistry_registry_token +0 -1
- package/Dockerfile +0 -23
- package/android-chrome-192x192.png +0 -0
- package/mcp-publisher.exe +0 -0
- package/smithery.yaml +0 -7
- package/src/index.ts +0 -477
- package/src/tools/chart.ts +0 -90
- package/src/tools/financials.ts +0 -67
- package/src/tools/news.ts +0 -67
- package/src/tools/research.ts +0 -40
- package/src/tools/snapshots.ts +0 -55
- package/src/tools/tags.ts +0 -61
- package/src/tools/trends.ts +0 -35
- package/src/utils/api.ts +0 -76
- package/tsconfig.json +0 -21
package/dist/index.js
CHANGED
|
@@ -1,440 +1,505 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
2
|
+
// ------------------------------------------------------------------------------------------------
|
|
3
|
+
// CRASH GUARD: Register error handlers BEFORE any other imports to catch initialization errors
|
|
4
|
+
// ------------------------------------------------------------------------------------------------
|
|
5
|
+
process.on('uncaughtException', (err) => {
|
|
6
|
+
console.error('FATAL CLOUD CRASH (Uncaught Exception):', err);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
});
|
|
9
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
10
|
+
console.error('FATAL CLOUD CRASH (Unhandled Rejection) at:', promise, 'reason:', reason);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
|
13
|
+
console.error('Process started. Registered crash guards.'); // Use stderr for visibility
|
|
14
|
+
// ------------------------------------------------------------------------------------------------
|
|
9
15
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
10
16
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
17
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
18
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
12
19
|
import express from 'express';
|
|
13
20
|
import cors from 'cors';
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
21
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
22
|
+
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
23
|
+
// ------------------------------------------------------------------------------------------------
|
|
24
|
+
// 🛠️ SMITHERY & DEPLOYMENT BEST PRACTICES FIX
|
|
25
|
+
// ------------------------------------------------------------------------------------------------
|
|
26
|
+
/**
|
|
27
|
+
* HTTP POST Transport for single-request JSON-RPC (Stateless)
|
|
28
|
+
* Robust version that handles notifications vs requests and prevents timeouts.
|
|
29
|
+
*/
|
|
30
|
+
/**
|
|
31
|
+
* HTTP POST Transport for JSON-RPC (Stateless)
|
|
32
|
+
* Robust version that handles Batch Requests, Buffering, and Shims.
|
|
33
|
+
*/
|
|
34
|
+
class HttpPostTransport {
|
|
35
|
+
res;
|
|
36
|
+
ignoredIds = new Set();
|
|
37
|
+
responseBuffer = [];
|
|
38
|
+
isBatch;
|
|
39
|
+
// Async Synchronization for Stateless HTTP
|
|
40
|
+
pendingRequestIds = new Set();
|
|
41
|
+
responseResolvers = new Map();
|
|
42
|
+
constructor(res, isBatch = false) {
|
|
43
|
+
this.res = res;
|
|
44
|
+
this.isBatch = isBatch;
|
|
45
|
+
}
|
|
46
|
+
ignoreId(id) {
|
|
47
|
+
this.ignoredIds.add(id);
|
|
48
|
+
}
|
|
49
|
+
// Called by the handler to signal we EXPECT a response for this ID
|
|
50
|
+
markRequestPending(id) {
|
|
51
|
+
this.pendingRequestIds.add(id);
|
|
52
|
+
}
|
|
53
|
+
start() {
|
|
54
|
+
return Promise.resolve();
|
|
55
|
+
}
|
|
56
|
+
async send(message) {
|
|
57
|
+
const id = message.id;
|
|
58
|
+
// 1. Check if this is a response to a pending request
|
|
59
|
+
if (id !== undefined && message.result !== undefined || message.error !== undefined) {
|
|
60
|
+
if (this.pendingRequestIds.has(id)) {
|
|
61
|
+
this.pendingRequestIds.delete(id);
|
|
62
|
+
// If there's a resolver waiting for this ID (unlikely in this design, but good for completeness)
|
|
63
|
+
// meaningful if we were waiting on specific ID promises.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// 2. Filter out ignored messages (internal shims)
|
|
67
|
+
// Even if ignored, we effectively "handled" the pending state by receiving it here.
|
|
68
|
+
if (id !== undefined && this.ignoredIds.has(id)) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// 3. Buffer the response
|
|
72
|
+
this.responseBuffer.push(message);
|
|
73
|
+
}
|
|
74
|
+
// Explicit method to flush responses to HTTP
|
|
75
|
+
async flush() {
|
|
76
|
+
if (this.res.headersSent)
|
|
77
|
+
return;
|
|
78
|
+
// WAIT loop: Wait for all pending requests to result in a response (or timeout)
|
|
79
|
+
const startTime = Date.now();
|
|
80
|
+
while (this.pendingRequestIds.size > 0) {
|
|
81
|
+
if (Date.now() - startTime > 9000) { // 9s timeout (server has 10s global timeout)
|
|
82
|
+
console.error('HttpPostTransport: Timed out waiting for pending responses:', Array.from(this.pendingRequestIds));
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
await new Promise(resolve => setTimeout(resolve, 50)); // Poll every 50ms
|
|
86
|
+
}
|
|
87
|
+
// If no responses, send 200 OK with empty array (or object) to allow client parsing
|
|
88
|
+
// Smithery seems to error on 204 No Content ("Unexpected content type: null")
|
|
89
|
+
if (this.responseBuffer.length === 0) {
|
|
90
|
+
console.log('Use HttpPostTransport: Buffer empty, sending []');
|
|
91
|
+
this.res.status(200).json([]);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Send Batch or Single response
|
|
95
|
+
if (this.isBatch) {
|
|
96
|
+
this.res.json(this.responseBuffer);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
// Strict JSON-RPC: Single Request -> Single Response.
|
|
100
|
+
this.res.json(this.responseBuffer[0]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async close() {
|
|
104
|
+
return Promise.resolve();
|
|
105
|
+
}
|
|
106
|
+
onclose;
|
|
107
|
+
onerror;
|
|
108
|
+
onmessage;
|
|
109
|
+
handleMessage(message) {
|
|
110
|
+
if (this.onmessage) {
|
|
111
|
+
this.onmessage(message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
async function main() {
|
|
116
|
+
try {
|
|
117
|
+
console.error('Initializing Server...');
|
|
118
|
+
// ... (Environment checks remain same) ...
|
|
119
|
+
if (!process.env.RAGALGO_API_KEY) {
|
|
120
|
+
console.error('⚠️ WARNING: RAGALGO_API_KEY is not set.');
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
console.error('✅ RAGALGO_API_KEY is detected.');
|
|
124
|
+
}
|
|
125
|
+
// Import tools
|
|
126
|
+
const { getNews, getNewsScored, NewsParamsSchema, NewsScoredParamsSchema } = await import('./tools/news.js');
|
|
127
|
+
const { getChartStock, getChartCoin, ChartStockParamsSchema, ChartCoinParamsSchema } = await import('./tools/chart.js');
|
|
128
|
+
const { getFinancials, FinancialsParamsSchema } = await import('./tools/financials.js');
|
|
129
|
+
const { getSnapshots, SnapshotsParamsSchema } = await import('./tools/snapshots.js');
|
|
130
|
+
const { searchTags, SearchTagsParamsSchema, matchTags, MatchTagsParamsSchema } = await import('./tools/tags.js');
|
|
131
|
+
const { getTrends, TrendsParamsSchema } = await import('./tools/trends.js');
|
|
132
|
+
const { getResearch, ResearchParamsSchema } = await import('./tools/research.js');
|
|
133
|
+
const { getAvailableRooms, GetAvailableRoomsSchema } = await import('./tools/rooms.js');
|
|
134
|
+
const isStdio = process.argv.includes('--stdio');
|
|
135
|
+
// Helper to create a fresh MCP Server instance
|
|
136
|
+
const createServer = () => {
|
|
137
|
+
const server = new Server({
|
|
138
|
+
name: 'RagAlgo',
|
|
139
|
+
version: '1.0.7',
|
|
140
|
+
}, {
|
|
141
|
+
capabilities: {
|
|
142
|
+
tools: {},
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
146
|
+
return {
|
|
147
|
+
tools: [
|
|
148
|
+
{
|
|
149
|
+
name: 'search_tags',
|
|
150
|
+
description: `🔍 [TAG LOOKUP - USE FIRST] ALWAYS use this BEFORE other RagAlgo tools when user mentions any stock, coin, or theme by NAME.
|
|
42
151
|
|
|
43
152
|
PRIMARY TOOL for converting names to tag_codes. Without correct tag_code, other tools will return inaccurate or empty results.
|
|
44
153
|
|
|
45
|
-
ALWAYS use when
|
|
46
|
-
-
|
|
47
|
-
- Crypto names:
|
|
48
|
-
-
|
|
154
|
+
ALWAYS use when user asks:
|
|
155
|
+
- Stock names: Apple, Tesla, Samsung, Nvidia, Toyota
|
|
156
|
+
- Crypto names: Bitcoin, Ethereum, Ripple, Solana
|
|
157
|
+
- Index/Market names: S&P 500, Nasdaq, Dow Jones, Nikkei 225
|
|
158
|
+
- Theme/sector names: AI, Semiconductor, EV, Bio
|
|
49
159
|
|
|
50
|
-
Examples: "
|
|
160
|
+
Examples: "Apple" → USTK_AAPL, "Samsung" → STK005930, "S&P 500" → ^GSPC
|
|
51
161
|
|
|
52
162
|
CRITICAL: Call this first, then use the returned tag_code in other tools.`,
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
limit: { type: 'number', description: 'Result count (default: 20)' },
|
|
59
|
-
},
|
|
60
|
-
required: ['q'],
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
// ============================================================
|
|
64
|
-
// 📊 SUMMARY TOOL - MOST EFFICIENT!
|
|
65
|
-
// ============================================================
|
|
66
|
-
{
|
|
67
|
-
name: 'get_snapshots',
|
|
68
|
-
description: `📊 [DAILY SUMMARY - MOST EFFICIENT] PRIMARY TOOL for Korean market overview. ALWAYS use this FIRST for general market questions.
|
|
163
|
+
inputSchema: zodToJsonSchema(SearchTagsParamsSchema)
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: 'get_snapshots',
|
|
167
|
+
description: `📊 [TIER 1: GLOBAL MARKET DASHBOARD] PRIMARY TOOL for ALL market questions. ALWAYS use this FIRST.
|
|
69
168
|
|
|
70
|
-
This is the ONLY tool that returns news + chart +
|
|
169
|
+
This is the ONLY tool that returns news + chart + research COMBINED in one call.
|
|
71
170
|
Prefer this over calling get_news + get_chart separately - much more efficient!
|
|
72
171
|
|
|
73
172
|
ALWAYS use when user asks:
|
|
74
|
-
- "
|
|
75
|
-
- "
|
|
76
|
-
- "
|
|
77
|
-
- "
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
Returns per asset:
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
TIP: Use search_tags first to get exact tag name.`,
|
|
131
|
-
inputSchema: {
|
|
132
|
-
type: 'object',
|
|
133
|
-
properties: {
|
|
134
|
-
tag: { type: 'string', description: 'Tag CODE (e.g., STK005930). Use search_tags first to get this code!' },
|
|
135
|
-
source: { type: 'string', description: 'Source filter' },
|
|
136
|
-
search: { type: 'string', description: 'Title search keyword' },
|
|
137
|
-
min_score: { type: 'number', description: 'Min sentiment score (-10 to 10)' },
|
|
138
|
-
max_score: { type: 'number', description: 'Max sentiment score (-10 to 10)' },
|
|
139
|
-
verdict: { type: 'string', enum: ['bullish', 'bearish', 'neutral'], description: 'Sentiment verdict filter' },
|
|
140
|
-
limit: { type: 'number', description: 'Result count (default: 20)' },
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
name: 'get_news',
|
|
146
|
-
description: `📰 [KOREAN NEWS - NO SCORES] Basic news without sentiment analysis. Use only when sentiment scores are not needed or for non-scored tier users.
|
|
147
|
-
|
|
148
|
-
Prefer get_news_scored over this for most use cases unless you want raw data including 0-score items.
|
|
149
|
-
|
|
150
|
-
Filter by: tag, source, date range
|
|
151
|
-
Returns: title, summary, url, tags, source`,
|
|
152
|
-
inputSchema: {
|
|
153
|
-
type: 'object',
|
|
154
|
-
properties: {
|
|
155
|
-
tag: { type: 'string', description: 'Tag filter (e.g., 삼성전자, 비트코인, 반도체)' },
|
|
156
|
-
source: { type: 'string', description: 'Source filter (e.g., 한경, 매경)' },
|
|
157
|
-
search: { type: 'string', description: 'Title search keyword' },
|
|
158
|
-
from_date: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
|
|
159
|
-
to_date: { type: 'string', description: 'End date (YYYY-MM-DD)' },
|
|
160
|
-
limit: { type: 'number', description: 'Result count (default: 20, max: 100)' },
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
// ============================================================
|
|
165
|
-
// 📈 CHART/TECHNICAL ANALYSIS TOOLS
|
|
166
|
-
// ============================================================
|
|
167
|
-
{
|
|
168
|
-
name: 'get_chart_stock',
|
|
169
|
-
description: `📈 [KOREAN STOCK CHARTS] PRIMARY tool for Korean stock technical analysis. Returns momentum scores and trend zones.
|
|
170
|
-
|
|
171
|
-
ALWAYS use for Korean stock chart/technical questions.
|
|
172
|
-
|
|
173
|
-
[IMPORTANT] You MUST use 'search_tags' first to get the correct ticker (e.g., STK005930).
|
|
174
|
-
|
|
175
|
-
Use when user asks:
|
|
176
|
-
- "차트 강한 종목" / "stocks with strong momentum"
|
|
177
|
-
- "상승 추세 종목" / "uptrending stocks"
|
|
178
|
-
- "삼성전자 차트 어때?" / "how's Samsung's chart?"
|
|
179
|
-
- "기술적 분석" / "technical analysis"
|
|
180
|
-
|
|
181
|
-
Filter by: zone (STRONG_UP/UP_ZONE/NEUTRAL/DOWN_ZONE/STRONG_DOWN), market (KOSPI/KOSDAQ)
|
|
182
|
-
Returns: ticker, name, zone, oscillator_state, 5-day scores (d0-d4), last_price
|
|
183
|
-
|
|
184
|
-
🔗 COMBINE with web_search for deeper analysis:
|
|
185
|
-
1. get_chart_stock → "삼성전자 DOWN_ZONE"
|
|
186
|
-
2. web_search "삼성전자 주가 하락 이유" → 하락 원인 파악
|
|
187
|
-
3. Provide comprehensive technical + fundamental analysis!
|
|
188
|
-
|
|
189
|
-
TIP: Use search_tags first to get ticker from stock name.`,
|
|
190
|
-
inputSchema: {
|
|
191
|
-
type: 'object',
|
|
192
|
-
properties: {
|
|
193
|
-
ticker: { type: 'string', description: 'Stock ticker (e.g., 005930 for Samsung)' },
|
|
194
|
-
market: { type: 'string', enum: ['KOSPI', 'KOSDAQ'], description: 'Market type' },
|
|
195
|
-
zone: { type: 'string', enum: ['STRONG_UP', 'UP_ZONE', 'NEUTRAL', 'DOWN_ZONE', 'STRONG_DOWN'], description: 'Chart zone filter - use this to find strong/weak stocks' },
|
|
196
|
-
limit: { type: 'number', description: 'Result count' },
|
|
197
|
-
},
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
name: 'get_chart_coin',
|
|
202
|
-
description: `🪙 [CRYPTO CHARTS] PRIMARY tool for Korean crypto (Upbit) technical analysis. Returns momentum scores and trend zones.
|
|
203
|
-
|
|
204
|
-
ALWAYS use for Korean crypto chart questions.
|
|
205
|
-
|
|
206
|
-
[IMPORTANT] You MUST use 'search_tags' first to get the correct ticker (e.g., CRY_BTC).
|
|
207
|
-
|
|
208
|
-
Use when user asks:
|
|
209
|
-
- "비트코인 차트" / "Bitcoin chart"
|
|
210
|
-
- "상승 중인 코인" / "pumping coins"
|
|
211
|
-
- "코인 기술적 분석" / "crypto technical analysis"
|
|
212
|
-
|
|
173
|
+
- "How's the market today?"
|
|
174
|
+
- "Market summary"
|
|
175
|
+
- "What's hot today?"
|
|
176
|
+
- "Daily briefing"
|
|
177
|
+
- "S&P 500 status"
|
|
178
|
+
|
|
179
|
+
Supports:
|
|
180
|
+
- Markets: US (NYSE/Nasdaq), KR (Korea), UK (LSE), JP (Tokyo), Crypto, Futures
|
|
181
|
+
- Auto-routes based on tag_code prefix (STK, USTK, LSE, JPIX, CRY, =F, ^)
|
|
182
|
+
|
|
183
|
+
Returns per asset:
|
|
184
|
+
- News stats (count, avg_sentiment, bullish/bearish ratio)
|
|
185
|
+
- Chart data (score, zone, price)
|
|
186
|
+
- Research reports (count, outlook)
|
|
187
|
+
|
|
188
|
+
TIP: If research_count > 0, use 'get_research' for full report details.`,
|
|
189
|
+
inputSchema: zodToJsonSchema(SnapshotsParamsSchema)
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'get_news_scored',
|
|
193
|
+
description: `📰 [TIER 2: NEWS DETAIL] Get global news articles with AI sentiment scores (-10 to +10).
|
|
194
|
+
|
|
195
|
+
Use for detailed news lookup when get_snapshots shows significant news activity.
|
|
196
|
+
Filter by: tag_code, verdict (bullish/bearish/neutral), score range
|
|
197
|
+
|
|
198
|
+
Supports: All global markets (US, KR, UK, JP, Crypto)
|
|
199
|
+
Response includes tag_codes for cross-referencing with charts.
|
|
200
|
+
|
|
201
|
+
TIP: Use get_snapshots first for overview, then this for detailed news on specific tags.`,
|
|
202
|
+
inputSchema: zodToJsonSchema(NewsScoredParamsSchema)
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'get_news',
|
|
206
|
+
description: `📰 [RAW NEWS - NO SCORES] Basic news without sentiment analysis. Use only when sentiment scores are not needed.
|
|
207
|
+
|
|
208
|
+
Prefer get_news_scored over this for most use cases.`,
|
|
209
|
+
inputSchema: zodToJsonSchema(NewsParamsSchema)
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'get_chart_stock',
|
|
213
|
+
description: `📈 [TIER 2: STOCK CHART DETAIL] Get detailed technical analysis with V4 scoring.
|
|
214
|
+
|
|
215
|
+
Use for: "which stocks are rising?", momentum screening, detailed chart analysis
|
|
216
|
+
Filter by: zone (STRONG_UP/UP_ZONE/NEUTRAL/DOWN_ZONE/STRONG_DOWN), market (US/KR/JP/UK)
|
|
217
|
+
|
|
218
|
+
Supports: US, KR, JP, UK markets
|
|
219
|
+
Response includes tag_code for cross-referencing with news.
|
|
220
|
+
|
|
221
|
+
TIP: Use get_snapshots first for quick overview, then this for detailed technical analysis.`,
|
|
222
|
+
inputSchema: zodToJsonSchema(ChartStockParamsSchema)
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: 'get_chart_coin',
|
|
226
|
+
description: `🪙 [TIER 2: CRYPTO CHART DETAIL] Get detailed crypto technical analysis with V4 scoring.
|
|
227
|
+
|
|
228
|
+
Use for: "how's Bitcoin?", crypto momentum screening, detailed chart analysis
|
|
213
229
|
Filter by: zone (STRONG_UP/UP_ZONE/NEUTRAL/DOWN_ZONE/STRONG_DOWN)
|
|
214
|
-
Returns: ticker, name, zone, oscillator_state, 10-candle scores (c0-c9, 12h intervals), last_price
|
|
215
230
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
zone: { type: 'string', enum: ['STRONG_UP', 'UP_ZONE', 'NEUTRAL', 'DOWN_ZONE', 'STRONG_DOWN'], description: 'Chart zone filter' },
|
|
224
|
-
limit: { type: 'number', description: 'Result count' },
|
|
225
|
-
},
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
// ============================================================
|
|
229
|
-
// 6. 컨설팅 보고서 (신규!)
|
|
230
|
-
// ============================================================
|
|
231
|
-
{
|
|
232
|
-
name: 'get_research',
|
|
233
|
-
description: `📑 [RESEARCH] Get consulting firm reports (McKinsey, BCG, etc.)
|
|
231
|
+
Supports: All major cryptocurrencies (KRW pairs)
|
|
232
|
+
Response includes tag_code for cross-referencing.`,
|
|
233
|
+
inputSchema: zodToJsonSchema(ChartCoinParamsSchema)
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
name: 'get_research',
|
|
237
|
+
description: `📑 [TIER 2: RESEARCH DETAIL] Get professional analyst reports and key insights.
|
|
234
238
|
|
|
235
|
-
Use
|
|
236
|
-
|
|
239
|
+
Use when:
|
|
240
|
+
- get_snapshots shows 'research_count > 0'
|
|
241
|
+
- User asks for: "market outlook", "sector analysis", "future trends", "investment insights"
|
|
242
|
+
- Questions about: "AI Industry outlook", "Semiconductor Cycle"
|
|
237
243
|
|
|
238
|
-
|
|
239
|
-
Includes tag_codes for cross-referencing with news/charts.
|
|
244
|
+
Filter by: tag_code, source (mckinsey, goldman, etc.)
|
|
240
245
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
limit: { type: 'number', description: 'Result count (default: 5)' },
|
|
247
|
-
source: { type: 'string', description: 'Source filter (mckinsey, goldman, etc.)' },
|
|
248
|
-
},
|
|
249
|
-
required: ['tag_code'],
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
// ============================================================
|
|
253
|
-
// 💰 FINANCIAL DATA TOOLS
|
|
254
|
-
// ============================================================
|
|
255
|
-
{
|
|
256
|
-
name: 'get_financials',
|
|
257
|
-
description: `💰 [KOREAN STOCK FUNDAMENTALS] PRIMARY tool for Korean stock financial data. Returns quarterly financial statements.
|
|
246
|
+
Returns:
|
|
247
|
+
- Full AI Summary
|
|
248
|
+
- Key Investment Insights
|
|
249
|
+
- Market Outlook (Bullish/Bearish)
|
|
250
|
+
- Tag codes for related assets
|
|
258
251
|
|
|
259
|
-
|
|
252
|
+
TIP: This tool provides *LONG-TERM* sector trends and professional analysis. Combine with news/charts for comprehensive view.`,
|
|
253
|
+
inputSchema: zodToJsonSchema(ResearchParamsSchema)
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
name: 'get_financials',
|
|
257
|
+
description: `💰 [STOCK FUNDAMENTALS] Get quarterly financial statements.
|
|
260
258
|
|
|
261
|
-
Use
|
|
262
|
-
- "삼성전자 재무제표" / "Samsung financials"
|
|
263
|
-
- "PER 낮은 종목" / "low PER stocks"
|
|
264
|
-
- "ROE 높은 기업" / "high ROE companies"
|
|
265
|
-
- "저평가 종목" / "undervalued stocks"
|
|
259
|
+
Use for: "Samsung financials", "low PER stocks", "high ROE companies", "undervalued stocks"
|
|
266
260
|
|
|
267
261
|
Returns: PER, PBR, ROE, ROA, revenue, operating_income, net_income, debt_ratio, dividend_yield
|
|
268
262
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
ticker: { type: 'string', description: 'Stock ticker (e.g., 005930)' },
|
|
276
|
-
period: { type: 'string', description: 'Quarter (e.g., 2024Q3)' },
|
|
277
|
-
market: { type: 'string', enum: ['KOSPI', 'KOSDAQ'], description: 'Market type' },
|
|
278
|
-
periods: { type: 'number', description: 'Recent N quarters (default: 4)' },
|
|
279
|
-
limit: { type: 'number', description: 'Result count' },
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
// ============================================================
|
|
284
|
-
// 📉 TREND TOOLS
|
|
285
|
-
// ============================================================
|
|
286
|
-
{
|
|
287
|
-
name: 'get_trends',
|
|
288
|
-
description: `📉 [SENTIMENT TRENDS] Get historical sentiment trend for a specific asset over time.
|
|
289
|
-
|
|
290
|
-
Use when user asks:
|
|
291
|
-
- "삼성전자 지난주 분위기" / "Samsung sentiment last week"
|
|
292
|
-
- "비트코인 추세" / "Bitcoin trend"
|
|
293
|
-
- "최근 7일간 뉴스 동향" / "news trend over 7 days"
|
|
263
|
+
Note: Currently supports KOREAN stocks only.`,
|
|
264
|
+
inputSchema: zodToJsonSchema(FinancialsParamsSchema)
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: 'match_tags',
|
|
268
|
+
description: `🏷️ [AUTO-TAG EXTRACTION] Extract stock/crypto/theme tags from any text.
|
|
294
269
|
|
|
295
|
-
|
|
296
|
-
Returns: daily news_count and avg_sentiment_score over N days
|
|
270
|
+
Use for: Analyzing what topics a news title mentions, auto-categorizing text content, finding related tags from a sentence.
|
|
297
271
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
days: { type: 'number', description: 'Recent N days (default: 7, max: 30)' },
|
|
306
|
-
},
|
|
307
|
-
required: ['tag_code'],
|
|
308
|
-
},
|
|
309
|
-
},
|
|
310
|
-
// ============================================================
|
|
311
|
-
// 🏷️ AUTO-TAGGING TOOL
|
|
312
|
-
// ============================================================
|
|
313
|
-
{
|
|
314
|
-
name: 'match_tags',
|
|
315
|
-
description: `🏷️ [AUTO-TAG EXTRACTION] Extract stock/crypto/theme tags from any text. Useful for categorizing news or analyzing what topics a text mentions.
|
|
272
|
+
Input: any text (e.g., "Nvidia HBM chip breakthrough news")
|
|
273
|
+
Returns: matched tags with confidence scores`,
|
|
274
|
+
inputSchema: zodToJsonSchema(MatchTagsParamsSchema)
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'get_trends',
|
|
278
|
+
description: `📉 [SENTIMENT TRENDS] Get historical sentiment trend for a specific asset over time.
|
|
316
279
|
|
|
317
|
-
Use
|
|
318
|
-
- Analyzing what stocks/themes a news title mentions
|
|
319
|
-
- Auto-categorizing text content
|
|
320
|
-
- Finding related tags from a sentence
|
|
280
|
+
Use for: "Samsung news trend last week", "Bitcoin sentiment this month", "recent 7-day news trend"
|
|
321
281
|
|
|
322
|
-
|
|
323
|
-
Returns:
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
{
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
|
|
282
|
+
REQUIRES tag_code - use search_tags first!
|
|
283
|
+
Returns: daily news_count and avg_sentiment over N days`,
|
|
284
|
+
inputSchema: zodToJsonSchema(TrendsParamsSchema)
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'get_available_rooms',
|
|
288
|
+
description: `📺 [REALTIME] Get active WebSocket subscription rooms for real-time data streaming.
|
|
289
|
+
|
|
290
|
+
Returns: Available room IDs for market_snapshot, global_news, and tag-specific streams.`,
|
|
291
|
+
inputSchema: zodToJsonSchema(GetAvailableRoomsSchema)
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
};
|
|
295
|
+
});
|
|
296
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
297
|
+
const { name, arguments: args } = request.params;
|
|
298
|
+
try {
|
|
299
|
+
let result;
|
|
300
|
+
switch (name) {
|
|
301
|
+
case 'get_news':
|
|
302
|
+
result = await getNews(NewsParamsSchema.parse(args));
|
|
303
|
+
break;
|
|
304
|
+
case 'get_news_scored':
|
|
305
|
+
result = await getNewsScored(NewsScoredParamsSchema.parse(args));
|
|
306
|
+
break;
|
|
307
|
+
case 'get_chart_stock':
|
|
308
|
+
result = await getChartStock(ChartStockParamsSchema.parse(args));
|
|
309
|
+
break;
|
|
310
|
+
case 'get_chart_coin':
|
|
311
|
+
result = await getChartCoin(ChartCoinParamsSchema.parse(args));
|
|
312
|
+
break;
|
|
313
|
+
case 'get_research':
|
|
314
|
+
result = await getResearch(ResearchParamsSchema.parse(args));
|
|
315
|
+
break;
|
|
316
|
+
case 'get_financials':
|
|
317
|
+
result = await getFinancials(FinancialsParamsSchema.parse(args));
|
|
318
|
+
break;
|
|
319
|
+
case 'get_snapshots':
|
|
320
|
+
result = await getSnapshots(SnapshotsParamsSchema.parse(args));
|
|
321
|
+
break;
|
|
322
|
+
case 'search_tags':
|
|
323
|
+
result = await searchTags(SearchTagsParamsSchema.parse(args));
|
|
324
|
+
break;
|
|
325
|
+
case 'match_tags':
|
|
326
|
+
result = await matchTags(MatchTagsParamsSchema.parse(args));
|
|
327
|
+
break;
|
|
328
|
+
case 'get_trends':
|
|
329
|
+
result = await getTrends(TrendsParamsSchema.parse(args));
|
|
330
|
+
break;
|
|
331
|
+
case 'get_available_rooms':
|
|
332
|
+
result = await getAvailableRooms(GetAvailableRoomsSchema.parse(args));
|
|
333
|
+
break;
|
|
334
|
+
default: throw new Error(`Unknown tool: ${name}`);
|
|
335
|
+
}
|
|
336
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
340
|
+
return { content: [{ type: 'text', text: `Error: ${errorMessage}` }], isError: true };
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
return server;
|
|
383
344
|
};
|
|
345
|
+
if (isStdio) {
|
|
346
|
+
const server = createServer();
|
|
347
|
+
const transport = new StdioServerTransport();
|
|
348
|
+
await server.connect(transport);
|
|
349
|
+
console.error('RagAlgo MCP Server started (Stdio Mode)');
|
|
350
|
+
}
|
|
351
|
+
else {
|
|
352
|
+
console.error('Starting in HTTP/SSE Mode');
|
|
353
|
+
const port = process.env.PORT || 8080;
|
|
354
|
+
const app = express();
|
|
355
|
+
app.use(cors());
|
|
356
|
+
app.use(express.json());
|
|
357
|
+
app.use((req, res, next) => {
|
|
358
|
+
console.log(`[${req.method}] ${req.originalUrl} `);
|
|
359
|
+
next();
|
|
360
|
+
});
|
|
361
|
+
// ------------------------------------------------------------------------------------------------
|
|
362
|
+
// 🚀 SMITHERY FIX: Explicit Health & Server Card Endpoints
|
|
363
|
+
// ------------------------------------------------------------------------------------------------
|
|
364
|
+
app.get('/', (req, res) => res.status(200).send('RagAlgo MCP Server Running'));
|
|
365
|
+
app.get('/health', (req, res) => res.status(200).send('OK'));
|
|
366
|
+
app.get("/.well-known/mcp-server-card", (req, res) => {
|
|
367
|
+
res.json({
|
|
368
|
+
mcp_id: "ragalgo-mcp-server",
|
|
369
|
+
name: "RagAlgo MCP Server",
|
|
370
|
+
description: "Your API key for the RagAlgo service",
|
|
371
|
+
capabilities: {
|
|
372
|
+
tools: true
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
// ------------------------------------------------------------------------------------------------
|
|
377
|
+
// SSE IMPLEMENTATION: Multi-Session Support (Map-based)
|
|
378
|
+
const server = createServer();
|
|
379
|
+
const transports = new Map();
|
|
380
|
+
app.get('/sse', async (req, res) => {
|
|
381
|
+
// FIX: Disable buffering for Railway/Nginx proxies to allow real-time SSE
|
|
382
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
383
|
+
console.log('New SSE connection initiated');
|
|
384
|
+
const sessionId = uuidv4();
|
|
385
|
+
const transport = new SSEServerTransport(`/messages?sessionId=${sessionId}`, res);
|
|
386
|
+
transports.set(sessionId, transport);
|
|
387
|
+
console.error(`Transport created for session: ${sessionId}`); // Log to stderr for Smithery visibility
|
|
388
|
+
try {
|
|
389
|
+
await server.connect(transport);
|
|
390
|
+
console.error(`Server connected to transport: ${sessionId}`);
|
|
391
|
+
// ------------------------------------------------------------------------------------------------
|
|
392
|
+
// 💓 KEEPALIVE FIX: Send explicit heartbeats for Railway/Glama
|
|
393
|
+
// MOVED AFTER connect() to avoid ERR_HTTP_HEADERS_SENT (SDK needs to write headers first)
|
|
394
|
+
// ------------------------------------------------------------------------------------------------
|
|
395
|
+
// Send immediate "ready" packet to flush buffers
|
|
396
|
+
res.write(':\n\n');
|
|
397
|
+
// Send heartbeat every 15 seconds to prevent load balancer timeouts
|
|
398
|
+
const keepAliveInterval = setInterval(() => {
|
|
399
|
+
if (res.writable) {
|
|
400
|
+
res.write(':\n\n');
|
|
401
|
+
}
|
|
402
|
+
}, 15000);
|
|
403
|
+
// ------------------------------------------------------------------------------------------------
|
|
404
|
+
// Cleanup on close (moved inside/near the interval creation scope for clarity, though logic remains same)
|
|
405
|
+
req.on('close', () => {
|
|
406
|
+
console.log(`SSE connection closed for session: ${sessionId}`);
|
|
407
|
+
clearInterval(keepAliveInterval); // Stop heartbeats
|
|
408
|
+
transports.delete(sessionId);
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
catch (error) {
|
|
412
|
+
console.error(`Error connecting server to transport ${sessionId}:`, error);
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
app.post('/messages', async (req, res) => {
|
|
416
|
+
const sessionId = req.query.sessionId;
|
|
417
|
+
console.log(`Received message for session: ${sessionId}`);
|
|
418
|
+
const transport = transports.get(sessionId);
|
|
419
|
+
if (!transport) {
|
|
420
|
+
console.error(`Session not found: ${sessionId}`);
|
|
421
|
+
res.status(404).json({ error: 'Session not found or inactive' });
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
await transport.handlePostMessage(req, res);
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
console.error(`Error handling post message for session ${sessionId}:`, error);
|
|
429
|
+
res.status(500).json({ error: 'Internal Server Error' });
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
// ------------------------------------------------------------------------------------------------
|
|
433
|
+
// 🛠️ SMITHERY FIX: Handle POST /mcp for stateless scanners
|
|
434
|
+
// ------------------------------------------------------------------------------------------------
|
|
435
|
+
app.post('/mcp', async (req, res) => {
|
|
436
|
+
// 1. TIMEOUT SAFEGUARD: Prevent hanging requests
|
|
437
|
+
const timeout = setTimeout(() => {
|
|
438
|
+
if (!res.headersSent)
|
|
439
|
+
res.status(504).send('Gateway Timeout: MCP Server processing took too long');
|
|
440
|
+
}, 10000);
|
|
441
|
+
try {
|
|
442
|
+
console.log('Received POST /mcp probe. Body:', JSON.stringify(req.body));
|
|
443
|
+
const isBatch = Array.isArray(req.body);
|
|
444
|
+
const messages = isBatch ? req.body : [req.body];
|
|
445
|
+
// 2. CHECK IF INITIALIZATION IS NEEDED
|
|
446
|
+
// If any message in the batch is 'initialize', we let the client handle it.
|
|
447
|
+
// If NO message is 'initialize', we must shim it.
|
|
448
|
+
const hasInit = messages.some(m => m.method === 'initialize');
|
|
449
|
+
const transport = new HttpPostTransport(res, isBatch);
|
|
450
|
+
const server = createServer();
|
|
451
|
+
await server.connect(transport);
|
|
452
|
+
// 3. INJECT SHIM IF NEEDED
|
|
453
|
+
if (!hasInit) {
|
|
454
|
+
console.log('[Stateless Shim] Injecting auto-initialization...');
|
|
455
|
+
const shimId = '__auto_init__';
|
|
456
|
+
transport.ignoreId(shimId);
|
|
457
|
+
// Inject 'initialize'
|
|
458
|
+
await transport.handleMessage({
|
|
459
|
+
jsonrpc: '2.0',
|
|
460
|
+
id: shimId,
|
|
461
|
+
method: 'initialize',
|
|
462
|
+
params: {
|
|
463
|
+
protocolVersion: '2024-11-05',
|
|
464
|
+
capabilities: {},
|
|
465
|
+
clientInfo: { name: 'stateless-shim', version: '1.0.0' }
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
// Inject 'notifications/initialized'
|
|
469
|
+
await transport.handleMessage({
|
|
470
|
+
jsonrpc: '2.0',
|
|
471
|
+
method: 'notifications/initialized'
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
// 4. PROCESS ACTUAL MESSAGES
|
|
475
|
+
for (const msg of messages) {
|
|
476
|
+
// Mark requests as PENDING so flush() waits for them
|
|
477
|
+
if (msg.id !== undefined) {
|
|
478
|
+
transport.markRequestPending(msg.id);
|
|
479
|
+
}
|
|
480
|
+
await transport.handleMessage(msg);
|
|
481
|
+
}
|
|
482
|
+
// 5. FLUSH RESPONSES
|
|
483
|
+
await transport.flush();
|
|
484
|
+
}
|
|
485
|
+
catch (error) {
|
|
486
|
+
console.error('Error in POST /mcp:', error);
|
|
487
|
+
if (!res.headersSent)
|
|
488
|
+
res.status(500).json({ error: 'Internal Server Error', details: String(error) });
|
|
489
|
+
}
|
|
490
|
+
finally {
|
|
491
|
+
clearTimeout(timeout);
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
// ------------------------------------------------------------------------------------------------
|
|
495
|
+
app.listen(Number(port), '0.0.0.0', () => {
|
|
496
|
+
console.error(`RagAlgo MCP Server listening on port ${port} `);
|
|
497
|
+
});
|
|
498
|
+
}
|
|
384
499
|
}
|
|
385
500
|
catch (error) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
content: [
|
|
389
|
-
{
|
|
390
|
-
type: 'text',
|
|
391
|
-
text: `Error: ${errorMessage}`,
|
|
392
|
-
},
|
|
393
|
-
],
|
|
394
|
-
isError: true,
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
// Start server
|
|
399
|
-
async function main() {
|
|
400
|
-
// Check if running in stdio mode (command line argument or specific env var)
|
|
401
|
-
const isStdio = process.argv.includes('--stdio');
|
|
402
|
-
if (isStdio) {
|
|
403
|
-
const transport = new StdioServerTransport();
|
|
404
|
-
await server.connect(transport);
|
|
405
|
-
console.error('RagAlgo MCP Server started (Stdio Mode)');
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
// SSE / HTTP Mode (Default for deployment)
|
|
409
|
-
const app = express();
|
|
410
|
-
const port = process.env.PORT || 8080;
|
|
411
|
-
app.use(cors());
|
|
412
|
-
app.use(express.json());
|
|
413
|
-
let transport = null;
|
|
414
|
-
app.get('/sse', async (req, res) => {
|
|
415
|
-
console.log('New SSE connection established');
|
|
416
|
-
transport = new SSEServerTransport('/messages', res);
|
|
417
|
-
await server.connect(transport);
|
|
418
|
-
});
|
|
419
|
-
app.post('/messages', async (req, res) => {
|
|
420
|
-
if (transport) {
|
|
421
|
-
await transport.handlePostMessage(req, res);
|
|
422
|
-
}
|
|
423
|
-
else {
|
|
424
|
-
res.status(404).json({ error: 'Session not found or connection not established' });
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
app.get('/health', (req, res) => {
|
|
428
|
-
res.status(200).json({ status: 'ok', version: '1.0.2' });
|
|
429
|
-
});
|
|
430
|
-
app.listen(port, () => {
|
|
431
|
-
console.log(`RagAlgo MCP Server listening on port ${port} (SSE Mode)`);
|
|
432
|
-
console.log(`- SSE Endpoint: http://localhost:${port}/sse`);
|
|
433
|
-
console.log(`- Health Check: http://localhost:${port}/health`);
|
|
434
|
-
});
|
|
501
|
+
console.error('FATAL STARTUP ERROR:', error);
|
|
502
|
+
process.exit(1);
|
|
435
503
|
}
|
|
436
504
|
}
|
|
437
|
-
main()
|
|
438
|
-
console.error('Fatal error:', error);
|
|
439
|
-
process.exit(1);
|
|
440
|
-
});
|
|
505
|
+
main();
|