daiso 1.0.9 → 1.0.10

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
@@ -7,7 +7,7 @@
7
7
 
8
8
  # Daiso MCP 및 Skill
9
9
 
10
- 다이소(제품/매장/재고), 키 없는 통합 가격 후보 비교, 주변 음식점/카페, 개발자 요청 제출, 롯데마트(매장/상품), GS25(매장/상품/재고), 세븐일레븐(상품/매장/재고/인기검색어/카탈로그), CU(매장/재고), 이마트24(매장/상품/재고), 올리브영(매장/재고), 메가박스(지점/영화/시간표/좌석), 롯데시네마(지점/영화/좌석), CGV(극장/영화/시간표) 조회 기능을 MCP, CLI, Codex Skill로 AI에 연결합니다.
10
+ 다이소(제품/매장/재고), 상품 가격 비교, 주변 음식점/카페, 주유소/유가, 개발자 요청 제출, 롯데마트(매장/상품), GS25(매장/상품/재고), 세븐일레븐(상품/매장/재고/인기검색어/카탈로그), CU(매장/재고), 이마트24(매장/상품/재고), 올리브영(매장/재고), 메가박스(지점/영화/시간표/좌석), 롯데시네마(지점/영화/좌석), CGV(극장/영화/시간표) 조회 기능을 MCP, CLI, Codex Skill로 AI에 연결합니다.
11
11
 
12
12
  <br>
13
13
 
@@ -15,7 +15,8 @@
15
15
 
