stockmatrix-mcp 0.1.4 → 0.4.0

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 CHANGED
@@ -1,14 +1,10 @@
1
1
  # stockmatrix-mcp
2
2
 
3
- MCP server for Korean stock market theme lifecycle analysis. Provides real-time theme scores, lifecycle stages, related stocks, and news for 250+ KOSPI/KOSDAQ investment themes.
3
+ Ask about Korean stock market themes in natural conversation with AI. Track 250+ KOSPI/KOSDAQ investment themes, get daily movers, compare themes side-by-side, and see predictions all through Claude, Cursor, or any MCP-compatible AI agent.
4
4
 
5
- ## Installation
5
+ Powered by **TLI (Theme Lifecycle Index)** — a Bayesian-optimized scoring algorithm combining search interest, news momentum, market volatility, and stock activity into a 0-100 score with lifecycle stage classification.
6
6
 
7
- ```bash
8
- npx -y stockmatrix-mcp
9
- ```
10
-
11
- ## Configuration
7
+ ## Quick Start
12
8
 
13
9
  ### Claude Desktop
14
10
 
@@ -25,9 +21,9 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
25
21
  }
26
22
  ```
27
23
 
28
- ### Cursor
24
+ ### Cursor / Windsurf
29
25
 
30
- Add to `.cursor/mcp.json`:
26
+ Add to `.cursor/mcp.json` or `.windsurf/mcp.json`:
31
27
 
32
28
  ```json
33
29
  {
@@ -40,7 +36,7 @@ Add to `.cursor/mcp.json`:
40
36
  }
41
37
  ```
42
38
 
43
- ### VS Code
39
+ ### VS Code / Claude Code
44
40
 
45
41
  Add to `.vscode/mcp.json`:
46
42
 
@@ -55,71 +51,133 @@ Add to `.vscode/mcp.json`:
55
51
  }
56
52
  ```
57
53
 
54
+ ## Try Asking
55
+
56
+ After setup, just ask in natural language:
57
+
58
+ | Prompt | What happens |
59
+ |--------|-------------|
60
+ | "요즘 뜨는 테마 TOP 5" | Top 5 themes ranked by TLI score |
61
+ | "오늘 한국 테마 시장 요약해줘" | AI-optimized market overview |
62
+ | "어제 대비 가장 많이 오른 테마는?" | Daily score movers and stage transitions |
63
+ | "AI 관련 테마 찾아줘" | Search AI-related themes |
64
+ | "반도체 vs 2차전지 비교해줘" | Side-by-side theme comparison |
65
+ | "앞으로 오를 테마 알려줘" | Rising predictions with analog evidence |
66
+ | "삼성전자가 속한 테마" | Stock-to-theme lookup (auto-detects 6-digit codes) |
67
+ | "방산 테마 상세 정보" | Score breakdown, stocks, news, comparisons |
68
+ | "반도체 테마 최근 한달 추세" | 30-day score history |
69
+ | "TLI 점수는 어떻게 계산돼?" | Algorithm methodology (tip: use `section=scoring` to save tokens) |
70
+ | "What are the hottest stock themes in Korea?" | Works in English too |
71
+
58
72
  ## Available Tools
59
73
 
60
74
  ### `get_theme_ranking`
61
-
62
- 한국 주식시장 테마 생명주기 랭킹을 조회합니다. Retrieves theme lifecycle rankings by stage.
75
+ Get theme rankings by lifecycle stage with limit and sort options.
63
76
 
64
77
  | Parameter | Type | Required | Description |
65
78
  |-----------|------|----------|-------------|
66
- | `stage` | string | No | Filter by stage: `emerging`, `growth`, `peak`, `decline`, `reigniting` |
79
+ | `stage` | string | No | `emerging` / `growth` / `peak` / `decline` / `reigniting` |
80
+ | `limit` | number | No | Results per stage (1-50, default: 10) |
81
+ | `sort` | string | No | `score` / `change7d` / `newsCount7d` (default: score) |
67
82
 
68
- ### `get_theme_detail`
83
+ ### `get_market_summary`
84
+ Get an AI-optimized market overview with top themes (includes themeId for chaining).
69
85
 
70
- 특정 테마의 상세 정보를 조회합니다. Gets detailed theme info including score, stage, related stocks, and news.
86
+ ### `get_theme_changes`
87
+ Get daily or weekly score movers, stage transitions, and newly emerging themes.
71
88
 
72
89
  | Parameter | Type | Required | Description |
73
90
  |-----------|------|----------|-------------|
74
- | `theme_id` | string (UUID) | Yes | Theme UUID |
91
+ | `period` | string | No | `1d` (default) / `7d` |
75
92
 
76
- ### `get_theme_history`
93
+ ### `compare_themes`
94
+ Compare 2-5 themes side-by-side with scores, stocks, sparklines, and similarity.
77
95
 
78
- 테마의 최근 30일 점수 이력을 조회합니다. Returns 30-day score history for a theme.
96
+ | Parameter | Type | Required | Description |
97
+ |-----------|------|----------|-------------|
98
+ | `theme_ids` | string[] | Yes | Array of 2-5 theme UUIDs |
99
+
100
+ ### `get_predictions`
101
+ Get themes predicted to rise, peak, or cool based on historical analog matching.
79
102
 
80
103
  | Parameter | Type | Required | Description |
81
104
  |-----------|------|----------|-------------|
82
- | `theme_id` | string (UUID) | Yes | Theme UUID |
105
+ | `phase` | string | No | `rising` / `hot` / `cooling` (default: all) |
83
106
 
84
107
  ### `search_themes`
108
+ Search themes by keyword, stock name, or stock code.
109
+
110
+ | Parameter | Type | Required | Description |
111
+ |-----------|------|----------|-------------|
112
+ | `query` | string | Yes | e.g. `"AI"`, `"반도체"`, `"삼성전자"`, `"005930"` |
113
+
114
+ ### `search_stocks`
115
+ Search stocks by company name or 6-digit code, with related theme preview. Automatically performs stock-to-theme lookup for 6-digit codes.
116
+
117
+ | Parameter | Type | Required | Description |
118
+ |-----------|------|----------|-------------|
119
+ | `query` | string | Yes | e.g. `"삼성전자"`, `"SK하이닉스"`, `"005930"` |
85
120
 
