daiso 1.0.8 → 1.0.9

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.
Files changed (44) hide show
  1. package/README.md +83 -7
  2. package/dist/api/actionsProxy.js +5 -0
  3. package/dist/api/configStatus.js +8 -0
  4. package/dist/api/feedbackHandlers.js +60 -0
  5. package/dist/api/healthCheckDefinitions.js +170 -0
  6. package/dist/api/healthCheckShape.js +106 -0
  7. package/dist/api/healthCheckTypes.js +4 -0
  8. package/dist/api/healthChecks.js +3 -271
  9. package/dist/api/opinetHandlers.js +91 -0
  10. package/dist/api/routes/feedbackRoutes.js +5 -0
  11. package/dist/api/routes/healthRoutes.js +17 -1
  12. package/dist/api/routes/opinetRoutes.js +24 -0
  13. package/dist/bin.js +0 -0
  14. package/dist/cli/commandOptions.js +23 -2
  15. package/dist/cli/commands/apiCommand.js +24 -0
  16. package/dist/cli/commands/cinema.js +65 -0
  17. package/dist/cli/commands/convenience.js +57 -100
  18. package/dist/cli/http.js +1 -1
  19. package/dist/cli.js +10 -1
  20. package/dist/cliHelp.js +44 -8
  21. package/dist/core/outputSchema.js +141 -0
  22. package/dist/core/registry.js +1 -138
  23. package/dist/index.js +92 -29
  24. package/dist/pages/openapiFullSpec.js +5 -0
  25. package/dist/pages/openapiSpecActions.js +63 -0
  26. package/dist/pages/openapiSpecPathsFeedback.js +95 -0
  27. package/dist/pages/openapiSpecPathsOpinet.js +81 -0
  28. package/dist/pages/promptReferenceText.js +15 -0
  29. package/dist/services/feedback/client.js +109 -0
  30. package/dist/services/feedback/index.js +22 -0
  31. package/dist/services/feedback/tools/submitDeveloperRequest.js +54 -0
  32. package/dist/services/feedback/types.js +3 -0
  33. package/dist/services/gs25/client.js +1 -84
  34. package/dist/services/gs25/productSearch.js +97 -0
  35. package/dist/services/oliveyoung/client.js +1 -55
  36. package/dist/services/oliveyoung/normalize.js +58 -0
  37. package/dist/services/opinet/client.js +231 -0
  38. package/dist/services/opinet/index.js +26 -0
  39. package/dist/services/opinet/location.js +119 -0
  40. package/dist/services/opinet/tools.js +80 -0
  41. package/dist/services/opinet/types.js +1 -0
  42. package/openapi.json +82 -0
  43. package/openapi.yaml +55 -0
  44. package/package.json +18 -17
@@ -6,6 +6,7 @@ import { printCommandHelp } from '../../cliHelp.js';
6
6
  import { parseCliArgs, toUrl, applyOptionsToQuery, toQueryOptions } from '../args.js';
7
7
  import { validateCommandOptions } from '../commandOptions.js';
8
8
  import { requestAndPrintResponse } from '../http.js';
9
+ import { runApiCommand } from './apiCommand.js';
9
10
  export async function handleCompareProducts(options, deps) {
10
11
  const parsed = parseCliArgs(options);
11
12
  if (parsed.options.help === 'true') {
@@ -222,110 +223,66 @@ export async function handleGs25Inventory(options, deps) {
222
223
  return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'gs25-inventory', parsed.options.json === 'true');
223
224
  }
224
225
  export async function handleSevenElevenProducts(options, deps) {
225
- const parsed = parseCliArgs(options);
226
- if (parsed.options.help === 'true') {
227
- return printCommandHelp('seveneleven-products', deps.writeOut, deps.writeErr);
228
- }
229
- if (validateCommandOptions('seveneleven-products', parsed.options, deps.writeErr)) {
230
- return 1;
231
- }
232
- const query = parsed.positionals[0];
233
- if (!query) {
234
- deps.writeErr('seveneleven-products 명령은 검색어가 필요합니다. 예: daiso seveneleven-products 삼각김밥');
235
- return 1;
236
- }
237
- const targetUrl = toUrl('/api/seveneleven/products');
238
- targetUrl.searchParams.set('query', query);
239
- applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
240
- return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'seveneleven-products', parsed.options.json === 'true');
226
+ return await runApiCommand({
227
+ command: 'seveneleven-products',
228
+ options,
229
+ deps,
230
+ path: '/api/seveneleven/products',
231
+ configure: (parsed) => {
232
+ const query = parsed.positionals[0];
233
+ if (!query) {
234
+ return 'seveneleven-products 명령은 검색어가 필요합니다. 예: daiso seveneleven-products 삼각김밥';
235
+ }
236
+ parsed.options.query = query;
237
+ return null;
238
+ },
239
+ });
241
240
  }
