@pipeworx/mcp-ecos-kr 0.1.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/LICENSE +21 -0
- package/README.md +55 -0
- package/package.json +20 -0
- package/server.json +18 -0
- package/src/index.ts +336 -0
- package/tsconfig.json +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pipeworx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# mcp-ecos-kr
|
|
2
|
+
|
|
3
|
+
ECOS — Bank of Korea Economic Statistics System.
|
|
4
|
+
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 965+ live data sources.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"mcpServers": {
|
|
19
|
+
"ecos-kr": {
|
|
20
|
+
"url": "https://gateway.pipeworx.io/ecos-kr/mcp"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or connect to the full Pipeworx gateway for access to all 965+ data sources:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"pipeworx": {
|
|
32
|
+
"url": "https://gateway.pipeworx.io/mcp"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Using with ask_pipeworx
|
|
39
|
+
|
|
40
|
+
Instead of calling tools directly, you can ask questions in plain English:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
ask_pipeworx({ question: "your question about Ecos Kr data" })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The gateway picks the right tool and fills the arguments automatically.
|
|
47
|
+
|
|
48
|
+
## More
|
|
49
|
+
|
|
50
|
+
- [All tools and guides](https://github.com/pipeworx-io/examples)
|
|
51
|
+
- [pipeworx.io](https://pipeworx.io)
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipeworx/mcp-ecos-kr",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ECOS — Bank of Korea Economic Statistics System.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "ecos-kr"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-ecos-kr"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.pipeworx-io/ecos-kr",
|
|
4
|
+
"title": "Ecos Kr",
|
|
5
|
+
"description": "ECOS — Bank of Korea Economic Statistics System.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/ecos-kr",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-ecos-kr",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/ecos-kr/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
interface McpToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface McpToolExport {
|
|
12
|
+
tools: McpToolDefinition[];
|
|
13
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
meter?: { credits: number };
|
|
15
|
+
cost?: Record<string, unknown>;
|
|
16
|
+
provider?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ECOS — Bank of Korea Economic Statistics System.
|
|
21
|
+
*
|
|
22
|
+
* Free public API at ecos.bok.or.kr/api. The "sample" demo key works
|
|
23
|
+
* for evaluation traffic with limited rate; production agents should
|
|
24
|
+
* bring their own key (free signup at ecos.bok.or.kr/api/forms/svc.do).
|
|
25
|
+
*
|
|
26
|
+
* What you get:
|
|
27
|
+
* - 101 curated headline indicators (M1/M2, KRW/USD, KRW/JPY, base
|
|
28
|
+
* rate, CPI, GDP, etc.) via ecos_key_indicators — zero-arg call.
|
|
29
|
+
* - 800+ statistic series (monetary policy, prices, balance of
|
|
30
|
+
* payments, GDP, household debt, real estate, etc.) via the
|
|
31
|
+
* table/items/series catalogue tools.
|
|
32
|
+
*
|
|
33
|
+
* Same niche as FRED (US) and energy-charts (EU electricity) — fills
|
|
34
|
+
* the Korean equity/macro data gap in Pipeworx (per 2026-06-03 mcp.so
|
|
35
|
+
* submission flagging Korean lookups as an active builder area).
|
|
36
|
+
*
|
|
37
|
+
* URL shape:
|
|
38
|
+
* GET https://ecos.bok.or.kr/api/<service>/<auth_key>/<format>/<lang>/<start>/<end>/<args...>
|
|
39
|
+
* Service is part of the path, args are part of the path. No query string.
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
const BASE_URL = 'https://ecos.bok.or.kr/api';
|
|
44
|
+
const DEMO_KEY = 'sample';
|
|
45
|
+
const UA = 'pipeworx-mcp-ecos-kr/1.0 (+https://pipeworx.io)';
|
|
46
|
+
|
|
47
|
+
const tools: McpToolExport['tools'] = [
|
|
48
|
+
{
|
|
49
|
+
name: 'ecos_key_indicators',
|
|
50
|
+
description:
|
|
51
|
+
'Bank of Korea headline economic indicators: M1/M2 money supply, KRW/USD + KRW/JPY exchange rates, base interest rate, CPI, PPI, GDP growth, current account balance, household debt, unemployment, housing index — 101 series total in one call. Each row: indicator name, current value, cycle date, unit. Use for "what is Korea\'s [exchange rate / interest rate / CPI / GDP] right now". Updates daily.',
|
|
52
|
+
inputSchema: {
|
|
53
|
+
type: 'object' as const,
|
|
54
|
+
properties: {
|
|
55
|
+
limit: { type: 'number', description: 'Max indicators to return (default 50, max 100).' },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'ecos_search_tables',
|
|
61
|
+
description:
|
|
62
|
+
'Search Bank of Korea ECOS statistic tables — 800+ official Korean economic series across monetary policy, exchange rates, prices, balance of payments, GDP, real estate, household credit, etc. Returns stat_code + Korean name + cycle (A annual / Q quarterly / M monthly / D daily). Use as a directory before ecos_get_series. q is matched substring-wise against the Korean name; pass blank to browse top-N.',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object' as const,
|
|
65
|
+
properties: {
|
|
66
|
+
q: { type: 'string', description: 'Korean substring to match against stat names. Empty = browse top-N.' },
|
|
67
|
+
limit: { type: 'number', description: 'Max rows to return (default 30, max 100).' },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'ecos_series_items',
|
|
73
|
+
description:
|
|
74
|
+
'List the items (sub-series) within a Bank of Korea ECOS statistic table. Most stat_codes have multiple sub-items (e.g., the exchange-rate table has rows per currency: USD, JPY, EUR, ...). Pass the stat_code from ecos_search_tables; returns item_code, item_name, cycle, data_count, start/end period. Use to discover the exact item codes needed by ecos_get_series.',
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: 'object' as const,
|
|
77
|
+
properties: {
|
|
78
|
+
stat_code: { type: 'string', description: 'Statistic table code from ecos_search_tables (e.g., "731Y001" for exchange rates).' },
|
|
79
|
+
limit: { type: 'number', description: 'Max items to return (default 30, max 100).' },
|
|
80
|
+
},
|
|
81
|
+
required: ['stat_code'],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'ecos_get_series',
|
|
86
|
+
description:
|
|
87
|
+
'Fetch a time series from Bank of Korea ECOS. Pass stat_code + cycle (A/Q/M/D) + start/end period. Date format matches the cycle: yearly = "2024", quarterly = "2024Q3", monthly = "202403", daily = "20240315". Optionally narrow to specific item_code(s) via item1/item2/item3/item4. Returns rows with stat_name, item_name, unit, time, data_value. Use after ecos_search_tables + ecos_series_items to discover codes.',
|
|
88
|
+
inputSchema: {
|
|
89
|
+
type: 'object' as const,
|
|
90
|
+
properties: {
|
|
91
|
+
stat_code: { type: 'string', description: 'Statistic code (e.g., "901Y009" for CPI, "731Y001" for exchange rates).' },
|
|
92
|
+
cycle: { type: 'string', description: 'Cycle: A (annual), Q (quarterly), M (monthly), D (daily), SM (semi-monthly).' },
|
|
93
|
+
start: { type: 'string', description: 'Start period in cycle\'s native format (e.g., "2024" / "2024Q1" / "202401" / "20240101").' },
|
|
94
|
+
end: { type: 'string', description: 'End period (same format as start).' },
|
|
95
|
+
item1: { type: 'string', description: 'Optional item filter (e.g., "0000001" for USD in the exchange-rate table).' },
|
|
96
|
+
item2: { type: 'string', description: 'Optional second item filter.' },
|
|
97
|
+
item3: { type: 'string', description: 'Optional third item filter.' },
|
|
98
|
+
item4: { type: 'string', description: 'Optional fourth item filter.' },
|
|
99
|
+
limit: { type: 'number', description: 'Max rows to return (default 100, max 1000).' },
|
|
100
|
+
},
|
|
101
|
+
required: ['stat_code', 'cycle', 'start', 'end'],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
interface KeyStatRow {
|
|
107
|
+
CLASS_NAME: string;
|
|
108
|
+
KEYSTAT_NAME: string;
|
|
109
|
+
DATA_VALUE: string;
|
|
110
|
+
CYCLE: string;
|
|
111
|
+
UNIT_NAME: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
interface StatTableRow {
|
|
115
|
+
P_STAT_CODE: string | null;
|
|
116
|
+
STAT_CODE: string;
|
|
117
|
+
STAT_NAME: string;
|
|
118
|
+
CYCLE: string | null;
|
|
119
|
+
SRCH_YN: string;
|
|
120
|
+
ORG_NAME: string | null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface StatItemRow {
|
|
124
|
+
STAT_CODE: string;
|
|
125
|
+
STAT_NAME: string;
|
|
126
|
+
GRP_CODE: string;
|
|
127
|
+
GRP_NAME: string;
|
|
128
|
+
ITEM_CODE: string;
|
|
129
|
+
ITEM_NAME: string;
|
|
130
|
+
P_ITEM_CODE: string | null;
|
|
131
|
+
P_ITEM_NAME: string | null;
|
|
132
|
+
CYCLE: string;
|
|
133
|
+
START_TIME: string;
|
|
134
|
+
END_TIME: string;
|
|
135
|
+
DATA_CNT: number;
|
|
136
|
+
UNIT_NAME: string;
|
|
137
|
+
WEIGHT: string | null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
interface SeriesRow {
|
|
141
|
+
STAT_CODE: string;
|
|
142
|
+
STAT_NAME: string;
|
|
143
|
+
ITEM_CODE1: string | null;
|
|
144
|
+
ITEM_NAME1: string | null;
|
|
145
|
+
ITEM_CODE2: string | null;
|
|
146
|
+
ITEM_NAME2: string | null;
|
|
147
|
+
ITEM_CODE3: string | null;
|
|
148
|
+
ITEM_NAME3: string | null;
|
|
149
|
+
ITEM_CODE4: string | null;
|
|
150
|
+
ITEM_NAME4: string | null;
|
|
151
|
+
UNIT_NAME: string;
|
|
152
|
+
TIME: string;
|
|
153
|
+
DATA_VALUE: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ECOS error envelope shape: { RESULT: { CODE: "INFO-200", MESSAGE: "..." } }
|
|
157
|
+
// or empty list. Normalize into a thrown Error.
|
|
158
|
+
type EcosResponse<T, K extends string> =
|
|
159
|
+
| { [P in K]: { list_total_count?: number; row?: T[] } }
|
|
160
|
+
| { RESULT?: { CODE?: string; MESSAGE?: string } };
|
|
161
|
+
|
|
162
|
+
function authKey(args: Record<string, unknown>): string {
|
|
163
|
+
const k = (args._apiKey as string | undefined)?.trim();
|
|
164
|
+
return k && k.length > 0 ? k : DEMO_KEY;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ECOS path: /{service}/{key}/{format}/{lang}/{start}/{end}/{rest...}. The free
|
|
168
|
+
// "sample" key (the only one obtainable without a Korean mobile number) caps each
|
|
169
|
+
// call at 10 rows — a larger range returns a misleading ERROR-301 ("count type
|
|
170
|
+
// invalid"). The cap is per-CALL count, so we page in chunks of 10 (the first
|
|
171
|
+
// chunk also yields list_total_count; the rest fetch in parallel) to return the
|
|
172
|
+
// full result with the sample key. Real keys work the same way (just more calls).
|
|
173
|
+
const ECOS_CHUNK = 10;
|
|
174
|
+
|
|
175
|
+
async function ecosFetchChunk<T, K extends string>(
|
|
176
|
+
service: K,
|
|
177
|
+
segs: string[],
|
|
178
|
+
): Promise<{ rows: T[]; total: number; noData: boolean }> {
|
|
179
|
+
const res = await fetch(`${BASE_URL}/${service}/${segs.join('/')}`, {
|
|
180
|
+
headers: { Accept: 'application/json', 'User-Agent': UA },
|
|
181
|
+
});
|
|
182
|
+
if (!res.ok) throw new Error(`ECOS error: HTTP ${res.status}`);
|
|
183
|
+
const data = (await res.json()) as EcosResponse<T, K>;
|
|
184
|
+
if ('RESULT' in data && data.RESULT) {
|
|
185
|
+
const code = data.RESULT.CODE ?? '?';
|
|
186
|
+
const msg = data.RESULT.MESSAGE ?? '(no message)';
|
|
187
|
+
if (code === 'INFO-200') return { rows: [], total: 0, noData: true }; // valid: no (more) data
|
|
188
|
+
if (code === 'INFO-100' || code === 'CODE-100') {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`ECOS auth required (${code}): ${msg}. Free signup at https://ecos.bok.or.kr/api/forms/svc.do. ` +
|
|
191
|
+
`Pass via _apiKey, or "sample" demo key works for evaluation.`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
throw new Error(`ECOS error (${code}): ${msg}`);
|
|
195
|
+
}
|
|
196
|
+
const wrapped = (data as Record<string, { list_total_count?: number; row?: T[] }>)[service];
|
|
197
|
+
return { rows: wrapped?.row ?? [], total: wrapped?.list_total_count ?? 0, noData: false };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function ecosGet<T, K extends string>(
|
|
201
|
+
service: K,
|
|
202
|
+
pathSegments: string[],
|
|
203
|
+
): Promise<T[]> {
|
|
204
|
+
const [key, fmt, lang, startSeg, endSeg, ...rest] = pathSegments;
|
|
205
|
+
const start = Number(startSeg) || 1;
|
|
206
|
+
const end = Number(endSeg) || ECOS_CHUNK;
|
|
207
|
+
|
|
208
|
+
const chunkSegs = (s: number, e: number) => [key, fmt, lang, String(s), String(e), ...rest];
|
|
209
|
+
|
|
210
|
+
// First chunk — also reveals the true total so we don't over-fetch.
|
|
211
|
+
const first = await ecosFetchChunk<T, K>(service, chunkSegs(start, Math.min(start + ECOS_CHUNK - 1, end)));
|
|
212
|
+
if (first.noData) return [];
|
|
213
|
+
const out: T[] = [...first.rows];
|
|
214
|
+
const trueEnd = first.total > 0 ? Math.min(end, first.total) : end;
|
|
215
|
+
|
|
216
|
+
const tasks: Promise<{ rows: T[]; total: number; noData: boolean }>[] = [];
|
|
217
|
+
for (let s = start + ECOS_CHUNK; s <= trueEnd; s += ECOS_CHUNK) {
|
|
218
|
+
tasks.push(ecosFetchChunk<T, K>(service, chunkSegs(s, Math.min(s + ECOS_CHUNK - 1, trueEnd))));
|
|
219
|
+
}
|
|
220
|
+
for (const chunk of await Promise.all(tasks)) out.push(...chunk.rows);
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
225
|
+
const key = authKey(args);
|
|
226
|
+
const limitOf = (def: number, max: number): number => {
|
|
227
|
+
const n = (args.limit as number | undefined) ?? def;
|
|
228
|
+
return Math.min(Math.max(1, n), max);
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
switch (name) {
|
|
232
|
+
case 'ecos_key_indicators': {
|
|
233
|
+
// Fixed ~101-indicator list; fetch all (pagination caps at the true total).
|
|
234
|
+
const limit = limitOf(110, 200);
|
|
235
|
+
const rows = await ecosGet<KeyStatRow, 'KeyStatisticList'>('KeyStatisticList', [key, 'json', 'kr', '1', String(limit)]);
|
|
236
|
+
return {
|
|
237
|
+
count: rows.length,
|
|
238
|
+
indicators: rows.map((r) => ({
|
|
239
|
+
category: r.CLASS_NAME,
|
|
240
|
+
name: r.KEYSTAT_NAME,
|
|
241
|
+
value: r.DATA_VALUE,
|
|
242
|
+
unit: r.UNIT_NAME,
|
|
243
|
+
cycle: r.CYCLE,
|
|
244
|
+
})),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
case 'ecos_search_tables': {
|
|
249
|
+
const q = ((args.q as string | undefined) ?? '').trim();
|
|
250
|
+
const limit = limitOf(30, 100);
|
|
251
|
+
// Pull a wider window if filtering, then post-filter — ECOS doesn't
|
|
252
|
+
// support server-side substring search on this endpoint.
|
|
253
|
+
const fetchN = q ? 500 : limit;
|
|
254
|
+
const rows = await ecosGet<StatTableRow, 'StatisticTableList'>('StatisticTableList', [key, 'json', 'kr', '1', String(fetchN)]);
|
|
255
|
+
const filtered = q
|
|
256
|
+
? rows.filter((r) => (r.STAT_NAME ?? '').includes(q)).slice(0, limit)
|
|
257
|
+
: rows.slice(0, limit);
|
|
258
|
+
return {
|
|
259
|
+
query: q || null,
|
|
260
|
+
count: filtered.length,
|
|
261
|
+
tables: filtered.map((r) => ({
|
|
262
|
+
stat_code: r.STAT_CODE,
|
|
263
|
+
parent_stat_code: r.P_STAT_CODE,
|
|
264
|
+
name: r.STAT_NAME,
|
|
265
|
+
cycle: r.CYCLE,
|
|
266
|
+
searchable: r.SRCH_YN === 'Y',
|
|
267
|
+
})),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
case 'ecos_series_items': {
|
|
272
|
+
const stat_code = (args.stat_code as string | undefined)?.trim();
|
|
273
|
+
if (!stat_code) throw new Error('Required argument "stat_code" is missing. Pass a code like "731Y001" (from ecos_search_tables).');
|
|
274
|
+
const limit = limitOf(30, 100);
|
|
275
|
+
const rows = await ecosGet<StatItemRow, 'StatisticItemList'>(
|
|
276
|
+
'StatisticItemList',
|
|
277
|
+
[key, 'json', 'kr', '1', String(limit), stat_code],
|
|
278
|
+
);
|
|
279
|
+
return {
|
|
280
|
+
stat_code,
|
|
281
|
+
count: rows.length,
|
|
282
|
+
items: rows.map((r) => ({
|
|
283
|
+
stat_name: r.STAT_NAME,
|
|
284
|
+
group_code: r.GRP_CODE,
|
|
285
|
+
group_name: r.GRP_NAME,
|
|
286
|
+
item_code: r.ITEM_CODE,
|
|
287
|
+
item_name: r.ITEM_NAME,
|
|
288
|
+
cycle: r.CYCLE,
|
|
289
|
+
start: r.START_TIME,
|
|
290
|
+
end: r.END_TIME,
|
|
291
|
+
data_count: r.DATA_CNT,
|
|
292
|
+
unit: r.UNIT_NAME,
|
|
293
|
+
})),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
case 'ecos_get_series': {
|
|
298
|
+
const stat_code = (args.stat_code as string | undefined)?.trim();
|
|
299
|
+
const cycle = (args.cycle as string | undefined)?.trim();
|
|
300
|
+
const start = (args.start as string | undefined)?.trim();
|
|
301
|
+
const end = (args.end as string | undefined)?.trim();
|
|
302
|
+
if (!stat_code) throw new Error('Required argument "stat_code" is missing. Pass a code like "901Y009" (CPI) or "731Y001" (exchange rates) — discover via ecos_search_tables.');
|
|
303
|
+
if (!cycle) throw new Error('Required argument "cycle" is missing. Pass "A" (annual), "Q" (quarterly), "M" (monthly), or "D" (daily) — match the cycle from ecos_search_tables.');
|
|
304
|
+
if (!start || !end) throw new Error('Required arguments "start" and "end" are missing. Format depends on cycle: "2024" for A, "2024Q1" for Q, "202403" for M, "20240315" for D.');
|
|
305
|
+
const limit = limitOf(100, 1000);
|
|
306
|
+
const path = [key, 'json', 'kr', '1', String(limit), stat_code, cycle, start, end];
|
|
307
|
+
const optionalItems = [args.item1, args.item2, args.item3, args.item4];
|
|
308
|
+
for (const it of optionalItems) {
|
|
309
|
+
if (typeof it === 'string' && it.trim()) path.push(it.trim());
|
|
310
|
+
}
|
|
311
|
+
const rows = await ecosGet<SeriesRow, 'StatisticSearch'>('StatisticSearch', path);
|
|
312
|
+
return {
|
|
313
|
+
stat_code,
|
|
314
|
+
cycle,
|
|
315
|
+
start,
|
|
316
|
+
end,
|
|
317
|
+
count: rows.length,
|
|
318
|
+
series: rows.map((r) => ({
|
|
319
|
+
stat_name: r.STAT_NAME,
|
|
320
|
+
item_code1: r.ITEM_CODE1,
|
|
321
|
+
item_name1: r.ITEM_NAME1,
|
|
322
|
+
item_code2: r.ITEM_CODE2,
|
|
323
|
+
item_name2: r.ITEM_NAME2,
|
|
324
|
+
unit: r.UNIT_NAME,
|
|
325
|
+
time: r.TIME,
|
|
326
|
+
value: r.DATA_VALUE,
|
|
327
|
+
})),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
default:
|
|
332
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export default { tools, callTool, meter: { credits: 1 } } satisfies McpToolExport;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|