86
- 테마를 검색합니다. Searches themes by name in Korean or English.
121
+ ### `get_theme_detail`
122
+ Get detailed analysis: score breakdown (4 components), stage, prediction, stocks, news, comparisons.
87
123
 
88
124
  | Parameter | Type | Required | Description |
89
125
  |-----------|------|----------|-------------|
90
- | `query` | string | Yes | Search query (e.g. `"AI"`, `"반도체"`, `"삼성전자"`) |
126
+ | `theme_id` | string (UUID) | Yes | Theme UUID from ranking or search |
127
+
128
+ ### `get_theme_history`
129
+ Get 30-day score history for trend analysis.
91
130
 
92
- ### `get_stock_theme`
131
+ | Parameter | Type | Required | Description |
132
+ |-----------|------|----------|-------------|
133
+ | `theme_id` | string (UUID) | Yes | Theme UUID |
93
134
 
94
- 특정 종목이 속한 테마를 조회합니다. Finds themes related to a specific stock code.
135
+ ### `get_methodology`
136
+ Get TLI algorithm documentation — scoring, stages, stabilization, comparison, prediction, data sources, and more.
95
137
 
96
138
  | Parameter | Type | Required | Description |
97
139
  |-----------|------|----------|-------------|
98
- | `symbol` | string | Yes | 6-digit Korean stock code (e.g. `"005930"` for Samsung) |
140
+ | `section` | string | No | `scoring` / `stages` / `comparison` / `prediction` / `all` (default: all) |
99
141
 
100
- ## Environment Variables
142
+ > Tip: Use `section=scoring` to get just the scoring algorithm and save context tokens.
101
143
 
102
- | Variable | Default | Description |
103
- |----------|---------|-------------|
104
- | `STOCKMATRIX_API_URL` | `https://stockmatrix.co.kr` | API base URL |
144
+ ## Scoring Algorithm
105
145
 
106
- ## Examples
146
+ TLI scores (0-100) are a weighted sum of 4 components, optimized via Bayesian Optimization:
107
147
 
108
- **"What are the hottest stock themes in Korea right now?"**
109
- → `get_theme_ranking` with `stage: "growth"`
148
+ | Component | Weight | Source |
149
+ |-----------|--------|--------|
150
+ | Search Interest | 30.4% | Naver DataLab |
151
+ | News Momentum | 36.6% | Naver News |
152
+ | Volatility | 10.4% | Interest time-series |
153
+ | Stock Activity | 22.6% | Naver Finance |
110
154
 
111
- **"Tell me about the semiconductor theme"**
112
- → `search_themes` with `query: "반도체"` → `get_theme_detail` with the returned theme ID
155
+ Scores are stabilized through **Cautious Decay** (3-signal majority vote), **Bollinger Band Clamp** (limits daily change), and **Age-adaptive EMA** (newer themes react faster).
113
156
 
114
- **"What themes is Samsung Electronics part of?"**
115
- → `get_stock_theme` with `symbol: "005930"`
157
+ ## Lifecycle Stages
116
158
 
117
- ## Data Sources
159
+ ```
160
+ Dormant -> Emerging -> Growth -> Peak -> Decline -> Dormant
161
+ |
162
+ Reigniting
163
+ ```
164
+
165
+ Stage transitions require 2 consecutive days of the same candidate (hysteresis) and follow Markov transition constraints.
166
+
167
+ ## Data Coverage
168
+
169
+ - **250+ themes** across KOSPI & KOSDAQ
170
+ - **Daily updates** — scores, news, stock mappings
171
+ - **Stock lookup** by company name or 6-digit code
172
+ - **AI market summary** for first-call overview
173
+ - **Predictions** with historical analog matching
174
+ - **Sources**: Naver DataLab, Naver Finance, Naver News
118
175
 
119
- - Naver DataLab search interest trends
120
- - Naver Finance theme stock data
121
- - Naver News article collection
122
- - KRX (Korea Exchange) market data
176
+ ## Configuration
177
+
178
+ | Variable | Default | Description |
179
+ |----------|---------|-------------|
180
+ | `STOCKMATRIX_API_URL` | `https://stockmatrix.co.kr` | Override API base URL |
123
181
 
124
182
  ## License
125
183
 
@@ -0,0 +1,7 @@
1
+ interface Cache {
2
+ get: <T = unknown>(key: string) => T | undefined;
3
+ set: <T = unknown>(key: string, value: T, ttlMs: number) => void;
4
+ readonly size: number;
5
+ }
6
+ export declare const createCache: (maxSize?: number) => Cache;
7
+ export {};
package/dist/cache.js ADDED
@@ -0,0 +1,36 @@
1
+ export const createCache = (maxSize = 50) => {
2
+ const store = new Map();
3
+ const evictExpired = () => {
4
+ const now = Date.now();
5
+ for (const [key, entry] of store) {
6
+ if (entry.expiresAt <= now) {
7
+ store.delete(key);
8
+ }
9
+ }
10
+ };
11
+ return {
12
+ get(key) {
13
+ const entry = store.get(key);
14
+ if (!entry)
15
+ return undefined;
16
+ if (entry.expiresAt <= Date.now()) {
17
+ store.delete(key);
18
+ return undefined;
19
+ }
20
+ return entry.value;
21
+ },
22
+ set(key, value, ttlMs) {
23
+ if (store.size >= maxSize) {
24
+ evictExpired();
25
+ }
26
+ if (store.size >= maxSize) {
27
+ const oldest = store.keys().next().value;
28
+ store.delete(oldest);
29
+ }
30
+ store.set(key, { value, expiresAt: Date.now() + ttlMs });
31
+ },
32
+ get size() {
33
+ return store.size;
34
+ },
35
+ };
36
+ };
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { createRequire } from 'node:module';
5
+ import { registerGetThemeRanking } from './tools/get-theme-ranking.js';
6
+ import { registerGetThemeDetail } from './tools/get-theme-detail.js';
7
+ import { registerGetThemeHistory } from './tools/get-theme-history.js';
8
+ import { registerSearchThemes } from './tools/search-themes.js';
9
+ import { registerSearchStocks } from './tools/search-stocks.js';
10
+ import { registerGetMarketSummary } from './tools/get-market-summary.js';
11
+ import { registerGetMethodology } from './tools/get-methodology.js';
12
+ import { registerGetThemeChanges } from './tools/get-theme-changes.js';
13
+ import { registerCompareThemes } from './tools/compare-themes.js';
14
+ import { registerGetPredictions } from './tools/get-predictions.js';
15
+ const require = createRequire(import.meta.url);
16
+ const { version } = require('../package.json');
17
+ const server = new McpServer({
18
+ name: 'stockmatrix-mcp',
19
+ version,
20
+ });
21
+ registerGetThemeRanking(server);
22
+ registerGetThemeDetail(server);
23
+ registerGetThemeHistory(server);
24
+ registerSearchThemes(server);
25
+ registerSearchStocks(server);
26
+ registerGetMarketSummary(server);
27
+ registerGetMethodology(server);
28
+ registerGetThemeChanges(server);
29
+ registerCompareThemes(server);
30
+ registerGetPredictions(server);
31
+ const main = async () => {
32
+ const transport = new StdioServerTransport();
33
+ await server.connect(transport);
34
+ console.error(`StockMatrix MCP server v${version} running on stdio`);
35
+ };
36
+ main().catch((error) => {
37
+ console.error('Fatal error in main():', error);
38
+ process.exit(1);
39
+ });
@@ -1,3 +1,7 @@
1
- export declare const fetchApi: <T = unknown>(path: string, params?: Record<string, string>) => Promise<T>;
2
- export declare const formatResult: (data: unknown) => string;
1
+ import type { ZodType } from 'zod';
2
+ export declare const fetchApi: <T = unknown>(path: string, params?: Record<string, string>, schema?: ZodType<T>) => Promise<T>;
3
+ /** JSON 직렬화 with optional context header for AI agents */
4
+ export declare const formatResult: (data: unknown, context?: string) => string;
3
5
  export declare const formatError: (error: unknown) => string;