242
241
  export async function handleSevenElevenStores(options, deps) {
243
- const parsed = parseCliArgs(options);
244
- if (parsed.options.help === 'true') {
245
- return printCommandHelp('seveneleven-stores', deps.writeOut, deps.writeErr);
246
- }
247
- if (validateCommandOptions('seveneleven-stores', parsed.options, deps.writeErr)) {
248
- return 1;
249
- }
250
- const keyword = parsed.positionals[0];
251
- if (!keyword) {
252
- deps.writeErr('seveneleven-stores 명령은 검색어가 필요합니다. 예: daiso seveneleven-stores 안산 중앙역');
253
- return 1;
254
- }
255
- const targetUrl = toUrl('/api/seveneleven/stores');
256
- targetUrl.searchParams.set('keyword', keyword);
257
- applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
258
- return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'seveneleven-stores', parsed.options.json === 'true');
242
+ return await runApiCommand({
243
+ command: 'seveneleven-stores',
244
+ options,
245
+ deps,
246
+ path: '/api/seveneleven/stores',
247
+ configure: (parsed) => {
248
+ const keyword = parsed.positionals[0];
249
+ if (!keyword) {
250
+ return 'seveneleven-stores 명령은 검색어가 필요합니다. 예: daiso seveneleven-stores 안산 중앙역';
251
+ }
252
+ parsed.options.keyword = keyword;
253
+ return null;
254
+ },
255
+ });
256
+ }
257
+ export async function handleSevenElevenInventory(options, deps) {
258
+ return await runApiCommand({
259
+ command: 'seveneleven-inventory',
260
+ options,
261
+ deps,
262
+ path: '/api/seveneleven/inventory',
263
+ configure: (parsed) => {
264
+ const keyword = parsed.positionals[0] || parsed.options.keyword;
265
+ if (!keyword) {
266
+ return 'seveneleven-inventory 명령은 검색어가 필요합니다. 예: daiso seveneleven-inventory 핫식스 --storeKeyword "안산 중앙역"';
267
+ }
268
+ parsed.options.keyword = keyword;
269
+ return null;
270
+ },
271
+ });
259
272
  }
260
273
  export async function handleSevenElevenPopwords(options, deps) {
261
- const parsed = parseCliArgs(options);
262
- if (parsed.options.help === 'true') {
263
- return printCommandHelp('seveneleven-popwords', deps.writeOut, deps.writeErr);
264
- }
265
- if (validateCommandOptions('seveneleven-popwords', parsed.options, deps.writeErr)) {
266
- return 1;
267
- }
268
- const targetUrl = toUrl('/api/seveneleven/popwords');
269
- applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
270
- return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'seveneleven-popwords', parsed.options.json === 'true');
274
+ return await runApiCommand({
275
+ command: 'seveneleven-popwords',
276
+ options,
277
+ deps,
278
+ path: '/api/seveneleven/popwords',
279
+ });
271
280
  }