16
16
  **ClawHub:** [clawhub.ai/hmmhmmhm/daiso-cli](https://clawhub.ai/hmmhmmhm/daiso-cli)
17
17
 
18
- 한국 로컬 리테일과 영화관 조회를 MCP, CLI, Codex Skill로 연결하는 도구입니다.
18
+ 한국 로컬 리테일, 생활 정보, 영화관 조회를 MCP, CLI, Codex Skill로 연결하는 도구입니다.
19
+ 사용자는 별도 API 키를 준비하지 않고 바로 사용할 수 있습니다.
19
20
 
20
21
  <br>
21
22
 
@@ -35,10 +36,15 @@
35
36
  <td>네이버 지역 검색</td>
36
37
  <td>음식점, 카페, 디저트, 주변 장소</td>
37
38
  </tr>
39
+ <tr>
40
+ <td>교통</td>
41
+ <td>오피넷</td>
42
+ <td>전국 평균 유가, 최저가 주유소, 위치 기반 주유소, 주유소 상세정보</td>
43
+ </tr>
38
44
  <tr>
39
45
  <td>비교</td>
40
46
  <td>다이소, GS25, 세븐일레븐, 이마트24</td>
41
- <td>새 외부 키 없는 상품 가격 후보 비교</td>
47
+ <td>같은 상품의 판매처별 가격 후보 비교</td>
42
48
  </tr>
43
49
  <tr>
44
50
  <td>운영</td>
@@ -75,11 +81,11 @@
75
81
  **[실시간 서비스 상태 보기](https://aka-page.betteruptime.com/)**
76
82
 
77
83
  <!-- WORKERS_INVOCATIONS_CHART:START -->
78
- <h3>Cloudflare Workers 호출량 (2026-03-01 ~ 2026-05-27, 88일)</h3>
84
+ <h3>Cloudflare Workers 호출량 (2026-06-14 ~ 2026-07-13, 30일)</h3>
79
85
 
80
- <img src="./assets/analytics/workers-invocations.png?v=2026-05-27T22:46:35.568Z" alt="Cloudflare Workers 호출량 그래프 (2026-03-01 ~ 2026-05-27)" width="100%">
86
+ <img src="./assets/analytics/workers-invocations.png?v=2026-07-13T16:10:36.883Z" alt="Cloudflare Workers 호출량 그래프 (2026-06-14 ~ 2026-07-13)" width="100%">
81
87
 
82
- <sub>기준 워커: <code>daiso-mcp</code> · 마지막 갱신: 2026-05-28 07:46 KST</sub>
88
+ <sub>기준 워커: <code>daiso-mcp</code> · 마지막 갱신: 2026-07-14 01:10 KST</sub>
83
89
 
84
90
  <!-- WORKERS_INVOCATIONS_CHART:END -->
85
91
 
@@ -127,6 +133,8 @@ GS25 mcp로 강남 근처 매장과 오감자 재고 알려줘
127
133
  세븐일레븐 mcp로 인기 검색어와 카탈로그 요약 알려줘
128
134
  강남역 근처 카페 찾아줘
129
135
  성수동 근처 브런치 음식점 찾아줘
136
+ 강남역 근처 제일 싼 주유소 찾아줘
137
+ 오늘 전국 평균 휘발유 가격 알려줘
130
138
  메가박스 mcp로 강남점 영화와 잔여 좌석 알려줘
131
139
  롯데시네마 mcp로 월드타워 근처 지점과 상영 영화 알려줘
132
140
  롯데시네마 mcp로 월드타워 잔여 좌석 알려줘
@@ -137,6 +145,7 @@ CGV mcp로 강남 상영 영화와 시간표 알려줘
137
145
 
138
146
  - 가격 비교: `콜라 어디가 싸?` → `compare_products`
139
147
  - 주변 장소: `강남역 근처 카페 찾아줘` → `places_search_nearby`
148
+ - 주유소/유가: `강남역 근처 제일 싼 주유소 찾아줘` → `opinet_search_stations_around`
140
149
  - 브랜드 명시 재고: `다이소 핫식스 재고 찾아줘` → 먼저 다이소에서 검색 후 결과 없을 때만 대안 제안
141
150
  - 편의점 재고: `GS25 강남 오감자 재고` → 상품 후보 확인 후 재고 조회
142
151
  - 영화 시간표: `오늘 강남 CGV 시간표` → KST 오늘 날짜로 극장 검색 후 시간표 조회
@@ -330,9 +339,9 @@ MCP 도구 응답은 각 서비스의 원본 필드를 유지하면서, 클라
330
339
  - `standard.theaters`: 영화관 목록을 `code`, `name`, `address`, `distanceMeters`, `raw` 형태로 정규화합니다.
331
340
  - `standard.places`: 음식점/카페 같은 장소 목록을 `name`, `address`, `raw` 중심으로 정규화합니다.
332
341
 
333
- ### 키 없는 통합 가격 후보 비교
342
+ ### 통합 상품 가격 비교
334
343
 
335
- 새 외부 API 키 없이 기존 상품 검색을 묶어 가격 후보를 비교합니다.
344
+ 여러 리테일/편의점 상품 검색 결과를 묶어 같은 상품의 판매처별 가격 후보를 비교합니다.
336
345
 
337
346
  ```bash
338
347
  npx daiso compare 콜라 --limit 3 --json
@@ -6,6 +6,19 @@ export const GS25_CLOUDFRONT_403_PATTERNS = [
6
6
  'The request could not be satisfied',
7
7
  '403 ERROR',
8
8
  ];
9
+ export const EMART24_UPSTREAM_403_PATTERNS = [
10
+ '403 Forbidden',
11
+ '<title>403 Forbidden</title>',
12
+ ];
13
+ export const CU_UPSTREAM_BLOCK_PATTERNS = [
14
+ '400 Bad Request',
15
+ 'Request Blocked',
16
+ ];
17
+ export const SEVENELEVEN_UPSTREAM_403_PATTERNS = [
18
+ '403 Forbidden',
19
+ '_Incapsula_Resource',
20
+ 'NOINDEX, NOFOLLOW',
21
+ ];
9
22
  export const HEALTH_CHECKS = [
10
23
  {
11
24
  id: 'cli.contract',
@@ -41,6 +54,7 @@ export const HEALTH_CHECKS = [
41
54
  path: '/api/emart24/products?keyword=%EC%BB%A4%ED%94%BC&pageSize=1',
42
55
  collectionKey: 'products',
43
56
  requiredFields: ['pluCd', 'goodsName', 'itemName', 'name'],
57
+ degradedFailurePatterns: EMART24_UPSTREAM_403_PATTERNS,
44
58
  },
45
59
  {
46
60
  id: 'gs25.products',
@@ -69,6 +83,7 @@ export const HEALTH_CHECKS = [
69
83
  path: '/api/seveneleven/products?query=%EC%BB%A4%ED%94%BC&size=1',
70
84
  collectionKey: 'products',
71
85
  requiredFields: ['itemCode', 'itemName', 'productNo', 'name'],
86
+ degradedFailurePatterns: SEVENELEVEN_UPSTREAM_403_PATTERNS,
72
87
  },
73
88
  {
74
89
  id: 'lottemart.products',
@@ -124,6 +139,7 @@ export const HEALTH_CHECKS = [
124
139
  path: '/api/cu/inventory?keyword=%EC%BB%A4%ED%94%BC&size=1&storeLimit=0&storeCheck=false',
125
140
  collectionKey: 'inventoryItems',
126
141
  requiredFields: ['itemCode', 'itemName', 'name'],
142
+ degradedFailurePatterns: CU_UPSTREAM_BLOCK_PATTERNS,
127
143
  },
128
144
  {
129
145
  id: 'emart24.inventory',
@@ -133,6 +149,7 @@ export const HEALTH_CHECKS = [
133
149
  path: '/api/emart24/inventory?keyword=%EC%BB%A4%ED%94%BC&storeKeyword=%EA%B0%95%EB%82%A8&limit=1',
134
150
  collectionKey: 'inventoryItems',
135
151
  requiredFields: ['pluCd', 'goodsName', 'itemName', 'name'],
152
+ degradedFailurePatterns: EMART24_UPSTREAM_403_PATTERNS,
136
153
  },
137
154
  {
138
155
  id: 'gs25.inventory',
@@ -156,6 +173,7 @@ export const HEALTH_CHECKS = [
156
173
  path: '/api/seveneleven/inventory?keyword=%EC%BB%A4%ED%94%BC&storeKeyword=%EA%B0%95%EB%82%A8&size=1',
157
174
  collectionKey: 'inventoryItems',
158
175
  requiredFields: ['itemCode', 'itemName', 'productNo', 'name'],
176
+ degradedFailurePatterns: SEVENELEVEN_UPSTREAM_403_PATTERNS,
159
177
  },
160
178
  {
161
179
  id: 'oliveyoung.inventory',
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 개별 서비스 헬스 체크 실행기
3
3
  */
4
- import { GS25_CLOUDFRONT_403_PATTERNS, HEALTH_CHECKS } from './healthCheckDefinitions.js';
4
+ import { EMART24_UPSTREAM_403_PATTERNS, GS25_CLOUDFRONT_403_PATTERNS, HEALTH_CHECKS, SEVENELEVEN_UPSTREAM_403_PATTERNS, } from './healthCheckDefinitions.js';
5
5
  import { hasRequiredRepresentativeFields, toCount, toFirstName } from './healthCheckShape.js';
6
6
  const HEALTH_CHECK_CACHE_TTL_MS = 300_000;
7
7
  const DEFAULT_HEALTH_CHECK_TIMEOUT_MS = 7000;
@@ -71,7 +71,16 @@ function shouldDegradeFailedResponse(check, message) {
71
71
  return check.degradedFailurePatterns?.some((pattern) => message.includes(pattern)) ?? false;
72
72
  }
73
73
  function shouldDegradeCliContractPath(path, message) {
74
- return path.startsWith('/api/gs25/') && GS25_CLOUDFRONT_403_PATTERNS.some((pattern) => message.includes(pattern));
74
+ if (path.startsWith('/api/gs25/')) {
75
+ return GS25_CLOUDFRONT_403_PATTERNS.some((pattern) => message.includes(pattern));
76
+ }
77
+ if (path.startsWith('/api/emart24/')) {
78
+ return EMART24_UPSTREAM_403_PATTERNS.some((pattern) => message.includes(pattern));
79
+ }
80
+ if (path.startsWith('/api/seveneleven/')) {
81
+ return SEVENELEVEN_UPSTREAM_403_PATTERNS.some((pattern) => message.includes(pattern));
82
+ }
83
+ return false;
75
84
  }
76
85
  function resolveCheckTimeoutMs(check, timeoutMs) {
77
86
  if (check.timeoutMs === undefined) {
@@ -229,7 +229,7 @@ export async function handleSevenElevenProducts(options, deps) {
229
229
  deps,
230
230
  path: '/api/seveneleven/products',
231
231
  configure: (parsed) => {
232
- const query = parsed.positionals[0];
232
+ const query = parsed.positionals[0] || parsed.options.query;
233
233
  if (!query) {
234
234
  return 'seveneleven-products 명령은 검색어가 필요합니다. 예: daiso seveneleven-products 삼각김밥';
235
235
  }
@@ -245,7 +245,7 @@ export async function handleSevenElevenStores(options, deps) {
245
245
  deps,
246
246
  path: '/api/seveneleven/stores',
247
247
  configure: (parsed) => {
248
- const keyword = parsed.positionals[0];
248
+ const keyword = parsed.positionals[0] || parsed.options.keyword;
249
249
  if (!keyword) {
250
250
  return 'seveneleven-stores 명령은 검색어가 필요합니다. 예: daiso seveneleven-stores 안산 중앙역';
251
251
  }
@@ -6,6 +6,9 @@ import { CGV_API } from './api.js';
6
6
  import { requestCgv } from './transport.js';
7
7
  const DEFAULT_THEATER_CODE = '0056';
8
8
  const MAX_FALLBACK_THEATERS = 5;
9
+ function asArray(value) {
10
+ return Array.isArray(value) ? value : [];
11
+ }
9
12
  async function resolveTheaterCode(playDate, theaterCode, params) {
10
13
  if (theaterCode) {
11
14
  return theaterCode;
@@ -25,7 +28,7 @@ async function fetchMoviesByTheaterCode(playDate, theaterCode, params) {
25
28
  scnYmd: playDate,
26
29
  });
27
30
  const response = await requestCgv(CGV_API.MOVIE_LIST_PATH, searchParams, params.timeout, params.zyteApiKey);
28
- return (response.data || [])
31
+ return asArray(response.data)
29
32
  .filter((item) => item.movNo && item.movNm)
30
33
  .map((item) => ({
31
34
  movieCode: item.movNo,
@@ -42,7 +45,7 @@ async function fetchTimetableByMovieCode(playDate, theaterCode, movieCode, param
42
45
  rtctlScopCd: CGV_API.TIMETABLE_SCOPE_CODE,
43
46
  });
44
47
  const response = await requestCgv(CGV_API.TIMETABLE_PATH, searchParams, params.timeout, params.zyteApiKey);
45
- return (response.data || [])
48
+ return asArray(response.data)
46
49
  .filter((item) => item.siteNo && item.movNo && item.scnYmd)
47
50
  .map((item) => ({
48
51
  scheduleId: `${item.scnYmd}${item.siteNo}${item.scnSseq || ''}`,
@@ -65,7 +68,7 @@ async function fetchTimetableBySite(playDate, theaterCode, params) {
65
68
  rtctlScopCd: CGV_API.TIMETABLE_SITE_SCOPE_CODE,
66
69
  });
67
70
  const response = await requestCgv(CGV_API.TIMETABLE_BY_SITE_PATH, searchParams, params.timeout, params.zyteApiKey);
68
- return (response.data || [])
71
+ return asArray(response.data)
69
72
  .filter((item) => item.siteNo && item.scnYmd)
70
73
  .map((item) => ({
71
74
  scheduleId: `${item.scnYmd}${item.siteNo}${item.scnSseq || ''}`,
@@ -85,7 +88,7 @@ export async function fetchCgvTheaters(params) {
85
88
  coCd: CGV_API.COMPANY_CODE,
86
89
  });
87
90
  const response = await requestCgv(CGV_API.THEATER_LIST_PATH, searchParams, params.timeout, params.zyteApiKey);
88
- const list = (response.data || []).flatMap((region) => (region.siteList || []).map((site) => ({
91
+ const list = asArray(response.data).flatMap((region) => asArray(region.siteList).map((site) => ({
89
92
  theaterCode: site.siteNo || '',
90
93
  theaterName: site.siteNm || '',
91
94
  regionCode: region.regnGrpCd || undefined,
@@ -14,25 +14,41 @@ async function searchProducts(args) {
14
14
  if (!keyword || keyword.trim().length === 0) {
15
15
  throw new Error('상품 검색어(keyword)를 입력해주세요.');
16
16
  }
17
- const result = await searchEmart24Products({
18
- keyword,
19
- page,
20
- pageSize,
21
- sortType,
22
- saleProductYn,
23
- }, {
24
- timeout: timeoutMs,
25
- });
26
- return buildTextResponse({
27
- keyword,
28
- page,
29
- pageSize,
30
- sortType,
31
- saleProductYn,
32
- totalCount: result.totalCount,
33
- count: result.products.length,
34
- products: result.products,
35
- });
17
+ try {
18
+ const result = await searchEmart24Products({
19
+ keyword,
20
+ page,
21
+ pageSize,
22
+ sortType,
23
+ saleProductYn,
24
+ }, {
25
+ timeout: timeoutMs,
26
+ });
27
+ return buildTextResponse({
28
+ keyword,
29
+ page,
30
+ pageSize,
31
+ sortType,
32
+ saleProductYn,
33
+ totalCount: result.totalCount,
34
+ count: result.products.length,
35
+ products: result.products,
36
+ });
37
+ }
38
+ catch (error) {
39
+ return buildTextResponse({
40
+ keyword,
41
+ page,
42
+ pageSize,
43
+ sortType,
44
+ saleProductYn,
45
+ totalCount: 0,
46
+ count: 0,
47
+ products: [],
48
+ status: 'degraded',
49
+ message: error instanceof Error ? error.message : '이마트24 상품 검색에 실패했습니다.',
50
+ });
51
+ }
36
52
  }
37
53
  const searchProductsOutputSchema = {
38
54
  keyword: z.string().describe('검색어'),
@@ -43,6 +59,8 @@ const searchProductsOutputSchema = {
43
59
  totalCount: z.number().describe('검색된 전체 상품 수'),
44
60
  count: z.number().describe('반환된 상품 수'),
45
61
  products: z.array(z.unknown()).describe('이마트24 상품 검색 결과'),
62
+ status: z.string().optional().describe('외부 API 차단 등으로 결과가 제한된 경우 degraded'),
63
+ message: z.string().optional().describe('degraded 상태의 원인 메시지'),
46
64
  };
47
65
  export function createSearchProductsTool() {
48
66
  return {
@@ -5,22 +5,43 @@ const CATEGORY_KEYWORDS = {
5
5
  dessert: '디저트',
6
6
  all: '',
7
7
  };
8
+ const HTML_ENTITY_REPLACEMENTS = {
9
+ '&amp;': '&',
10
+ '&quot;': '"',
11
+ '&#39;': "'",
12
+ '&#x27;': "'",
13
+ };
8
14
  function getProcessEnvValue(name) {
9
15
  /* c8 ignore next -- Node 테스트와 CLI 런타임은 process를 제공하며, Worker에는 API에서 키를 주입합니다. */
10
16
  return typeof process !== 'undefined' ? process.env[name] : undefined;
11
17
  }
18
+ function stripHtmlTags(value) {
19
+ let result = '';
20
+ let insideTag = false;
21
+ for (const character of value) {
22
+ if (character === '<') {
23
+ insideTag = true;
24
+ continue;
25
+ }
26
+ if (character === '>' && insideTag) {
27
+ insideTag = false;
28
+ continue;
29
+ }
30
+ if (!insideTag) {
31
+ result += character;
32
+ }
33
+ }
34
+ return result;
35
+ }
12
36
  function cleanHtml(value) {
13
37
  if (!value) {
14
38
  return '';
15
39
  }
16
- return value
17
- .replace(/<[^>]*>/g, '')
18
- .replace(/&amp;/g, '&')
19
- .replace(/&lt;/g, '<')
20
- .replace(/&gt;/g, '>')
21
- .replace(/&quot;/g, '"')
22
- .replace(/&#39;/g, "'")
23
- .trim();
40
+ let result = stripHtmlTags(value);
41
+ for (const [entity, replacement] of Object.entries(HTML_ENTITY_REPLACEMENTS)) {
42
+ result = result.split(entity).join(replacement);
43
+ }
44
+ return result.trim();
24
45
  }
25
46
  function toCoordinate(value) {
26
47
  if (!value) {
@@ -14,23 +14,40 @@ async function searchProducts(args) {
14
14
  if (!query || query.trim().length === 0) {
15
15
  throw new Error('상품 검색어(query)를 입력해주세요.');
16
16
  }
17
- const result = await searchSevenElevenProductsWithVariants(query, {
18
- page,
19
- size,
20
- sort,
21
- timeout: timeoutMs,
22
- });
23
- return buildTextResponse({
24
- query: result.query,
25
- page,
26
- size,
27
- sort,
28
- totalCount: result.totalCount,
29
- count: result.products.length,
30
- collectionIds: result.collectionIds,
31
- appliedQueries: result.appliedQueries,
32
- products: result.products,
33
- });
17
+ try {
18
+ const result = await searchSevenElevenProductsWithVariants(query, {
19
+ page,
20
+ size,
21
+ sort,
22
+ timeout: timeoutMs,
23
+ });
24
+ return buildTextResponse({
25
+ query: result.query,
26
+ page,
27
+ size,
28
+ sort,
29
+ totalCount: result.totalCount,
30
+ count: result.products.length,
31
+ collectionIds: result.collectionIds,
32
+ appliedQueries: result.appliedQueries,
33
+ products: result.products,
34
+ });
35
+ }
36
+ catch (error) {
37
+ return buildTextResponse({
38
+ query,
39
+ page,
40
+ size,
41
+ sort,
42
+ totalCount: 0,
43
+ count: 0,
44
+ collectionIds: [],
45
+ appliedQueries: [query],
46
+ products: [],
47
+ status: 'degraded',
48
+ message: error instanceof Error ? error.message : '세븐일레븐 상품 검색에 실패했습니다.',
49
+ });
50
+ }
34
51
  }
35
52
  const searchProductsOutputSchema = {
36
53
  query: z.string().describe('적용된 검색어'),
@@ -42,6 +59,8 @@ const searchProductsOutputSchema = {
42
59
  collectionIds: z.array(z.string()).describe('검색 컬렉션 ID 목록'),
43
60
  appliedQueries: z.array(z.string()).describe('실제로 시도한 검색어 목록'),
44
61
  products: z.array(z.unknown()).describe('세븐일레븐 상품 검색 결과'),
62
+ status: z.string().optional().describe('외부 API 차단 등으로 결과가 제한된 경우 degraded'),
63
+ message: z.string().optional().describe('degraded 상태의 원인 메시지'),
45
64
  };
46
65
  export function createSearchProductsTool() {
47
66
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "daiso",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "Korean retail and cinema MCP server, CLI, and Codex Skill for Daiso, convenience stores, marts, Olive Young, and theaters",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -79,34 +79,37 @@
79
79
  "node": ">=20"
80
80
  },
81
81
  "devDependencies": {
82
- "@biomejs/biome": "^2.4.15",
83
- "@cloudflare/workers-types": "^4.20260526.1",
82
+ "@biomejs/biome": "^2.5.2",
83
+ "@cloudflare/workers-types": "^4.20260702.1",
84
84
  "@eslint/js": "^10.0.1",
85
- "@types/node": "^25.9.1",
85
+ "@types/node": "^26.1.0",
86
86
  "@vitest/coverage-v8": "^4.1.7",
87
- "eslint": "^10.4.0",
87
+ "eslint": "^10.6.0",
88
88
  "eslint-config-prettier": "^10.1.8",
89
- "globals": "^17.4.0",
89
+ "globals": "^17.7.0",
90
90
  "jiti": "^2.7.0",
91
- "playwright": "^1.60.0",
92
- "prettier": "^3.8.3",
93
- "sharp": "^0.34.5",
94
- "tsx": "^4.22.3",
91
+ "playwright": "^1.61.1",
92
+ "prettier": "^3.9.4",
93
+ "sharp": "^0.35.3",
94
+ "tsx": "^4.23.0",
95
95
  "typescript": "^6.0.3",
96
- "typescript-eslint": "^8.60.0",
96
+ "typescript-eslint": "^8.62.1",
97
97
  "vitest": "^4.1.7",
98
- "wrangler": "^4.95.0"
98
+ "wrangler": "^4.107.0"
99
99
  },
100
100
  "overrides": {
101
101
  "flatted": "3.4.2",
102
- "undici": "7.24.0",
102
+ "undici": "7.28.0",
103
103
  "brace-expansion": "5.0.6",
104
- "ws": "8.20.1"
104
+ "ws": "8.21.0",
105
+ "tmp": "0.2.7",
106
+ "markdown-it": "14.2.0",
107
+ "esbuild": "0.28.1"
105
108
  },
106
109
  "dependencies": {
107
110
  "@modelcontextprotocol/sdk": "^1.27.1",
108
- "hono": "^4.12.23",
109
- "proj4": "^2.20.8",
111
+ "hono": "^4.12.27",
112
+ "proj4": "^2.20.9",
110
113
  "workers-mcp": "^0.0.13"
111
114
  }
112
115
  }