6
+ /** 빈 결과에 가이던스 메시지를 포함하는 포맷터 */
7
+ export declare const formatEmptyResult: (context: string, guidance: string) => string;
@@ -1,5 +1,11 @@
1
+ import { createRequire } from 'node:module';
2
+ import { createCache } from './cache.js';
3
+ const require = createRequire(import.meta.url);
4
+ const { version } = require('../package.json');
5
+ const apiCache = createCache(50);
6
+ const CACHE_TTL_MS = 3_600_000; // 1 hour
1
7
  const BASE_URL = process.env.STOCKMATRIX_API_URL || 'https://stockmatrix.co.kr';
2
- const MCP_USER_AGENT = `stockmatrix-mcp/0.1.3`;
8
+ const MCP_USER_AGENT = `stockmatrix-mcp/${version}`;
3
9
  // 시작 시 URL 유효성 검증
4
10
  try {
5
11
  new URL(BASE_URL);
@@ -23,13 +29,18 @@ const isRetryable = (error) => {
23
29
  return false;
24
30
  };
25
31
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
26
- export const fetchApi = async (path, params) => {
32
+ export const fetchApi = async (path, params, schema) => {
27
33
  const url = new URL(path, BASE_URL);
28
34
  if (params) {
29
35
  for (const [key, value] of Object.entries(params)) {
30
36
  url.searchParams.set(key, value);
31
37
  }
32
38
  }
39
+ const cacheKey = url.toString();
40
+ const cached = apiCache.get(cacheKey);
41
+ if (cached !== undefined) {
42
+ return schema ? schema.parse(cached) : cached;
43
+ }
33
44
  let lastError;
34
45
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
35
46
  if (attempt > 0) {
@@ -64,9 +75,13 @@ export const fetchApi = async (path, params) => {
64
75
  if (!wrapped.success) {
65
76
  throw new Error(wrapped.error?.message || 'API returned unsuccessful response');
66
77
  }
67
- return wrapped.data;
78
+ const result = schema ? schema.parse(wrapped.data) : wrapped.data;
79
+ apiCache.set(cacheKey, result, CACHE_TTL_MS);
80
+ return result;
68
81
  }
69
- return json;
82
+ const rawResult = schema ? schema.parse(json) : json;
83
+ apiCache.set(cacheKey, rawResult, CACHE_TTL_MS);
84
+ return rawResult;
70
85
  }
71
86
  catch (error) {
72
87
  lastError = error;
@@ -78,8 +93,18 @@ export const fetchApi = async (path, params) => {
78
93
  }
79
94
  throw lastError;
80
95
  };
81
- export const formatResult = (data) => JSON.stringify(data, null, 2);
96
+ /** JSON 직렬화 with optional context header for AI agents */
97
+ export const formatResult = (data, context) => {
98
+ if (context) {
99
+ return `${context}\n\n${JSON.stringify(data, null, 2)}`;
100
+ }
101
+ return JSON.stringify(data, null, 2);
102
+ };
82
103
  export const formatError = (error) => {
83
104
  const message = error instanceof Error ? error.message : String(error);
84
105
  return `Error: ${message}`;
85
106
  };
107
+ /** 빈 결과에 가이던스 메시지를 포함하는 포맷터 */
108
+ export const formatEmptyResult = (context, guidance) => {
109
+ return `${context}\n\n${JSON.stringify([], null, 2)}\n\n${guidance}`;
110
+ };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
- #!/usr/bin/env node
2
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
2
  export declare const createSandboxServer: () => McpServer;
package/dist/index.js CHANGED
@@ -1,31 +1,32 @@
1
- #!/usr/bin/env node
1
+ import { createRequire } from 'node:module';
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
3
  import { registerGetThemeRanking } from './tools/get-theme-ranking.js';
5
4
  import { registerGetThemeDetail } from './tools/get-theme-detail.js';
6
5
  import { registerGetThemeHistory } from './tools/get-theme-history.js';
7
6
  import { registerSearchThemes } from './tools/search-themes.js';
8
- import { registerGetStockTheme } from './tools/get-stock-theme.js';
7
+ import { registerSearchStocks } from './tools/search-stocks.js';
8
+ import { registerGetMarketSummary } from './tools/get-market-summary.js';
9
+ import { registerGetMethodology } from './tools/get-methodology.js';
10
+ import { registerGetThemeChanges } from './tools/get-theme-changes.js';
11
+ import { registerCompareThemes } from './tools/compare-themes.js';
12
+ import { registerGetPredictions } from './tools/get-predictions.js';
13
+ const require = createRequire(import.meta.url);
14
+ const { version } = require('../package.json');
9
15
  const createServer = () => {
10
16
  const s = new McpServer({
11
17
  name: 'stockmatrix-mcp',
12
- version: '0.1.3',
18
+ version,
13
19
  });
14
20
  registerGetThemeRanking(s);
15
21
  registerGetThemeDetail(s);
16
22
  registerGetThemeHistory(s);
17
23
  registerSearchThemes(s);
18
- registerGetStockTheme(s);
24
+ registerSearchStocks(s);
25
+ registerGetMarketSummary(s);
26
+ registerGetMethodology(s);
27
+ registerGetThemeChanges(s);
28
+ registerCompareThemes(s);
29
+ registerGetPredictions(s);
19
30
  return s;
20
31
  };
21
32
  export const createSandboxServer = () => createServer();
22
- const server = createServer();
23
- const main = async () => {
24
- const transport = new StdioServerTransport();
25
- await server.connect(transport);
26
- console.error('StockMatrix MCP server running on stdio');
27
- };
28
- main().catch((error) => {
29
- console.error('Fatal error in main():', error);
30
- process.exit(1);
31
- });
@@ -1,2 +1,2 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- export declare const registerGetStockTheme: (server: McpServer) => void;
2
+ export declare const registerCompareThemes: (server: McpServer) => void;
@@ -0,0 +1,40 @@
1
+ import { z } from 'zod';
2
+ import { fetchApi, formatResult, formatError } from '../fetch-helper.js';
3
+ const CONTEXT = `[StockMatrix Theme Comparison]
4
+ Compare 2–5 Korean stock market themes side-by-side.
5
+ Shows each theme's current TLI score, lifecycle stage, 7-day sparkline,
6
+ pairwise similarity (from comparison algorithm), overlapping stocks, and any warnings.
7
+ Use when the user asks to compare or contrast multiple themes.`;
8
+ export const registerCompareThemes = (server) => {
9
+ server.tool('compare_themes', `Compare 2–5 Korean stock market themes side-by-side with lifecycle scores, similarity, and overlapping stocks.
10
+
11
+ Use when the user asks:
12
+ - Compare semiconductor and AI themes
13
+ - How similar are these themes?
14
+ - 반도체 vs AI 테마 비교, 테마 간 유사도
15
+ - Which theme is stronger right now?
16
+ - Do these themes share the same stocks?
17
+
18
+ Returns each theme's score/stage/sparkline, pairwise similarity scores, and overlapping stocks.`, {
19
+ theme_ids: z
20
+ .array(z.string().uuid())
21
+ .min(2)
22
+ .max(5)
23
+ .describe('Array of 2–5 theme UUIDs to compare'),
24
+ }, async ({ theme_ids }) => {
25
+ try {
26
+ const data = await fetchApi('/api/tli/compare', {
27
+ ids: theme_ids.join(','),
28
+ });
29
+ return {
30
+ content: [{ type: 'text', text: formatResult(data, CONTEXT) }],
31
+ };
32
+ }
33
+ catch (error) {
34
+ return {
35
+ content: [{ type: 'text', text: formatError(error) }],
36
+ isError: true,
37
+ };
38
+ }
39
+ });
40
+ };
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare const registerGetMarketSummary: (server: McpServer) => void;
@@ -0,0 +1,30 @@
1
+ import { fetchApi, formatResult, formatError } from '../fetch-helper.js';
2
+ const CONTEXT = `[StockMatrix Market Summary]
3
+ High-level Korean stock market theme briefing for AI agents.
4
+ Use as a first call when the user asks what is hot, what the market looks like today, or wants a concise overview before drilling into a theme.
5
+ Includes stage distribution, top themes, market coverage, endpoint references, and citation/disclaimer metadata.
6
+ Each theme in the response includes themeId for chaining to get_theme_detail.`;
7
+ export const registerGetMarketSummary = (server) => {
8
+ server.tool('get_market_summary', `Get an AI-optimized summary of the Korean stock theme market.
9
+
10
+ Use when the user asks:
11
+ - What's happening in Korean stock themes right now?
12
+ - Give me a market overview before drilling down
13
+ - 오늘 한국 테마 시장 요약
14
+ - 현재 뜨는 테마와 시장 분포를 한 번에 보고 싶어
15
+
16
+ Returns a concise market overview, stage distribution, top themes, endpoint references, citation metadata, and disclaimer text.`, {}, async () => {
17
+ try {
18
+ const data = await fetchApi('/api/ai/summary');
19
+ return {
20
+ content: [{ type: 'text', text: formatResult(data, CONTEXT) }],
21
+ };
22
+ }
23
+ catch (error) {
24
+ return {
25
+ content: [{ type: 'text', text: formatError(error) }],
26
+ isError: true,
27
+ };
28
+ }
29
+ });
30
+ };
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare const registerGetMethodology: (server: McpServer) => void;
@@ -0,0 +1,63 @@
1
+ import { z } from 'zod';
2
+ import { fetchApi, formatResult } from '../fetch-helper.js';
3
+ const SECTIONS = [
4
+ 'scoring',
5
+ 'stabilization',
6
+ 'stages',
7
+ 'comparison',
8
+ 'prediction',
9
+ 'data_sources',
10
+ 'update_schedule',
11
+ 'runtime',
12
+ 'data_flow',
13
+ 'database_tables',
14
+ 'limitations',
15
+ 'all',
16
+ ];
17
+ const METHODOLOGY_FALLBACK = {
18
+ fallback: true,
19
+ scoring: {
20
+ range: '0-100',
21
+ components: [
22
+ { name: 'interest', weight: '30.4%' },
23
+ { name: 'newsMomentum', weight: '36.6%' },
24
+ { name: 'volatility', weight: '10.4%' },
25
+ { name: 'activity', weight: '22.6%' },
26
+ ],
27
+ },
28
+ disclaimer: 'Full methodology unavailable — showing cached summary. Try again later.',
29
+ };
30
+ const CONTEXT = `[StockMatrix TLI Methodology]
31
+ Comprehensive documentation of the TLI (Theme Lifecycle Index) algorithm — scoring, stages, stabilization, comparison, prediction, data sources, pipeline, and database schema.`;
32
+ export const registerGetMethodology = (server) => {
33
+ server.tool('get_methodology', `Get the TLI (Theme Lifecycle Index) algorithm methodology — how scores, stages, and predictions work.
34
+
35
+ Use when the user asks:
36
+ - How are theme scores calculated?
37
+ - What do the lifecycle stages mean?
38
+ - How does the prediction work?
39
+ - What data sources, schedules, runtime pipeline, or database tables power TLI?
40
+ - TLI 알고리즘 설명, 점수 산출 방식, 단계 판정 기준
41
+ - What data sources are used?
42
+ - TLI 수집 파이프라인, 업데이트 주기, 비교 파이프라인, 데이터 테이블
43
+
44
+ Returns structured documentation of the scoring algorithm, data collection pipeline, runtime orchestration, database tables, stage determination, stabilization techniques, comparison analysis, and prediction methodology.`, {
45
+ section: z
46
+ .enum(SECTIONS)
47
+ .optional()
48
+ .describe('Specific section: scoring, stabilization, stages, comparison, prediction, data_sources, update_schedule, runtime, data_flow, database_tables, limitations, or all (default: all)'),
49
+ }, async ({ section }) => {
50
+ try {
51
+ const params = section ? { section } : undefined;
52
+ const data = await fetchApi('/api/tli/methodology', params);
53
+ return {
54
+ content: [{ type: 'text', text: formatResult(data, CONTEXT) }],
55
+ };
56
+ }
57
+ catch {
58
+ return {
59
+ content: [{ type: 'text', text: formatResult(METHODOLOGY_FALLBACK, CONTEXT) }],
60
+ };
61
+ }
62
+ });
63
+ };
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare const registerGetPredictions: (server: McpServer) => void;
@@ -0,0 +1,45 @@
1
+ import { z } from 'zod';
2
+ import { fetchApi, formatResult, formatError } from '../fetch-helper.js';
3
+ const CONTEXT = `[StockMatrix Theme Predictions]
4
+ Lifecycle phase predictions for Korean stock themes based on analog comparison forecasting.
5
+ Shows which themes are rising, hot, or cooling with confidence levels and analog evidence.
6
+ Use to identify emerging opportunities (rising) or themes past peak (cooling).
7
+ Chain with get_theme_detail(themeId) for deeper analysis of any predicted theme.`;
8
+ export const registerGetPredictions = (server) => {
9
+ server.tool('get_predictions', `Get lifecycle phase predictions for Korean stock themes.
10
+
11
+ Use when the user asks:
12
+ - Which themes are rising / about to peak / cooling down?
13
+ - Show me predicted hot themes
14
+ - 상승세 테마 예측, 하락 전환 예상 테마
15
+ - 테마 생명주기 예측 결과를 보고 싶어
16
+
17
+ Returns themes with predicted phase (rising/hot/cooling), confidence, expected peak day, and top analog evidence.`, {
18
+ phase: z
19
+ .enum(['rising', 'hot', 'cooling'])
20
+ .optional()
21
+ .describe('Filter by predicted phase: rising (ascending), hot (at peak), cooling (declining)'),
22
+ }, async ({ phase }) => {
23
+ try {
24
+ const params = {};
25
+ if (phase)
26
+ params.phase = phase;
27
+ const data = await fetchApi('/api/tli/predictions', params);
28
+ if (!data.themes || data.themes.length === 0) {
29
+ const guidance = data.guidance || 'Prediction data not yet available.';
30
+ return {
31
+ content: [{ type: 'text', text: formatResult({ ...data, guidance }, CONTEXT) }],
32
+ };
33
+ }
34
+ return {
35
+ content: [{ type: 'text', text: formatResult(data, CONTEXT) }],
36
+ };
37
+ }
38
+ catch (error) {
39
+ return {
40
+ content: [{ type: 'text', text: formatError(error) }],
41
+ isError: true,
42
+ };
43
+ }
44
+ });
45
+ };
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare const registerGetThemeChanges: (server: McpServer) => void;
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ import { fetchApi, formatResult, formatError, formatEmptyResult } from '../fetch-helper.js';
3
+ const CONTEXT = `[StockMatrix Theme Changes]
4
+ Daily or weekly score changes. period=1d means vs previous day, 7d means vs 7 days ago.
5
+ movers: rising (score increased) and falling (score decreased), sorted by magnitude.
6
+ stageTransitions: themes that changed lifecycle stage.
7
+ newlyEmerging: themes that entered Emerging stage.
8
+ Use theme IDs with get_theme_detail for deeper analysis.`;
9
+ const isEmpty = (data) => data.movers.rising.length === 0 &&
10
+ data.movers.falling.length === 0 &&
11
+ data.stageTransitions.length === 0 &&
12
+ data.newlyEmerging.length === 0;
13
+ export const registerGetThemeChanges = (server) => {
14
+ server.tool('get_theme_changes', `Get recent score changes and stage transitions for Korean stock themes.
15
+
16
+ Use when the user asks:
17
+ - What themes changed the most today/this week?
18
+ - Which themes are rising or falling?
19
+ - Any stage transitions recently?
20
+ - 오늘 테마 변동, 급등/급락 테마
21
+ - 이번 주 생명주기 단계 변화한 테마
22
+ - 새로 떠오르는 테마 있어?
23
+
24
+ Returns movers (rising/falling by score change), stage transitions, and newly emerging themes.`, {
25
+ period: z
26
+ .enum(['1d', '7d'])
27
+ .optional()
28
+ .describe('Comparison period: 1d = vs yesterday (default), 7d = vs 7 days ago'),
29
+ }, async ({ period }) => {
30
+ try {
31
+ const data = await fetchApi('/api/tli/changes', {
32
+ period: period || '1d',
33
+ });
34
+ if (isEmpty(data)) {
35
+ return {
36
+ content: [
37
+ {
38
+ type: 'text',
39
+ text: formatEmptyResult(CONTEXT, 'No significant changes detected for this period. Try period=7d for a wider window, or use get_theme_ranking for current standings.'),
40
+ },
41
+ ],
42
+ };
43
+ }
44
+ return {
45
+ content: [{ type: 'text', text: formatResult(data, CONTEXT) }],
46
+ };
47
+ }
48
+ catch (error) {
49
+ return {
50
+ content: [{ type: 'text', text: formatError(error) }],
51
+ isError: true,
52
+ };
53
+ }
54
+ });
55
+ };
@@ -1,16 +1,34 @@
1
1
  import { z } from 'zod';
2
- import { fetchApi, formatResult, formatError } from '../fetch-helper.js';
2
+ import { fetchApi, formatResult, formatError, formatEmptyResult } from '../fetch-helper.js';
3
+ const CONTEXT = `[StockMatrix Theme Detail]
4
+ Score components (Bayesian-optimized weights):
5
+ - interest (30.4%): Naver DataLab search volume — 7-day avg vs 30-day baseline
6
+ - newsMomentum (36.6%): Naver News article count — weekly change rate
7
+ - volatility (10.4%): Interest time-series standard deviation
8
+ - activity (22.6%): Related stock price changes, volume intensity, data coverage
9
+ Score stabilization: Cautious Decay (3-signal majority vote prevents false drops) → Bollinger Clamp → Age-adaptive EMA smoothing.
10
+ Stage transitions use Markov constraints + 2-day hysteresis.
11
+ Comparisons: 3-Pillar similarity (feature + curve + keyword) with Mutual Rank, threshold ≥ 0.40.`;
3
12
  export const registerGetThemeDetail = (server) => {
4
- server.tool('get_theme_detail', '특정 테마의 상세 정보를 조회합니다. 테마 점수, 생명주기 단계, 관련 종목, 뉴스 등을 포함합니다.', {
13
+ server.tool('get_theme_detail', `Get detailed analysis for a specific Korean stock theme.
14
+
15
+ Returns: TLI score breakdown (4 components with weights), lifecycle stage, 24h/7d score changes, prediction outlook, top related stocks with price changes, latest news headlines, and similar theme comparisons.
16
+
17
+ Use after get_theme_ranking or search_themes to drill into a specific theme. Answers: "tell me more about this theme", "what stocks are in this theme", "테마 상세 정보", "관련 종목 알려줘", "이 테마 전망".`, {
5
18
  theme_id: z
6
19
  .string()
7
20
  .uuid('Theme ID must be a valid UUID')
8
- .describe('Theme UUID (e.g. "a1b2c3d4-e5f6-7890-abcd-ef1234567890")'),
21
+ .describe('Theme UUID from ranking or search results'),
9
22
  }, async ({ theme_id }) => {
10
23
  try {
11
24
  const data = await fetchApi(`/api/tli/themes/${theme_id}`);
25
+ if (!data) {
26
+ return {
27
+ content: [{ type: 'text', text: formatEmptyResult(CONTEXT, `Theme not found for ID "${theme_id}". Use search_themes to find valid theme IDs.`) }],
28
+ };
29
+ }
12
30
  return {
13
- content: [{ type: 'text', text: formatResult(data) }],
31
+ content: [{ type: 'text', text: formatResult(data, CONTEXT) }],
14
32
  };
15
33
  }
16
34
  catch (error) {
@@ -1,16 +1,32 @@
1
1
  import { z } from 'zod';
2
- import { fetchApi, formatResult, formatError } from '../fetch-helper.js';
2
+ import { fetchApi, formatResult, formatError, formatEmptyResult } from '../fetch-helper.js';
3
+ const CONTEXT = `[StockMatrix Theme History — 30-day]
4
+ Daily TLI scores with stage transitions. Use to identify:
5
+ - Trend direction: rising scores = growing interest, falling = declining
6
+ - Stage transitions: stage changes require 2 consecutive days (hysteresis)
7
+ - Score stability: Cautious Decay prevents false drops from data gaps
8
+ - Momentum: EMA smoothing adapts to theme age (newer themes react faster)
9
+ Scores are 0-100, computed from interest + news + volatility + activity.`;
3
10
  export const registerGetThemeHistory = (server) => {
4
- server.tool('get_theme_history', '테마의 최근 30 점수 이력을 조회합니다. 생명주기 추이를 파악할 수 있습니다.', {
11
+ server.tool('get_theme_history', `Get 30-day score history for a Korean stock theme.
12
+
13
+ Returns daily TLI scores and stage transitions for trend analysis. Use when the user asks about theme momentum over time, whether a theme is gaining or losing interest, or wants to see historical trajectory.
14
+
15
+ Answers: "이 테마 추세가 어때?", "최근 한달 흐름", "is this theme gaining or losing momentum?", "show me the trend".`, {
5
16
  theme_id: z
6
17
  .string()
7
18
  .uuid('Theme ID must be a valid UUID')
8
- .describe('Theme UUID (e.g. "a1b2c3d4-e5f6-7890-abcd-ef1234567890")'),
19
+ .describe('Theme UUID from ranking or search results'),
9
20
  }, async ({ theme_id }) => {
10
21
  try {
11
22
  const data = await fetchApi(`/api/tli/themes/${theme_id}/history`);
23
+ if (!data || (Array.isArray(data) && data.length === 0)) {
24
+ return {
25
+ content: [{ type: 'text', text: formatEmptyResult(CONTEXT, `No history data found for theme "${theme_id}". The theme may be too new or inactive.`) }],
26
+ };
27
+ }
12
28
  return {
13
- content: [{ type: 'text', text: formatResult(data) }],
29
+ content: [{ type: 'text', text: formatResult(data, CONTEXT) }],
14
30
  };
15
31
  }
16
32
  catch (error) {
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { fetchApi, formatResult, formatError } from '../fetch-helper.js';
2
+ import { fetchApi, formatResult, formatError, formatEmptyResult } from '../fetch-helper.js';
3
3
  const VALID_STAGES = [
4
4
  'emerging',
5
5
  'growth',
@@ -7,29 +7,72 @@ const VALID_STAGES = [
7
7
  'decline',
8
8
  'reigniting',
9
9
  ];
10
+ const STAGE_DESCRIPTIONS = {
11
+ emerging: 'score < growth threshold, early interest signal',
12
+ growth: 'score ≥ 40 with stable/rising trend — expanding interest',
13
+ peak: 'score ≥ 71 or high score + news surge — maximum attention',
14
+ decline: 'falling trend + score drop + declining news coverage',
15
+ reigniting: 'previously declined theme showing renewed growth',
16
+ };
17
+ const CONTEXT = `[StockMatrix TLI Ranking]
18
+ Scores: 0-100 (Bayesian-optimized weighted sum of 4 components: interest 30%, news momentum 37%, volatility 10%, activity 23%).
19
+ Stages: Emerging → Growth → Peak → Decline → Dormant (with possible Reigniting). Stage transitions require 2 consecutive days of same candidate (hysteresis).
20
+ Higher score = stronger theme momentum. Stage indicates lifecycle position.
21
+ The \`summary\` object includes \`signals\` (market mood indicators), \`hottestTheme\` (single highest scorer with 3+ stocks), and \`surging\` (rapidly rising themes).`;
10
22
  export const registerGetThemeRanking = (server) => {
11
- server.tool('get_theme_ranking', '한국 주식시장 테마 생명주기 랭킹을 조회합니다. 단계별(초기/성장/정점/쇠퇴/재점화) 테마 목록과 점수를 반환합니다.', {
23
+ server.tool('get_theme_ranking', `Get Korean stock market theme rankings with lifecycle scores (TLI: Theme Lifecycle Index).
24
+
25
+ Use when the user asks about:
26
+ - Trending stock themes, hot investment sectors, market momentum
27
+ - What themes are rising/falling in Korea
28
+ - 한국 주식 테마 랭킹, 요즘 뜨는 테마, 상승/하락 테마
29
+ - KOSPI/KOSDAQ theme trends
30
+
31
+ Returns themes ranked by score (0-100) with lifecycle stage and related stocks. Scores are computed from search interest (Naver DataLab), news momentum, market volatility, and stock activity — all optimized via Bayesian optimization.`, {
12
32
  stage: z
13
33
  .enum(VALID_STAGES)
14
34
  .optional()
15
- .describe('Filter by lifecycle stage: emerging (초기), growth (성장), peak (정점), decline (쇠퇴), reigniting (재점화)'),
16
- }, async ({ stage }) => {
35
+ .describe('Filter by lifecycle stage: emerging (초기 — early interest), growth (성장 — expanding), peak (정점 — maximum attention), decline (하락 — fading), reigniting (재점화 — comeback)'),
36
+ limit: z
37
+ .number()
38
+ .int()
39
+ .min(1)
40
+ .max(50)
41
+ .optional()
42
+ .describe('Max themes per stage (1-50, default 10)'),
43
+ sort: z
44
+ .enum(['score', 'change7d', 'newsCount7d'])
45
+ .optional()
46
+ .describe('Sort order within each stage: score (default), change7d, newsCount7d'),
47
+ }, async ({ stage, limit, sort }) => {
17
48
  try {
18
- const data = await fetchApi('/api/tli/scores/ranking');
49
+ const params = {};
50
+ if (limit !== undefined)
51
+ params.limit = String(limit);
52
+ if (sort !== undefined)
53
+ params.sort = sort;
54
+ const fetchParams = Object.keys(params).length > 0 ? params : undefined;
55
+ const data = await fetchApi('/api/tli/scores/ranking', fetchParams);
19
56
  if (stage) {
20
57
  const stageData = data[stage];
21
58
  const summary = data.summary;
59
+ const stageContext = `${CONTEXT}\nFiltered: ${stage} — ${STAGE_DESCRIPTIONS[stage]}`;
60
+ if (!stageData || (Array.isArray(stageData) && stageData.length === 0)) {
61
+ return {
62
+ content: [{ type: 'text', text: formatEmptyResult(stageContext, `No ${stage} themes currently. Try other stages: ${VALID_STAGES.filter(s => s !== stage).join(', ')}.`) }],
63
+ };
64
+ }
22
65
  return {
23
66
  content: [
24
67
  {
25
68
  type: 'text',
26
- text: formatResult({ stage, themes: stageData, summary }),
69
+ text: formatResult({ stage, themes: stageData, summary }, stageContext),
27
70
  },
28
71
  ],
29
72
  };
30
73
  }
31
74
  return {
32
- content: [{ type: 'text', text: formatResult(data) }],
75
+ content: [{ type: 'text', text: formatResult(data, CONTEXT) }],
33
76
  };
34
77
  }
35
78
  catch (error) {
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare const registerSearchStocks: (server: McpServer) => void;
@@ -0,0 +1,60 @@
1
+ import { z } from 'zod';
2
+ import { fetchApi, formatResult, formatError, formatEmptyResult } from '../fetch-helper.js';
3
+ const CONTEXT = `[StockMatrix Stock Search]
4
+ Search Korean stocks by company name or 6-digit code, then inspect which themes they belong to.
5
+ For 6-digit codes, returns detailed stock-to-theme lookup. For text queries, returns matching stocks with themes.
6
+ Results include stock identity plus top related themes with lifecycle score and stage.
7
+ Common examples: 삼성전자, SK하이닉스, NAVER, 카카오, 005930, 000660.`;
8
+ const IS_SIX_DIGIT = /^\d{6}$/;
9
+ export const registerSearchStocks = (server) => {
10
+ server.tool('search_stocks', `Search Korean stocks by company name, symbol, or 6-digit stock code, and preview their related themes.
11
+
12
+ Use when the user asks:
13
+ - Find Samsung Electronics / 삼성전자
14
+ - I only know the company name, not the stock code
15
+ - 종목명으로 코드 찾고 관련 테마도 보고 싶어
16
+ - 삼성전자가 속한 테마 알려줘 / what themes is Samsung in?
17
+ - 005930 테마 알려줘 / which themes is this stock part of?
18
+
19
+ For 6-digit stock codes, automatically performs a detailed stock-to-theme lookup (replaces get_stock_theme).
20
+ For text queries, searches by company name and returns matching stocks with theme previews.`, {
21
+ query: z
22
+ .string()
23
+ .min(1)
24
+ .max(200)
25
+ .describe('Company name or 6-digit stock code, e.g. "삼성전자", "SK하이닉스", "005930"'),
26
+ }, async ({ query }) => {
27
+ try {
28
+ if (IS_SIX_DIGIT.test(query.trim())) {
29
+ const [themeData, searchData] = await Promise.all([
30
+ fetchApi(`/api/tli/stocks/${query.trim()}/theme`).catch(() => null),
31
+ fetchApi('/api/tli/stocks/search', { q: query.trim() }).catch(() => null),
32
+ ]);
33
+ if (!themeData && (!searchData || (Array.isArray(searchData) && searchData.length === 0))) {
34
+ return {
35
+ content: [{ type: 'text', text: formatEmptyResult(CONTEXT, `No stock found for code "${query}". Verify the 6-digit Korean stock code.`) }],
36
+ };
37
+ }
38
+ const combined = { stockThemes: themeData, searchResults: searchData };
39
+ return {
40
+ content: [{ type: 'text', text: formatResult(combined, CONTEXT) }],
41
+ };
42
+ }
43
+ const data = await fetchApi('/api/tli/stocks/search', { q: query });
44
+ if (Array.isArray(data) && data.length === 0) {
45
+ return {
46
+ content: [{ type: 'text', text: formatEmptyResult(CONTEXT, `No stocks found for "${query}". Try a different company name or check the spelling.`) }],
47
+ };
48
+ }
49
+ return {
50
+ content: [{ type: 'text', text: formatResult(data, CONTEXT) }],
51
+ };
52
+ }
53
+ catch (error) {
54
+ return {
55
+ content: [{ type: 'text', text: formatError(error) }],
56
+ isError: true,
57
+ };
58
+ }
59
+ });
60
+ };
@@ -1,17 +1,33 @@
1
1
  import { z } from 'zod';
2
- import { fetchApi, formatResult, formatError } from '../fetch-helper.js';
2
+ import { fetchApi, formatResult, formatError, formatEmptyResult } from '../fetch-helper.js';
3
+ const CONTEXT = `[StockMatrix Theme Search]
4
+ Results include TLI score (0-100), lifecycle stage, and theme ID for drill-down.
5
+ Use theme_id with get_theme_detail for full analysis or get_theme_history for 30-day trend.
6
+ Search also matches related stock names and 6-digit stock codes when available.
7
+ Stages: Emerging (초기) → Growth (성장) → Peak (정점) → Decline (하락), with Reigniting (재점화) for comeback themes.`;
3
8
  export const registerSearchThemes = (server) => {
4
- server.tool('search_themes', '테마를 검색합니다. 이름(한국어/영문)으로 필터링하여 일치하는 테마의 점수, 단계 정보를 반환합니다.', {
9
+ server.tool('search_themes', `Search Korean stock market themes by keyword (Korean or English).
10
+
11
+ Use when the user asks about a specific sector, industry, investment theme, stock name, or stock code. Searches theme names, related stock names, and stock symbols.
12
+
13
+ Examples: "AI", "반도체" (semiconductor), "2차전지" (EV battery), "방산" (defense), "로봇" (robotics), "원자력" (nuclear), "삼성전자" (Samsung), "005930".
14
+
15
+ Returns matching themes with TLI scores and lifecycle stages. Use the returned theme_id with get_theme_detail or get_theme_history for deeper analysis.`, {
5
16
  query: z
6
17
  .string()
7
18
  .min(1)
8
19
  .max(200)
9
- .describe('Search query (theme name or related stock name, e.g. "AI", "반도체", "삼성전자")'),
20
+ .describe('Search query theme name, sector keyword, stock name, or 6-digit stock code'),
10
21
  }, async ({ query }) => {
11
22
  try {
12
23
  const data = await fetchApi('/api/tli/themes', { q: query });
24
+ if (Array.isArray(data) && data.length === 0) {
25
+ return {
26
+ content: [{ type: 'text', text: formatEmptyResult(CONTEXT, `No themes found for "${query}". Try broader keywords like "AI", "반도체", "2차전지".`) }],
27
+ };
28
+ }
13
29
  return {
14
- content: [{ type: 'text', text: formatResult(data) }],
30
+ content: [{ type: 'text', text: formatResult(data, CONTEXT) }],
15
31
  };
16
32
  }
17
33
  catch (error) {
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "stockmatrix-mcp",
3
3
  "mcpName": "io.github.MongLong0214/stockmatrix-mcp",
4
- "version": "0.1.4",
5
- "description": "StockMatrix MCP Server - Korean stock market theme lifecycle analysis for AI agents",
4
+ "version": "0.4.0",
5
+ "description": "StockMatrix MCP Server Korean stock market theme lifecycle analysis (TLI) with Bayesian-optimized scoring for AI agents",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
8
8
  "bin": {
9
- "stockmatrix-mcp": "dist/index.js"
9
+ "stockmatrix-mcp": "dist/cli.js"
10
10
  },
11
11
  "files": [
12
12
  "dist"
13
13
  ],
14
14
  "scripts": {
15
15
  "build": "tsc",
16
- "start": "node dist/index.js",
16
+ "start": "node dist/cli.js",
17
17
  "prepublishOnly": "npm run build"
18
18
  },
19
19
  "keywords": [
@@ -26,6 +26,11 @@
26
26
  "kosdaq",
27
27
  "ai-agent"
28
28
  ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/MongLong0214/stock-ai-newsletter",
32
+ "directory": "mcp"
33
+ },
29
34
  "homepage": "https://stockmatrix.co.kr/developers",
30
35
  "license": "MIT",
31
36
  "author": "StockMatrix <aistockmatrix@gmail.com>",
@@ -1,23 +0,0 @@
1
- import { z } from 'zod';
2
- import { fetchApi, formatResult, formatError } from '../fetch-helper.js';
3
- export const registerGetStockTheme = (server) => {
4
- server.tool('get_stock_theme', '특정 종목이 속한 테마를 조회합니다. 종목 코드로 관련 테마 정보를 확인할 수 있습니다.', {
5
- symbol: z
6
- .string()
7
- .regex(/^\d{6}$/, 'Korean stock code must be 6 digits')
8
- .describe('Korean stock code (e.g. "005930" for Samsung Electronics, "000660" for SK Hynix)'),
9
- }, async ({ symbol }) => {
10
- try {
11
- const data = await fetchApi(`/api/tli/stocks/${symbol}/theme`);
12
- return {
13
- content: [{ type: 'text', text: formatResult(data) }],
14
- };
15
- }
16
- catch (error) {
17
- return {
18
- content: [{ type: 'text', text: formatError(error) }],
19
- isError: true,
20
- };
21
- }
22
- });
23
- };