272
281
  export async function handleSevenElevenCatalog(options, deps) {
273
- const parsed = parseCliArgs(options);
274
- if (parsed.options.help === 'true') {
275
- return printCommandHelp('seveneleven-catalog', deps.writeOut, deps.writeErr);
276
- }
277
- if (validateCommandOptions('seveneleven-catalog', parsed.options, deps.writeErr)) {
278
- return 1;
279
- }
280
- const targetUrl = toUrl('/api/seveneleven/catalog');
281
- applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
282
- return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'seveneleven-catalog', parsed.options.json === 'true');
283
- }
284
- export async function handleLottecinemaTheaters(options, deps) {
285
- const parsed = parseCliArgs(options);
286
- if (parsed.options.help === 'true') {
287
- return printCommandHelp('lottecinema-theaters', deps.writeOut, deps.writeErr);
288
- }
289
- const keyword = parsed.positionals[0];
290
- if (keyword) {
291
- parsed.options.keyword = keyword;
292
- }
293
- if (validateCommandOptions('lottecinema-theaters', parsed.options, deps.writeErr)) {
294
- return 1;
295
- }
296
- const targetUrl = toUrl('/api/lottecinema/theaters');
297
- applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
298
- return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottecinema-theaters', parsed.options.json === 'true');
299
- }
300
- export async function handleLottecinemaMovies(options, deps) {
301
- const parsed = parseCliArgs(options);
302
- if (parsed.options.help === 'true') {
303
- return printCommandHelp('lottecinema-movies', deps.writeOut, deps.writeErr);
304
- }
305
- const keyword = parsed.positionals[0];
306
- if (keyword) {
307
- parsed.options.keyword = keyword;
308
- }
309
- if (validateCommandOptions('lottecinema-movies', parsed.options, deps.writeErr)) {
310
- return 1;
311
- }
312
- const targetUrl = toUrl('/api/lottecinema/movies');
313
- applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
314
- return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottecinema-movies', parsed.options.json === 'true');
315
- }
316
- export async function handleLottecinemaSeats(options, deps) {
317
- const parsed = parseCliArgs(options);
318
- if (parsed.options.help === 'true') {
319
- return printCommandHelp('lottecinema-seats', deps.writeOut, deps.writeErr);
320
- }
321
- const keyword = parsed.positionals[0];
322
- if (keyword) {
323
- parsed.options.keyword = keyword;
324
- }
325
- if (validateCommandOptions('lottecinema-seats', parsed.options, deps.writeErr)) {
326
- return 1;
327
- }
328
- const targetUrl = toUrl('/api/lottecinema/seats');
329
- applyOptionsToQuery(targetUrl, toQueryOptions(parsed.options));
330
- return await requestAndPrintResponse(deps.fetchImpl, deps.writeOut, deps.writeErr, targetUrl, 'lottecinema-seats', parsed.options.json === 'true');
282
+ return await runApiCommand({
283
+ command: 'seveneleven-catalog',
284
+ options,
285
+ deps,
286
+ path: '/api/seveneleven/catalog',
287
+ });
331
288
  }
