smart-web-mcp 0.38.1 → 0.40.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/CHANGELOG.md +17 -0
- package/README.md +10 -8
- package/dist/korea/smartvertical.d.ts +64 -0
- package/dist/korea/smartvertical.js +272 -0
- package/dist/korea/smartvertical.js.map +1 -0
- package/dist/mcp-server.js +63 -437
- package/dist/mcp-server.js.map +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.js +1 -0
- package/dist/settings.js.map +1 -1
- package/dist/smartfetch/providers/x.js +140 -0
- package/dist/smartfetch/providers/x.js.map +1 -1
- package/package.json +1 -1
package/dist/mcp-server.js
CHANGED
|
@@ -5,7 +5,7 @@ import { SMARTFETCH_TARGETS } from "./smartfetch/targets.js";
|
|
|
5
5
|
import { projectSmartcrawlOutput } from "./smartcrawl.js";
|
|
6
6
|
import { resolveSmartfetchOutputBudget } from "./smartfetch.js";
|
|
7
7
|
import { projectSmartsearchOutput } from "./smartsearch.js";
|
|
8
|
-
import {
|
|
8
|
+
import { SMARTVERTICAL_DOMAINS, renderSmartvertical, runSmartvertical } from "./korea/smartvertical.js";
|
|
9
9
|
const ISSUE_REPORT_GUIDANCE = "If smart-web returns reproducible incorrect or misleading output, open a GitHub issue on jojo-labs/smart-web with the exact input, arguments, observed output, expected behavior, and version. Skip obvious transient network/auth/rate-limit failures unless smart-web misclassified them.";
|
|
10
10
|
function capMcpText(text, maxCharacters, label) {
|
|
11
11
|
if (maxCharacters === undefined || text.length <= maxCharacters)
|
|
@@ -242,444 +242,70 @@ export function createSmartWebServer(version, runtime) {
|
|
|
242
242
|
content: [{ type: "text", text: requestedFormat === "json" ? JSON.stringify(outputWithBudgetEvidence, null, 2) : await runtime.renderSmartfetch(outputWithBudgetEvidence, requestedFormat) }],
|
|
243
243
|
};
|
|
244
244
|
});
|
|
245
|
-
// ───
|
|
246
|
-
server.registerTool("
|
|
247
|
-
title: "
|
|
248
|
-
description: "
|
|
245
|
+
// ─── Vertical tools ───────────────────────────────────────────────────────
|
|
246
|
+
server.registerTool("smartvertical", {
|
|
247
|
+
title: "Smart Vertical",
|
|
248
|
+
description: "Korean/local vertical and structured data router. Use domain to access places, weather, subway, finance, DART, real estate, safety, news, commerce, spellcheck, zipcode, law, public restrooms, and GeekNews without exposing every provider as a separate MCP tool.",
|
|
249
249
|
inputSchema: {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
domain: z.enum(SMARTVERTICAL_DOMAINS).describe("Vertical domain: geeknews, subway, weather, fine_dust, stock, dart, real_estate, drug_safety, food_safety, news, aliexpress, shopping, coupang, spellcheck, zipcode, law, place, or public_restroom."),
|
|
251
|
+
action: z.string().optional().describe("Domain-specific action. Examples: geeknews list/search/detail; stock search/base-info/trade-info; dart disclosures/company/financials; real_estate region-code/transactions; food_safety recall/unfit/functional-ingredient; coupang search/rocket/budget/goldbox; law search/get-text/search-precedents."),
|
|
252
|
+
query: z.string().optional().describe("Primary keyword/search query. Also works as a fallback for domains that need q, product name, law keyword, address keyword, station, or location."),
|
|
253
|
+
id: z.string().optional().describe("Domain-specific identifier, e.g. GeekNews item id."),
|
|
254
|
+
text: z.string().min(1).max(10000).optional().describe("Text input for spellcheck."),
|
|
255
|
+
item_names: z.array(z.string().min(1)).min(1).max(5).optional().describe("Drug names for drug_safety; query can be used for a single item."),
|
|
256
|
+
location: z.string().optional().describe("Location/anchor hint for place, public_restroom, subway, or other local lookups."),
|
|
257
|
+
station_name: z.string().optional().describe("Subway station name or fine-dust station name."),
|
|
258
|
+
station: z.string().optional().describe("Alias for station_name when calling subway."),
|
|
259
|
+
region_hint: z.string().optional().describe("Fine-dust region hint, e.g. 서울 강남구."),
|
|
260
|
+
nx: z.number().int().optional().describe("KMA weather grid X."),
|
|
261
|
+
ny: z.number().int().optional().describe("KMA weather grid Y."),
|
|
262
|
+
lat: z.number().optional().describe("Latitude for weather/place/public_restroom."),
|
|
263
|
+
lon: z.number().optional().describe("Longitude for weather/place/public_restroom."),
|
|
264
|
+
base_date: z.string().optional().describe("Weather base date YYYYMMDD."),
|
|
265
|
+
base_time: z.string().optional().describe("Weather base time HHmm."),
|
|
266
|
+
market: z.enum(["KOSPI", "KOSDAQ", "KONEX"]).optional().describe("KRX market for stock base-info/trade-info."),
|
|
267
|
+
code: z.string().optional().describe("Stock code."),
|
|
268
|
+
bas_date: z.string().optional().describe("KRX base date YYYYMMDD."),
|
|
269
|
+
corp_code: z.string().optional().describe("DART corp code."),
|
|
270
|
+
bgn_de: z.string().optional().describe("DART disclosure begin date YYYYMMDD."),
|
|
271
|
+
end_de: z.string().optional().describe("DART disclosure end date YYYYMMDD."),
|
|
272
|
+
bsns_year: z.string().optional().describe("DART business year."),
|
|
273
|
+
reprt_code: z.string().optional().describe("DART report code."),
|
|
274
|
+
fs_div: z.enum(["OFS", "CFS"]).optional().describe("DART financial statement division."),
|
|
275
|
+
pblntf_ty: z.string().optional().describe("DART disclosure type."),
|
|
276
|
+
lawd_cd: z.string().optional().describe("Real-estate law-dong code."),
|
|
277
|
+
asset_type: z.enum(["apartment", "officetel", "villa", "single-house", "commercial"]).optional().describe("Real-estate asset type."),
|
|
278
|
+
deal_type: z.enum(["trade", "rent"]).optional().describe("Real-estate deal type."),
|
|
279
|
+
deal_ymd: z.string().optional().describe("Real-estate deal month YYYYMM."),
|
|
280
|
+
display: z.number().int().min(1).max(100).optional().describe("News result count."),
|
|
281
|
+
start: z.number().int().min(1).optional().describe("News result offset."),
|
|
282
|
+
sort: z.string().optional().describe("Domain-specific sort, e.g. date/sim, rel/price_asc, blended/distance/rating."),
|
|
283
|
+
locale: z.enum(["ko", "en"]).optional().describe("AliExpress locale."),
|
|
284
|
+
currency: z.enum(["KRW", "USD"]).optional().describe("AliExpress currency hint."),
|
|
285
|
+
ship_to: z.string().min(2).max(2).optional().describe("AliExpress ship-to ISO-2 country."),
|
|
286
|
+
max_price: z.number().int().optional().describe("Coupang budget max price in KRW."),
|
|
287
|
+
law_id: z.string().optional().describe("Law id for law get-text."),
|
|
288
|
+
article: z.string().optional().describe("Law article, e.g. 제38조."),
|
|
289
|
+
radius_m: z.number().int().min(50).max(50000).optional().describe("Place search radius in meters."),
|
|
290
|
+
radius_km: z.number().min(0.1).max(5).optional().describe("Public restroom radius in kilometers."),
|
|
291
|
+
providers: z.array(z.enum(["kakao", "google", "naver"])).min(1).max(3).optional().describe("Place providers."),
|
|
292
|
+
intent: z.enum(["auto", "food", "cafe", "walk", "dog_walk", "errand", "date", "work"]).optional().describe("Place search intent."),
|
|
293
|
+
limit: z.number().int().min(1).max(100).optional().describe("Generic result limit; domain-specific maxes still apply internally."),
|
|
294
|
+
page_count: z.number().int().min(1).max(100).optional().describe("DART page count."),
|
|
295
|
+
num_of_rows: z.number().int().min(1).max(1000).optional().describe("Real-estate rows."),
|
|
296
|
+
timeout_ms: z.number().int().min(3000).max(60000).optional().describe("Domain request timeout in milliseconds."),
|
|
255
297
|
},
|
|
256
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint:
|
|
257
|
-
}, async (
|
|
258
|
-
const
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const r = result;
|
|
268
|
-
return { structuredContent: r, content: [{ type: "text", text: renderGeeknews(r) }] };
|
|
269
|
-
});
|
|
270
|
-
server.registerTool("kr_subway", {
|
|
271
|
-
title: "서울 지하철 도착정보",
|
|
272
|
-
description: "서울 지하철 역 기준 실시간 도착 예정 열차 조회 (k-skill-proxy 경유). 역명을 입력하면 상/하행 첫·두번째 도착 열차를 반환.",
|
|
273
|
-
inputSchema: {
|
|
274
|
-
station_name: z.string().min(1).describe("역명 (예: 강남, 서울역, 잠실)"),
|
|
275
|
-
limit: z.number().int().min(1).max(50).optional(),
|
|
276
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
277
|
-
},
|
|
278
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
279
|
-
}, async ({ station_name, limit, timeout_ms }) => {
|
|
280
|
-
const result = await runSubway({
|
|
281
|
-
stationName: station_name,
|
|
282
|
-
...(limit === undefined ? {} : { limit }),
|
|
283
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
284
|
-
});
|
|
285
|
-
if ("ok" in result && result.ok === false)
|
|
286
|
-
throw new Error(result.message);
|
|
287
|
-
const r = result;
|
|
288
|
-
return { structuredContent: r, content: [{ type: "text", text: renderSubway(r) }] };
|
|
289
|
-
});
|
|
290
|
-
server.registerTool("kr_weather", {
|
|
291
|
-
title: "한국 날씨",
|
|
292
|
-
description: "기상청 단기예보 조회 (k-skill-proxy 경유). 격자좌표(nx/ny) 또는 위경도(lat/lon)를 입력.",
|
|
293
|
-
inputSchema: {
|
|
294
|
-
nx: z.number().int().optional().describe("기상청 격자 X (nx)"),
|
|
295
|
-
ny: z.number().int().optional().describe("기상청 격자 Y (ny)"),
|
|
296
|
-
lat: z.number().optional().describe("위도"),
|
|
297
|
-
lon: z.number().optional().describe("경도"),
|
|
298
|
-
base_date: z.string().optional().describe("YYYYMMDD"),
|
|
299
|
-
base_time: z.string().optional().describe("HHmm (예: 0600)"),
|
|
300
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
301
|
-
},
|
|
302
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
303
|
-
}, async ({ nx, ny, lat, lon, base_date, base_time, timeout_ms }) => {
|
|
304
|
-
const result = await runWeather({
|
|
305
|
-
...(nx === undefined ? {} : { nx }),
|
|
306
|
-
...(ny === undefined ? {} : { ny }),
|
|
307
|
-
...(lat === undefined ? {} : { lat }),
|
|
308
|
-
...(lon === undefined ? {} : { lon }),
|
|
309
|
-
...(base_date === undefined ? {} : { baseDate: base_date }),
|
|
310
|
-
...(base_time === undefined ? {} : { baseTime: base_time }),
|
|
311
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
312
|
-
});
|
|
313
|
-
if ("ok" in result && result.ok === false)
|
|
314
|
-
throw new Error(result.message);
|
|
315
|
-
const r = result;
|
|
316
|
-
return { structuredContent: r, content: [{ type: "text", text: renderWeather(r) }] };
|
|
317
|
-
});
|
|
318
|
-
server.registerTool("kr_fine_dust", {
|
|
319
|
-
title: "미세먼지",
|
|
320
|
-
description: "에어코리아 기반 PM10/PM2.5/통합대기등급 조회 (k-skill-proxy 경유). 지역명 힌트 또는 측정소명 입력.",
|
|
321
|
-
inputSchema: {
|
|
322
|
-
region_hint: z.string().optional().describe("지역명 힌트 (예: 서울 강남구, 종로구)"),
|
|
323
|
-
station_name: z.string().optional().describe("정확한 측정소명 (ambiguous 응답 후 재조회용)"),
|
|
324
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
325
|
-
},
|
|
326
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
327
|
-
}, async ({ region_hint, station_name, timeout_ms }) => {
|
|
328
|
-
const result = await runFineDust({
|
|
329
|
-
...(region_hint === undefined ? {} : { regionHint: region_hint }),
|
|
330
|
-
...(station_name === undefined ? {} : { stationName: station_name }),
|
|
331
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
332
|
-
});
|
|
333
|
-
if ("ok" in result && result.ok === false)
|
|
334
|
-
throw new Error(result.message);
|
|
335
|
-
const r = result;
|
|
336
|
-
return { structuredContent: r, content: [{ type: "text", text: renderFineDust(r) }] };
|
|
337
|
-
});
|
|
338
|
-
server.registerTool("kr_stock", {
|
|
339
|
-
title: "한국 주식",
|
|
340
|
-
description: "KRX 상장 종목 검색·기본정보·일별 시세 조회 (k-skill-proxy 경유). 사용자가 KRX API 키를 발급할 필요 없음.",
|
|
341
|
-
inputSchema: {
|
|
342
|
-
action: z.enum(["search", "base-info", "trade-info"]).describe("search: 종목 검색, base-info: 기본정보, trade-info: 일별 시세"),
|
|
343
|
-
q: z.string().optional().describe("종목명 또는 코드 (search용)"),
|
|
344
|
-
market: z.enum(["KOSPI", "KOSDAQ", "KONEX"]).optional().describe("시장구분 (base-info/trade-info용)"),
|
|
345
|
-
code: z.string().optional().describe("6자리 종목코드 (base-info/trade-info용)"),
|
|
346
|
-
bas_date: z.string().optional().describe("기준일 YYYYMMDD (기본: 오늘 KST)"),
|
|
347
|
-
limit: z.number().int().min(1).max(20).optional(),
|
|
348
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
349
|
-
},
|
|
350
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
351
|
-
}, async ({ action, q, market, code, bas_date, limit, timeout_ms }) => {
|
|
352
|
-
const result = await runStock({
|
|
353
|
-
action,
|
|
354
|
-
...(q === undefined ? {} : { q }),
|
|
355
|
-
...(market === undefined ? {} : { market }),
|
|
356
|
-
...(code === undefined ? {} : { code }),
|
|
357
|
-
...(bas_date === undefined ? {} : { basDate: bas_date }),
|
|
358
|
-
...(limit === undefined ? {} : { limit }),
|
|
359
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
360
|
-
});
|
|
361
|
-
if ("ok" in result && result.ok === false)
|
|
362
|
-
throw new Error(result.message);
|
|
363
|
-
const r = result;
|
|
364
|
-
return { structuredContent: r, content: [{ type: "text", text: renderStock(r) }] };
|
|
365
|
-
});
|
|
366
|
-
server.registerTool("kr_dart", {
|
|
367
|
-
title: "금감원 DART 전자공시",
|
|
368
|
-
description: "금감원 DART OpenAPI로 공시검색·기업개황·재무제표 조회. 환경변수 API_K_DART가 필요 (https://opendart.fss.or.kr 에서 무료 발급).",
|
|
369
|
-
inputSchema: {
|
|
370
|
-
action: z.enum(["disclosures", "company", "financials"]).describe("disclosures: 공시 목록, company: 기업개황, financials: 재무제표"),
|
|
371
|
-
corp_code: z.string().optional().describe("DART 고유번호 8자리"),
|
|
372
|
-
bgn_de: z.string().optional().describe("공시 시작일 YYYYMMDD"),
|
|
373
|
-
end_de: z.string().optional().describe("공시 종료일 YYYYMMDD"),
|
|
374
|
-
bsns_year: z.string().optional().describe("사업연도 YYYY (financials용)"),
|
|
375
|
-
reprt_code: z.string().optional().describe("보고서 종류 (11011=사업보고서, 11012=반기, 11013=1분기, 11014=3분기)"),
|
|
376
|
-
fs_div: z.enum(["OFS", "CFS"]).optional().describe("재무제표 구분 (OFS=개별, CFS=연결)"),
|
|
377
|
-
pblntf_ty: z.string().optional().describe("공시유형 (A=정기, B=주요사항 등)"),
|
|
378
|
-
page_count: z.number().int().min(1).max(100).optional(),
|
|
379
|
-
timeout_ms: z.number().int().min(3000).max(60000).optional(),
|
|
380
|
-
},
|
|
381
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
382
|
-
}, async ({ action, corp_code, bgn_de, end_de, bsns_year, reprt_code, fs_div, pblntf_ty, page_count, timeout_ms }) => {
|
|
383
|
-
const result = await runDart({
|
|
384
|
-
action,
|
|
385
|
-
...(corp_code === undefined ? {} : { corp_code }),
|
|
386
|
-
...(bgn_de === undefined ? {} : { bgn_de }),
|
|
387
|
-
...(end_de === undefined ? {} : { end_de }),
|
|
388
|
-
...(bsns_year === undefined ? {} : { bsns_year }),
|
|
389
|
-
...(reprt_code === undefined ? {} : { reprt_code }),
|
|
390
|
-
...(fs_div === undefined ? {} : { fs_div }),
|
|
391
|
-
...(pblntf_ty === undefined ? {} : { pblntf_ty }),
|
|
392
|
-
...(page_count === undefined ? {} : { page_count }),
|
|
393
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
394
|
-
});
|
|
395
|
-
if ("ok" in result && result.ok === false)
|
|
396
|
-
throw new Error(result.message);
|
|
397
|
-
const r = result;
|
|
398
|
-
return { structuredContent: r, content: [{ type: "text", text: renderDart(r) }] };
|
|
399
|
-
});
|
|
400
|
-
server.registerTool("kr_real_estate", {
|
|
401
|
-
title: "부동산 실거래가",
|
|
402
|
-
description: "국토교통부 기반 아파트/오피스텔/빌라/단독주택 실거래가·전월세 조회 (k-skill-proxy 경유). region-code로 법정동코드를 먼저 확인.",
|
|
403
|
-
inputSchema: {
|
|
404
|
-
action: z.enum(["region-code", "transactions"]).describe("region-code: 지역코드 조회, transactions: 실거래 조회"),
|
|
405
|
-
q: z.string().optional().describe("지역명 (region-code용, 예: 강남구)"),
|
|
406
|
-
lawd_cd: z.string().optional().describe("5자리 법정동코드 (transactions용)"),
|
|
407
|
-
asset_type: z.enum(["apartment", "officetel", "villa", "single-house", "commercial"]).optional().describe("자산유형 (기본: apartment)"),
|
|
408
|
-
deal_type: z.enum(["trade", "rent"]).optional().describe("거래유형 (기본: trade)"),
|
|
409
|
-
deal_ymd: z.string().optional().describe("거래년월 YYYYMM (기본: 전월)"),
|
|
410
|
-
num_of_rows: z.number().int().min(1).max(1000).optional(),
|
|
411
|
-
timeout_ms: z.number().int().min(3000).max(60000).optional(),
|
|
412
|
-
},
|
|
413
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
414
|
-
}, async ({ action, q, lawd_cd, asset_type, deal_type, deal_ymd, num_of_rows, timeout_ms }) => {
|
|
415
|
-
const result = await runRealEstate({
|
|
416
|
-
action,
|
|
417
|
-
...(q === undefined ? {} : { q }),
|
|
418
|
-
...(lawd_cd === undefined ? {} : { lawd_cd }),
|
|
419
|
-
...(asset_type === undefined ? {} : { asset_type }),
|
|
420
|
-
...(deal_type === undefined ? {} : { deal_type }),
|
|
421
|
-
...(deal_ymd === undefined ? {} : { deal_ymd }),
|
|
422
|
-
...(num_of_rows === undefined ? {} : { num_of_rows }),
|
|
423
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
424
|
-
});
|
|
425
|
-
if ("ok" in result && result.ok === false)
|
|
426
|
-
throw new Error(result.message);
|
|
427
|
-
const r = result;
|
|
428
|
-
return { structuredContent: r, content: [{ type: "text", text: renderRealEstate(r) }] };
|
|
429
|
-
});
|
|
430
|
-
server.registerTool("kr_drug_safety", {
|
|
431
|
-
title: "의약품 안전 체크",
|
|
432
|
-
description: "식약처 e약은요 의약품 효능·용법·주의사항·상호작용 조회 (k-skill-proxy 경유). 호흡곤란·의식저하 등 응급 증상은 119 우선.",
|
|
433
|
-
inputSchema: {
|
|
434
|
-
item_names: z.array(z.string().min(1)).min(1).max(5).describe("의약품명 목록 (예: ['타이레놀', '판콜'])"),
|
|
435
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
436
|
-
},
|
|
437
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
438
|
-
}, async ({ item_names, timeout_ms }) => {
|
|
439
|
-
const result = await runDrugSafety({
|
|
440
|
-
itemNames: item_names,
|
|
441
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
442
|
-
});
|
|
443
|
-
if ("ok" in result && result.ok === false)
|
|
444
|
-
throw new Error(result.message);
|
|
445
|
-
const r = result;
|
|
446
|
-
return { structuredContent: r, content: [{ type: "text", text: renderDrugSafety(r) }] };
|
|
447
|
-
});
|
|
448
|
-
server.registerTool("kr_food_safety", {
|
|
449
|
-
title: "식품 안전 체크",
|
|
450
|
-
description: "식약처 식품안전나라 회수/부적합/기능성원료 조회 (k-skill-proxy 경유). 혈변·탈수·호흡곤란 등 응급 증상은 119 우선.",
|
|
451
|
-
inputSchema: {
|
|
452
|
-
action: z.enum(["recall", "unfit", "functional-ingredient"]).describe("recall: 회수·판매중지, unfit: 검사부적합, functional-ingredient: 건강기능식품 원료"),
|
|
453
|
-
query: z.string().min(1).describe("제품명 또는 검색어"),
|
|
454
|
-
limit: z.number().int().min(1).max(50).optional(),
|
|
455
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
456
|
-
},
|
|
457
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
458
|
-
}, async ({ action, query, limit, timeout_ms }) => {
|
|
459
|
-
const result = await runFoodSafety({
|
|
460
|
-
action,
|
|
461
|
-
query,
|
|
462
|
-
...(limit === undefined ? {} : { limit }),
|
|
463
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
464
|
-
});
|
|
465
|
-
if ("ok" in result && result.ok === false)
|
|
466
|
-
throw new Error(result.message);
|
|
467
|
-
const r = result;
|
|
468
|
-
return { structuredContent: r, content: [{ type: "text", text: renderFoodSafety(r) }] };
|
|
469
|
-
});
|
|
470
|
-
server.registerTool("kr_naver_news", {
|
|
471
|
-
title: "네이버 뉴스 검색",
|
|
472
|
-
description: "네이버 검색 Open API 뉴스 검색 (k-skill-proxy 경유). 기사 제목·요약·발행시각·링크 반환. 사용자가 API 키를 발급할 필요 없음.",
|
|
473
|
-
inputSchema: {
|
|
474
|
-
query: z.string().min(2).describe("검색어 (2글자 이상)"),
|
|
475
|
-
display: z.number().int().min(1).max(100).optional().describe("반환 건수 (기본 10)"),
|
|
476
|
-
start: z.number().int().min(1).optional().describe("검색 시작위치 (start + display - 1 ≤ 1000)"),
|
|
477
|
-
sort: z.enum(["sim", "date"]).optional().describe("sim: 유사도순, date: 최신순 (기본 date)"),
|
|
478
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
479
|
-
},
|
|
480
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
481
|
-
}, async ({ query, display, start, sort, timeout_ms }) => {
|
|
482
|
-
const result = await runNaverNews({
|
|
483
|
-
query,
|
|
484
|
-
...(display === undefined ? {} : { display }),
|
|
485
|
-
...(start === undefined ? {} : { start }),
|
|
486
|
-
...(sort === undefined ? {} : { sort }),
|
|
487
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
488
|
-
});
|
|
489
|
-
if ("ok" in result && result.ok === false)
|
|
490
|
-
throw new Error(result.message);
|
|
491
|
-
const r = result;
|
|
492
|
-
return { structuredContent: r, content: [{ type: "text", text: renderNaverNews(r) }] };
|
|
493
|
-
});
|
|
494
|
-
server.registerTool("kr_aliexpress", {
|
|
495
|
-
title: "AliExpress 상품검색",
|
|
496
|
-
description: "AliExpress 공개 검색 페이지에서 상품 후보·현재 노출가·상품 링크를 추출. 비로그인/비제휴/no-secret 경로이며 배송비·쿠폰·통관·환율·재고는 별도 확인 필요.",
|
|
497
|
-
inputSchema: {
|
|
498
|
-
query: z.string().min(2).describe("상품명 또는 검색어"),
|
|
499
|
-
limit: z.number().int().min(1).max(30).optional().describe("반환 건수 (기본 10)"),
|
|
500
|
-
locale: z.enum(["ko", "en"]).optional().describe("AliExpress locale/host (기본 ko)"),
|
|
501
|
-
currency: z.enum(["KRW", "USD"]).optional().describe("가격 표시 통화 힌트 (기본 KRW)"),
|
|
502
|
-
ship_to: z.string().min(2).max(2).optional().describe("배송 국가 ISO-2 코드 (기본 KR)"),
|
|
503
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
504
|
-
},
|
|
505
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
506
|
-
}, async ({ query, limit, locale, currency, ship_to, timeout_ms }) => {
|
|
507
|
-
const result = await runAliExpress({
|
|
508
|
-
query,
|
|
509
|
-
...(limit === undefined ? {} : { limit }),
|
|
510
|
-
...(locale === undefined ? {} : { locale }),
|
|
511
|
-
...(currency === undefined ? {} : { currency }),
|
|
512
|
-
...(ship_to === undefined ? {} : { shipTo: ship_to }),
|
|
513
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
514
|
-
});
|
|
515
|
-
if ("ok" in result && result.ok === false)
|
|
516
|
-
throw new Error(result.message);
|
|
517
|
-
const r = result;
|
|
518
|
-
return { structuredContent: r, content: [{ type: "text", text: renderAliExpress(r) }] };
|
|
519
|
-
});
|
|
520
|
-
server.registerTool("kr_naver_shopping", {
|
|
521
|
-
title: "네이버 쇼핑 가격비교",
|
|
522
|
-
description: "네이버 쇼핑 상품 후보·현재 노출가·판매처 링크 비교 (k-skill-proxy 경유). 배송비·쿠폰·회원 혜택은 비로그인 기준으로 미반영.",
|
|
523
|
-
inputSchema: {
|
|
524
|
-
query: z.string().min(2).describe("상품명 또는 검색어"),
|
|
525
|
-
limit: z.number().int().min(1).max(40).optional().describe("반환 건수 (기본 10)"),
|
|
526
|
-
sort: z.enum(["rel", "date", "price_asc", "price_dsc", "review"]).optional().describe("정렬 (기본 rel)"),
|
|
527
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
528
|
-
},
|
|
529
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
530
|
-
}, async ({ query, limit, sort, timeout_ms }) => {
|
|
531
|
-
const result = await runNaverShopping({
|
|
532
|
-
query,
|
|
533
|
-
...(limit === undefined ? {} : { limit }),
|
|
534
|
-
...(sort === undefined ? {} : { sort }),
|
|
535
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
536
|
-
});
|
|
537
|
-
if ("ok" in result && result.ok === false)
|
|
538
|
-
throw new Error(result.message);
|
|
539
|
-
const r = result;
|
|
540
|
-
return { structuredContent: r, content: [{ type: "text", text: renderNaverShopping(r) }] };
|
|
541
|
-
});
|
|
542
|
-
server.registerTool("kr_coupang", {
|
|
543
|
-
title: "쿠팡 상품검색",
|
|
544
|
-
description: "쿠팡 키워드 상품검색·로켓배송 필터·가격대 검색·골드박스 특가 조회. Retention Corp 호스팅 경로(a.retn.kr) 경유로 anti-bot 우회. affiliate 고지 포함 응답.",
|
|
545
|
-
inputSchema: {
|
|
546
|
-
action: z.enum(["search", "rocket", "budget", "goldbox"]).optional().describe("search: 일반 검색, rocket: 로켓배송만, budget: 가격대 검색, goldbox: 당일 특가"),
|
|
547
|
-
query: z.string().optional().describe("검색어 (goldbox 제외 필수)"),
|
|
548
|
-
max_price: z.number().int().optional().describe("최대 가격 (budget 모드, 원 단위)"),
|
|
549
|
-
limit: z.number().int().min(1).max(20).optional(),
|
|
550
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
551
|
-
},
|
|
552
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
553
|
-
}, async ({ action, query, max_price, limit, timeout_ms }) => {
|
|
554
|
-
const result = await runCoupang({
|
|
555
|
-
...(action === undefined ? {} : { action }),
|
|
556
|
-
...(query === undefined ? {} : { query }),
|
|
557
|
-
...(max_price === undefined ? {} : { maxPrice: max_price }),
|
|
558
|
-
...(limit === undefined ? {} : { limit }),
|
|
559
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
560
|
-
});
|
|
561
|
-
if ("ok" in result && result.ok === false)
|
|
562
|
-
throw new Error(result.message);
|
|
563
|
-
const r = result;
|
|
564
|
-
return { structuredContent: r, content: [{ type: "text", text: renderCoupang(r) }] };
|
|
565
|
-
});
|
|
566
|
-
server.registerTool("kr_spellcheck", {
|
|
567
|
-
title: "한국어 맞춤법 검사",
|
|
568
|
-
description: "바른한글(구 부산대 맞춤법/문법 검사기) 공개 표면 경유 한국어 맞춤법 교정. 비상업적·저빈도 사용 전용. 10,000자 이하.",
|
|
569
|
-
inputSchema: {
|
|
570
|
-
text: z.string().min(1).max(10000).describe("교정할 한국어 텍스트"),
|
|
571
|
-
timeout_ms: z.number().int().min(3000).max(60000).optional(),
|
|
572
|
-
},
|
|
573
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
574
|
-
}, async ({ text, timeout_ms }) => {
|
|
575
|
-
const result = await runSpellCheck({
|
|
576
|
-
text,
|
|
577
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
578
|
-
});
|
|
579
|
-
if ("ok" in result && result.ok === false)
|
|
580
|
-
throw new Error(result.message);
|
|
581
|
-
const r = result;
|
|
582
|
-
return { structuredContent: r, content: [{ type: "text", text: renderSpellCheck(r) }] };
|
|
583
|
-
});
|
|
584
|
-
server.registerTool("kr_zipcode", {
|
|
585
|
-
title: "우편번호 검색",
|
|
586
|
-
description: "우체국 공식 통합 우편번호 검색 페이지(epost.kr) 경유 우편번호 + 공식 영문주소 조회.",
|
|
587
|
-
inputSchema: {
|
|
588
|
-
query: z.string().min(2).describe("주소 키워드 (예: 서울특별시 강남구 테헤란로 123)"),
|
|
589
|
-
limit: z.number().int().min(1).max(10).optional().describe("반환 건수 (기본 5)"),
|
|
590
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
591
|
-
},
|
|
592
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
593
|
-
}, async ({ query, limit, timeout_ms }) => {
|
|
594
|
-
const result = await runZipcode({
|
|
595
|
-
query,
|
|
596
|
-
...(limit === undefined ? {} : { limit }),
|
|
597
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
598
|
-
});
|
|
599
|
-
if ("ok" in result && result.ok === false)
|
|
600
|
-
throw new Error(result.message);
|
|
601
|
-
const r = result;
|
|
602
|
-
return { structuredContent: r, content: [{ type: "text", text: renderZipcode(r) }] };
|
|
603
|
-
});
|
|
604
|
-
server.registerTool("kr_law", {
|
|
605
|
-
title: "한국 법령 검색",
|
|
606
|
-
description: "법망(api.beopmang.org) 공개 REST API로 한국 법령 검색·조문 본문 조회·판례 검색. API 키 불필요.",
|
|
607
|
-
inputSchema: {
|
|
608
|
-
action: z.enum(["search", "get-text", "search-precedents"]).describe("search: 법령명 검색, get-text: 조문 본문, search-precedents: 판례 검색"),
|
|
609
|
-
query: z.string().optional().describe("검색어 (search / search-precedents용)"),
|
|
610
|
-
law_id: z.string().optional().describe("법령 ID (get-text용, search로 먼저 확인)"),
|
|
611
|
-
article: z.string().optional().describe("조문 (예: 제38조)"),
|
|
612
|
-
limit: z.number().int().min(1).max(30).optional(),
|
|
613
|
-
timeout_ms: z.number().int().min(3000).max(30000).optional(),
|
|
614
|
-
},
|
|
615
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
616
|
-
}, async ({ action, query, law_id, article, limit, timeout_ms }) => {
|
|
617
|
-
const result = await runLaw({
|
|
618
|
-
action,
|
|
619
|
-
...(query === undefined ? {} : { query }),
|
|
620
|
-
...(law_id === undefined ? {} : { law_id }),
|
|
621
|
-
...(article === undefined ? {} : { article }),
|
|
622
|
-
...(limit === undefined ? {} : { limit }),
|
|
623
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
624
|
-
});
|
|
625
|
-
if ("ok" in result && result.ok === false)
|
|
626
|
-
throw new Error(result.message);
|
|
627
|
-
const r = result;
|
|
628
|
-
return { structuredContent: r, content: [{ type: "text", text: renderLaw(r) }] };
|
|
629
|
-
});
|
|
630
|
-
server.registerTool("kr_place_search", {
|
|
631
|
-
title: "한국 지도 장소 검색",
|
|
632
|
-
description: "카카오맵/구글 Places/네이버 로컬을 병합해 한국 내 맛집·카페·산책·강아지 산책·근처 장소 후보를 거리/평점/리뷰수 기준으로 추천. 위치 힌트 또는 위경도 입력 가능.",
|
|
633
|
-
inputSchema: {
|
|
634
|
-
query: z.string().optional().describe("검색어 (예: 삼겹살, 조용한 카페, 강아지 산책). 비우면 intent 기본값 사용"),
|
|
635
|
-
location: z.string().optional().describe("위치 힌트/기준점 (예: CU 성남만남밀알점, 하대원동, 현재 위치명)"),
|
|
636
|
-
lat: z.number().optional().describe("위도 (직접 입력 시 위치 해석 없이 거리 기준 검색)"),
|
|
637
|
-
lon: z.number().optional().describe("경도"),
|
|
638
|
-
radius_m: z.number().int().min(50).max(50000).optional().describe("검색 반경 m (기본 1500)"),
|
|
639
|
-
limit: z.number().int().min(1).max(30).optional().describe("반환 건수 (기본 10)"),
|
|
640
|
-
intent: z.enum(["auto", "food", "cafe", "walk", "dog_walk", "errand", "date", "work"]).optional().describe("추천 의도. dog_walk/walk는 공원·산책로·애견동반 후보를 확장 검색"),
|
|
641
|
-
sort: z.enum(["blended", "relevance", "distance", "rating", "review_count"]).optional().describe("정렬 기준 (기본 blended)"),
|
|
642
|
-
providers: z.array(z.enum(["kakao", "google", "naver"])).min(1).max(3).optional().describe("사용할 지도/장소 provider 목록 (기본 kakao+google+naver)"),
|
|
643
|
-
timeout_ms: z.number().int().min(3000).max(60000).optional(),
|
|
644
|
-
},
|
|
645
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
646
|
-
}, async ({ query, location, lat, lon, radius_m, limit, intent, sort, providers, timeout_ms }) => {
|
|
647
|
-
const r = await runPlaceSearch({
|
|
648
|
-
...(query === undefined ? {} : { query }),
|
|
649
|
-
...(location === undefined ? {} : { location }),
|
|
650
|
-
...(lat === undefined ? {} : { lat }),
|
|
651
|
-
...(lon === undefined ? {} : { lon }),
|
|
652
|
-
...(radius_m === undefined ? {} : { radiusM: radius_m }),
|
|
653
|
-
...(limit === undefined ? {} : { limit }),
|
|
654
|
-
...(intent === undefined ? {} : { intent }),
|
|
655
|
-
...(sort === undefined ? {} : { sort }),
|
|
656
|
-
...(providers === undefined ? {} : { providers }),
|
|
657
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
658
|
-
});
|
|
659
|
-
return { structuredContent: r, content: [{ type: "text", text: renderPlaceSearch(r) }] };
|
|
660
|
-
});
|
|
661
|
-
server.registerTool("kr_public_restroom", {
|
|
662
|
-
title: "근처 공중화장실",
|
|
663
|
-
description: "공공데이터포털 공중화장실 표준데이터 기반 근처 공중/개방화장실 조회. 위치 힌트(지명) 또는 위경도 직접 입력. 좌표 해석에 KAKAO_REST_API_KEY 환경변수 사용.",
|
|
664
|
-
inputSchema: {
|
|
665
|
-
location: z.string().min(1).describe("위치 힌트 (예: 광화문, 서울역, 강남구청)"),
|
|
666
|
-
lat: z.number().optional().describe("위도 (직접 입력 시 Kakao 없이 작동)"),
|
|
667
|
-
lon: z.number().optional().describe("경도"),
|
|
668
|
-
radius_km: z.number().min(0.1).max(5).optional().describe("검색 반경 km (기본 1.0)"),
|
|
669
|
-
limit: z.number().int().min(1).max(20).optional().describe("반환 건수 (기본 5)"),
|
|
670
|
-
timeout_ms: z.number().int().min(3000).max(60000).optional(),
|
|
671
|
-
},
|
|
672
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
673
|
-
}, async ({ location, lat, lon, radius_km, limit, timeout_ms }) => {
|
|
674
|
-
const r = await runPublicRestroom({
|
|
675
|
-
location,
|
|
676
|
-
...(lat === undefined ? {} : { lat }),
|
|
677
|
-
...(lon === undefined ? {} : { lon }),
|
|
678
|
-
...(radius_km === undefined ? {} : { radiusKm: radius_km }),
|
|
679
|
-
...(limit === undefined ? {} : { limit }),
|
|
680
|
-
...(timeout_ms === undefined ? {} : { timeoutMs: timeout_ms }),
|
|
681
|
-
});
|
|
682
|
-
return { structuredContent: r, content: [{ type: "text", text: renderPublicRestroom(r) }] };
|
|
298
|
+
annotations: { title: "Smart Vertical", readOnlyHint: true, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
299
|
+
}, async (input) => {
|
|
300
|
+
const output = await runSmartvertical(input);
|
|
301
|
+
return {
|
|
302
|
+
structuredContent: {
|
|
303
|
+
source: output.source,
|
|
304
|
+
domain: output.domain,
|
|
305
|
+
result: output.result,
|
|
306
|
+
},
|
|
307
|
+
content: [{ type: "text", text: renderSmartvertical(output) }],
|
|
308
|
+
};
|
|
683
309
|
});
|
|
684
310
|
return server;
|
|
685
311
|
}
|