package/dist/cli/http.js CHANGED
@@ -54,7 +54,7 @@ function buildCliHint(command, url, bodyText) {
54
54
  if (url.pathname === '/api/seveneleven/inventory') {
55
55
  return [
56
56
  '힌트: 세븐일레븐 재고는 매장 키워드가 필요합니다.',
57
- '다음 명령 예시: daiso get /api/seveneleven/inventory --keyword <상품명> --storeKeyword 강남',
57
+ '다음 명령 예시: daiso seveneleven-inventory <상품명> --storeKeyword 강남',
58
58
  ];
59
59
  }
60
60
  return [];
package/dist/cli.js CHANGED
@@ -12,7 +12,8 @@ import { DEFAULT_BASE_URL, DEFAULT_MCP_URL } from './cli/constants.js';
12
12
  import { parseCliArgs } from './cli/args.js';
13
13
  import { validateCommandOptions } from './cli/commandOptions.js';
14
14
  import { handleGet, handleProducts, handleProduct, handleStores, handleInventory, handleDisplayLocation, } from './cli/commands/daiso.js';
15
- import { handleCuStores, handleCuInventory, handleEmart24Stores, handleEmart24Products, handleEmart24Inventory, handleLotteMartStores, handleLotteMartProducts, handleCompareProducts, handlePlaces, handleGs25Stores, handleGs25Products, handleGs25Inventory, handleSevenElevenProducts, handleSevenElevenStores, handleSevenElevenPopwords, handleSevenElevenCatalog, handleLottecinemaTheaters, handleLottecinemaMovies, handleLottecinemaSeats, } from './cli/commands/convenience.js';
15
+ import { handleCuStores, handleCuInventory, handleEmart24Stores, handleEmart24Products, handleEmart24Inventory, handleLotteMartStores, handleLotteMartProducts, handleCompareProducts, handlePlaces, handleGs25Stores, handleGs25Products, handleGs25Inventory, handleSevenElevenProducts, handleSevenElevenStores, handleSevenElevenInventory, handleSevenElevenPopwords, handleSevenElevenCatalog, } from './cli/commands/convenience.js';
16
+ import { handleCgvTheaters, handleCgvMovies, handleCgvTimetable, handleLottecinemaTheaters, handleLottecinemaMovies, handleLottecinemaSeats, } from './cli/commands/cinema.js';
16
17
  function rejectTopLevelUnknownOptions(options, command, deps) {
17
18
  const parsed = parseCliArgs(options);
18
19
  return validateCommandOptions(command, parsed.options, deps.writeErr);
@@ -120,6 +121,12 @@ export async function runCli(argv, deps) {
120
121
  return await handleCuStores(options, resolvedDeps);
121
122
  if (command === 'cu-inventory')
122
123
  return await handleCuInventory(options, resolvedDeps);
124
+ if (command === 'cgv-theaters')
125
+ return await handleCgvTheaters(options, resolvedDeps);
126
+ if (command === 'cgv-movies')
127
+ return await handleCgvMovies(options, resolvedDeps);
128
+ if (command === 'cgv-timetable')
129
+ return await handleCgvTimetable(options, resolvedDeps);
123
130
  if (command === 'lottecinema-theaters')
124
131
  return await handleLottecinemaTheaters(options, resolvedDeps);
125
132
  if (command === 'lottecinema-movies')
@@ -146,6 +153,8 @@ export async function runCli(argv, deps) {
146
153
  return await handleSevenElevenProducts(options, resolvedDeps);
147
154
  if (command === 'seveneleven-stores')
148
155
  return await handleSevenElevenStores(options, resolvedDeps);
156
+ if (command === 'seveneleven-inventory')
157
+ return await handleSevenElevenInventory(options, resolvedDeps);
149
158
  if (command === 'seveneleven-popwords')
150
159
  return await handleSevenElevenPopwords(options, resolvedDeps);
151
160
  if (command === 'seveneleven-catalog')
package/dist/cliHelp.js CHANGED
@@ -17,6 +17,9 @@ export const COMMAND_LIST = [
17
17
  'places',
18
18
  'cu-stores',
19
19
  'cu-inventory',
20
+ 'cgv-theaters',
21
+ 'cgv-movies',
22
+ 'cgv-timetable',
20
23
  'lottecinema-theaters',
21
24
  'lottecinema-movies',
22
25
  'lottecinema-seats',
@@ -30,6 +33,7 @@ export const COMMAND_LIST = [
30
33
  'gs25-inventory',
31
34
  'seveneleven-products',
32
35
  'seveneleven-stores',
36
+ 'seveneleven-inventory',
33
37
  'seveneleven-popwords',
34
38
  'seveneleven-catalog',
35
39
  ];
@@ -49,6 +53,9 @@ const COMMAND_SUMMARY = {
49
53
  places: '음식점/카페 등 주변 장소 검색',
50
54
  'cu-stores': 'CU 매장 검색',
51
55
  'cu-inventory': 'CU 재고 조회',
56
+ 'cgv-theaters': 'CGV 극장 검색',
57
+ 'cgv-movies': 'CGV 영화 검색',
58
+ 'cgv-timetable': 'CGV 시간표 조회',
52
59
  'lottecinema-theaters': '롯데시네마 주변 지점 조회',
53
60
  'lottecinema-movies': '롯데시네마 영화/회차 조회',
54
61
  'lottecinema-seats': '롯데시네마 잔여 좌석 조회',
@@ -62,6 +69,7 @@ const COMMAND_SUMMARY = {
62
69
  'gs25-inventory': 'GS25 재고 조회',
63
70
  'seveneleven-products': '세븐일레븐 상품 검색',
64
71
  'seveneleven-stores': '세븐일레븐 매장 검색',
72
+ 'seveneleven-inventory': '세븐일레븐 재고 조회',
65
73
  'seveneleven-popwords': '세븐일레븐 인기 검색어 조회',
66
74
  'seveneleven-catalog': '세븐일레븐 카탈로그 스냅샷 조회',
67
75
  };
@@ -176,6 +184,30 @@ const COMMAND_DETAIL = {
176
184
  '예시: daiso cu-inventory 과자',
177
185
  '예시: daiso cu-inventory 컵라면 --storeKeyword 강남 --size 10',
178
186
  ],
187
+ 'cgv-theaters': [
188
+ '명령: cgv-theaters',
189
+ '설명: CGV 극장 검색 API를 호출합니다.',
190
+ '사용법: daiso cgv-theaters [keyword] [--regionCode 값] [--playDate YYYYMMDD] [--lat 값] [--lng 값] [--limit N] [--json]',
191
+ '옵션: --keyword, --regionCode, --playDate, --lat, --lng, --limit, --json',
192
+ '예시: daiso cgv-theaters 강남 --limit 5',
193
+ '예시: daiso cgv-theaters --regionCode 01 --playDate 20260304',
194
+ ],
195
+ 'cgv-movies': [
196
+ '명령: cgv-movies',
197
+ '설명: CGV 영화 검색 API를 호출합니다.',
198
+ '사용법: daiso cgv-movies [keyword] [--playDate YYYYMMDD] [--theaterCode 값] [--movieCode 값] [--lat 값] [--lng 값] [--json]',
199
+ '옵션: --keyword, --playDate, --theaterCode, --movieCode, --lat, --lng, --json',
200
+ '예시: daiso cgv-movies 강남 --playDate 20260304',
201
+ '예시: daiso cgv-movies --playDate 20260304 --theaterCode 0056',
202
+ ],
203
+ 'cgv-timetable': [
204
+ '명령: cgv-timetable',
205
+ '설명: CGV 시간표 API를 호출합니다.',
206
+ '사용법: daiso cgv-timetable [keyword] [--playDate YYYYMMDD] [--theaterCode 값] [--movieCode 값] [--lat 값] [--lng 값] [--json]',
207
+ '옵션: --keyword, --playDate, --theaterCode, --movieCode, --lat, --lng, --json',
208
+ '예시: daiso cgv-timetable 강남 --playDate 20260304',
209
+ '예시: daiso cgv-timetable --playDate 20260304 --theaterCode 0056',
210
+ ],
179
211
  'lottecinema-theaters': [
180
212
  '명령: lottecinema-theaters',
181
213
  '설명: 롯데시네마 주변 지점 API를 호출합니다.',
@@ -282,6 +314,14 @@ const COMMAND_DETAIL = {
282
314
  '옵션: --limit, --json',
283
315
  '예시: daiso seveneleven-stores 안산 중앙역 --limit 10',
284
316
  ],
317
+ 'seveneleven-inventory': [
318
+ '명령: seveneleven-inventory',
319
+ '설명: 세븐일레븐 재고 API를 호출합니다.',
320
+ '사용법: daiso seveneleven-inventory <keyword> [--storeKeyword 값] [--storeLimit N] [--page N] [--size N] [--json]',
321
+ '필수: <keyword>',
322
+ '옵션: --keyword, --storeKeyword, --storeLimit, --page, --size, --json',
323
+ '예시: daiso seveneleven-inventory 핫식스 --storeKeyword "안산 중앙역" --storeLimit 10',
324
+ ],
285
325
  'seveneleven-popwords': [
286
326
  '명령: seveneleven-popwords',
287
327
  '설명: 세븐일레븐 인기 검색어 API를 호출합니다.',
@@ -323,24 +363,20 @@ export function printHelp(writeOut) {
323
363
  writeOut(' npx daiso inventory 1034604 --keyword 강남역');
324
364
  writeOut(' npx daiso display-location 1034604 04515');
325
365
  writeOut(' npx daiso places 강남역 --category cafe --limit 5');
326
- writeOut(' npx daiso cu-stores 강남');
327
366
  writeOut(' npx daiso cu-inventory 과자 --storeKeyword 강남');
367
+ writeOut(' npx daiso cgv-theaters 강남 --limit 5');
368
+ writeOut(' npx daiso cgv-timetable --playDate 20260307 --theaterCode 0056');
328
369
  writeOut(' npx daiso lottecinema-theaters --keyword "안산 중앙역" --limit 5');
329
- writeOut(' npx daiso lottecinema-movies --playDate 20260310 --keyword "안산 중앙역"');
330
- writeOut(' npx daiso lottecinema-seats --playDate 20260310 --keyword "안산 중앙역" --movieId 23816');
331
370
  writeOut(' npx daiso lottemart-stores 잠실 --area 서울 --limit 10');
332
371
  writeOut(' npx daiso lottemart-products 콜라 --storeName 강변점 --area 서울');
333
- writeOut(' npx daiso emart24-stores 강남 --limit 10');
334
372
  writeOut(' npx daiso emart24-products 두바이 --pageSize 20');
335
- writeOut(' npx daiso emart24-inventory 8800244010504 --bizNoArr 28339,05015');
336
- writeOut(' npx daiso gs25-stores 강남 --limit 10');
337
373
  writeOut(' npx daiso gs25-products 오감자');
338
374
  writeOut(' npx daiso gs25-inventory 오감자 --storeKeyword 강남');
339
375
  writeOut(' npx daiso seveneleven-products 삼각김밥 --size 20');
340
376
  writeOut(' npx daiso seveneleven-stores 안산 중앙역 --limit 10');
377
+ writeOut(' npx daiso seveneleven-inventory 핫식스 --storeKeyword "안산 중앙역"');
341
378
  writeOut(' npx daiso seveneleven-popwords --label home');
342
379
  writeOut(' npx daiso seveneleven-catalog --limit 10');
343
- writeOut(' npx daiso get /api/cgv/movies --playDate 20260307 --theaterCode 0056');
344
380
  writeOut('');
345
381
  writeOut('정보가 부족할 때:');
346
382
  writeOut(' 제품명을 먼저 검색해 productId를 확인: npx daiso products 수납박스');
@@ -354,7 +390,7 @@ export function printHelp(writeOut) {
354
390
  export function printCommandHelp(command, writeOut, writeErr) {
355
391
  if (!Object.hasOwn(COMMAND_DETAIL, command)) {
356
392
  writeErr(`도움말을 찾을 수 없는 명령어: ${command}`);
357
- writeErr('사용 가능한 명령어: help, version, url, health, claude, get, products, product, stores, inventory, display-location, places, cu-stores, cu-inventory, lottecinema-theaters, lottecinema-movies, lottecinema-seats, lottemart-stores, lottemart-products, emart24-stores, emart24-products, emart24-inventory, gs25-stores, gs25-products, gs25-inventory, seveneleven-products, seveneleven-stores, seveneleven-popwords, seveneleven-catalog');
393
+ writeErr(`사용 가능한 명령어: ${COMMAND_LIST.join(', ')}`);
358
394
  return 1;
359
395
  }
360
396
  const detail = COMMAND_DETAIL[command];
@@ -0,0 +1,141 @@
1
+ /**
2
+ * MCP 도구 outputSchema 생성
3
+ */
4
+ import * as z from 'zod';
5
+ const DEFAULT_OUTPUT_SCHEMA = z.object({}).loose().describe('도구 실행 결과(JSON 객체)');
6
+ function createFallbackOutputSchema(toolName) {
7
+ if (toolName.includes('compare')) {
8
+ return {
9
+ keyword: z.string().optional(),
10
+ services: z.array(z.string()).optional(),
11
+ serviceCount: z.number().optional(),
12
+ resultCount: z.number().optional(),
13
+ bestPrice: z.unknown().optional(),
14
+ results: z.array(z.unknown()).optional(),
15
+ errors: z.array(z.unknown()).optional(),
16
+ note: z.string().optional(),
17
+ };
18
+ }
19
+ if (toolName.includes('search_products') ||
20
+ toolName.includes('searchProducts') ||
21
+ toolName.endsWith('_products')) {
22
+ return {
23
+ keyword: z.string().optional(),
24
+ query: z.string().optional(),
25
+ page: z.number().optional(),
26
+ pageSize: z.number().optional(),
27
+ totalCount: z.number().optional(),
28
+ count: z.number().optional(),
29
+ products: z.array(z.unknown()).optional(),
30
+ note: z.string().optional(),
31
+ };
32
+ }
33
+ if (toolName.includes('find_stores') ||
34
+ toolName.includes('nearby_stores') ||
35
+ toolName.includes('search_stores')) {
36
+ return {
37
+ keyword: z.string().optional(),
38
+ searchParams: z.unknown().optional(),
39
+ location: z.unknown().optional(),
40
+ totalCount: z.number().optional(),
41
+ filteredCount: z.number().optional(),
42
+ count: z.number().optional(),
43
+ stores: z.array(z.unknown()).optional(),
44
+ };
45
+ }
46
+ if (toolName.includes('places')) {
47
+ return {
48
+ provider: z.string().optional(),
49
+ searchMode: z.string().optional(),
50
+ query: z.string().optional(),
51
+ category: z.string().optional(),
52
+ location: z.string().optional(),
53
+ totalCount: z.number().optional(),
54
+ count: z.number().optional(),
55
+ places: z.array(z.unknown()).optional(),
56
+ };
57
+ }
58
+ if (toolName.includes('inventory')) {
59
+ return {
60
+ productId: z.string().optional(),
61
+ keyword: z.string().optional(),
62
+ itemCode: z.string().nullable().optional(),
63
+ location: z.unknown().optional(),
64
+ product: z.unknown().optional(),
65
+ onlineStock: z.number().optional(),
66
+ inventory: z.unknown().optional(),
67
+ storeInventory: z.unknown().optional(),
68
+ };
69
+ }
70
+ if (toolName.includes('display_location')) {
71
+ return {
72
+ productId: z.string().optional(),
73
+ storeCode: z.string().optional(),
74
+ displayLocation: z.unknown().optional(),
75
+ locations: z.array(z.unknown()).optional(),
76
+ };
77
+ }
78
+ if (toolName.includes('price_info')) {
79
+ return {
80
+ productId: z.string().optional(),
81
+ productName: z.string().optional(),
82
+ product: z.unknown().optional(),
83
+ price: z.number().optional(),
84
+ };
85
+ }
86
+ if (toolName.includes('theater')) {
87
+ return {
88
+ keyword: z.string().optional(),
89
+ location: z.unknown().optional(),
90
+ count: z.number().optional(),
91
+ theaters: z.array(z.unknown()).optional(),
92
+ };
93
+ }
94
+ if (toolName.includes('movie') || toolName.includes('now_showing')) {
95
+ return {
96
+ keyword: z.string().optional(),
97
+ playDate: z.string().optional(),
98
+ theaterCode: z.string().optional(),
99
+ count: z.number().optional(),
100
+ movies: z.array(z.unknown()).optional(),
101
+ };
102
+ }
103
+ if (toolName.includes('timetable') || toolName.includes('remaining_seats')) {
104
+ return {
105
+ playDate: z.string().optional(),
106
+ theaterCode: z.string().optional(),
107
+ movieCode: z.string().optional(),
108
+ count: z.number().optional(),
109
+ showtimes: z.array(z.unknown()).optional(),
110
+ seats: z.array(z.unknown()).optional(),
111
+ };
112
+ }
113
+ if (toolName.includes('popwords')) {
114
+ return {
115
+ label: z.string().optional(),
116
+ keywords: z.array(z.string()).optional(),
117
+ count: z.number().optional(),
118
+ };
119
+ }
120
+ if (toolName.includes('catalog')) {
121
+ return {
122
+ pages: z.array(z.unknown()).optional(),
123
+ issues: z.array(z.unknown()).optional(),
124
+ exhibitions: z.array(z.unknown()).optional(),
125
+ count: z.number().optional(),
126
+ };
127
+ }
128
+ return {};
129
+ }
130
+ export function createToolOutputSchema(toolName, outputSchema) {
131
+ const schema = outputSchema || createFallbackOutputSchema(toolName);
132
+ if (Object.keys(schema).length === 0) {
133
+ return DEFAULT_OUTPUT_SCHEMA;
134
+ }
135
+ return z
136
+ .object({
137
+ ...schema,
138
+ standard: z.unknown().optional().describe('서비스 공통 상품/매장/영화관 정규화 결과'),
139
+ })
140
+ .loose();
141
+ }
@@ -3,145 +3,8 @@
3
3
  *
4
4
  * 서비스 프로바이더를 등록하고 MCP 서버에 도구를 연결합니다.
5
5
  */
6
- import * as z from 'zod';
7
6
  import { getErrorMessage, toStandardErrorDiagnostics } from './errors.js';
8
- const DEFAULT_OUTPUT_SCHEMA = z.object({}).loose().describe('도구 실행 결과(JSON 객체)');
9
- function createFallbackOutputSchema(toolName) {
10
- if (toolName.includes('compare')) {
11
- return {
12
- keyword: z.string().optional(),
13
- services: z.array(z.string()).optional(),
14
- serviceCount: z.number().optional(),
15
- resultCount: z.number().optional(),
16
- bestPrice: z.unknown().optional(),
17
- results: z.array(z.unknown()).optional(),
18
- errors: z.array(z.unknown()).optional(),
19
- note: z.string().optional(),
20
- };
21
- }
22
- if (toolName.includes('search_products') ||
23
- toolName.includes('searchProducts') ||
24
- toolName.endsWith('_products')) {
25
- return {
26
- keyword: z.string().optional(),
27
- query: z.string().optional(),
28
- page: z.number().optional(),
29
- pageSize: z.number().optional(),
30
- totalCount: z.number().optional(),
31
- count: z.number().optional(),
32
- products: z.array(z.unknown()).optional(),
33
- note: z.string().optional(),
34
- };
35
- }
36
- if (toolName.includes('find_stores') ||
37
- toolName.includes('nearby_stores') ||
38
- toolName.includes('search_stores')) {
39
- return {
40
- keyword: z.string().optional(),
41
- searchParams: z.unknown().optional(),
42
- location: z.unknown().optional(),
43
- totalCount: z.number().optional(),
44
- filteredCount: z.number().optional(),
45
- count: z.number().optional(),
46
- stores: z.array(z.unknown()).optional(),
47
- };
48
- }
49
- if (toolName.includes('places')) {
50
- return {
51
- provider: z.string().optional(),
52
- searchMode: z.string().optional(),
53
- query: z.string().optional(),
54
- category: z.string().optional(),
55
- location: z.string().optional(),
56
- totalCount: z.number().optional(),
57
- count: z.number().optional(),
58
- places: z.array(z.unknown()).optional(),
59
- };
60
- }
61
- if (toolName.includes('inventory')) {
62
- return {
63
- productId: z.string().optional(),
64
- keyword: z.string().optional(),
65
- itemCode: z.string().nullable().optional(),
66
- location: z.unknown().optional(),
67
- product: z.unknown().optional(),
68
- onlineStock: z.number().optional(),
69
- inventory: z.unknown().optional(),
70
- storeInventory: z.unknown().optional(),
71
- };
72
- }
73
- if (toolName.includes('display_location')) {
74
- return {
75
- productId: z.string().optional(),
76
- storeCode: z.string().optional(),
77
- displayLocation: z.unknown().optional(),
78
- locations: z.array(z.unknown()).optional(),
79
- };
80
- }
81
- if (toolName.includes('price_info')) {
82
- return {
83
- productId: z.string().optional(),
84
- productName: z.string().optional(),
85
- product: z.unknown().optional(),
86
- price: z.number().optional(),
87
- };
88
- }
89
- if (toolName.includes('theater')) {
90
- return {
91
- keyword: z.string().optional(),
92
- location: z.unknown().optional(),
93
- count: z.number().optional(),
94
- theaters: z.array(z.unknown()).optional(),
95
- };
96
- }
97
- if (toolName.includes('movie') || toolName.includes('now_showing')) {
98
- return {
99
- keyword: z.string().optional(),
100
- playDate: z.string().optional(),
101
- theaterCode: z.string().optional(),
102
- count: z.number().optional(),
103
- movies: z.array(z.unknown()).optional(),
104
- };
105
- }
106
- if (toolName.includes('timetable') || toolName.includes('remaining_seats')) {
107
- return {
108
- playDate: z.string().optional(),
109
- theaterCode: z.string().optional(),
110
- movieCode: z.string().optional(),
111
- count: z.number().optional(),
112
- showtimes: z.array(z.unknown()).optional(),
113
- seats: z.array(z.unknown()).optional(),
114
- };
115
- }
116
- if (toolName.includes('popwords')) {
117
- return {
118
- label: z.string().optional(),
119
- keywords: z.array(z.string()).optional(),
120
- count: z.number().optional(),
121
- };
122
- }
123
- if (toolName.includes('catalog')) {
124
- return {
125
- pages: z.array(z.unknown()).optional(),
126
- issues: z.array(z.unknown()).optional(),
127
- exhibitions: z.array(z.unknown()).optional(),
128
- count: z.number().optional(),
129
- };
130
- }
131
- return {};
132
- }
133
- function createToolOutputSchema(toolName, outputSchema) {
134
- const schema = outputSchema || createFallbackOutputSchema(toolName);
135
- if (Object.keys(schema).length === 0) {
136
- return DEFAULT_OUTPUT_SCHEMA;
137
- }
138
- return z
139
- .object({
140
- ...schema,
141
- standard: z.unknown().optional().describe('서비스 공통 상품/매장/영화관 정규화 결과'),
142
- })
143
- .loose();
144
- }
7
+ import { createToolOutputSchema } from './outputSchema.js';
145
8
  function parseJsonText(text) {
146
9
  try {
147
10
  const parsed = JSON.parse(text);