@isnow890/naver-search-mcp 1.0.20 → 1.0.40
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 +283 -33
- package/dist/data/README.md +26 -0
- package/dist/data/category.xls +0 -0
- package/dist/src/handlers/category.handlers.js +240 -0
- package/dist/src/handlers/datalab.handlers.js +36 -9
- package/dist/src/handlers/search.handlers.js +44 -11
- package/dist/src/index.js +300 -50
- package/dist/src/schemas/category.schemas.js +6 -0
- package/package.json +8 -7
- package/dist/src/tools/datalab.tools.js +0 -70
- package/dist/src/tools/search.tools.js +0 -62
package/README.md
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
# Naver Search MCP Server
|
|
2
2
|
|
|
3
3
|
[](README-ko.md)
|
|
4
|
+
|
|
5
|
+
[](https://archestra.ai/mcp-catalog/isnow890__naver-search-mcp)
|
|
4
6
|
[](https://smithery.ai/server/@isnow890/naver-search-mcp)
|
|
5
7
|
[](https://mcp.so/server/naver-search-mcp/isnow890)
|
|
6
8
|
|
|
7
9
|
MCP server for Naver Search API and DataLab API integration, enabling comprehensive search across various Naver services and data trend analysis.
|
|
8
10
|
|
|
11
|
+
> ⚠️ **Smithery Installation Notice**: Due to compatibility issues with the Smithery platform, **direct installation is recommended starting from version 1.0.40**. Smithery installation is only supported up to version 1.0.30.
|
|
12
|
+
|
|
9
13
|
#### Version History
|
|
10
14
|
|
|
15
|
+
###### 1.0.4 (2025-08-21)
|
|
16
|
+
|
|
17
|
+
- `find_category` tool added - with fuzzy matching and ranking system support
|
|
18
|
+
- Enhanced parameter validation with Zod schema
|
|
19
|
+
- Improved category search workflow
|
|
20
|
+
- Implemented level-based category ranking system (major categories prioritized)
|
|
21
|
+
|
|
22
|
+
###### 1.0.30 (2025-08-04)
|
|
23
|
+
|
|
24
|
+
- MCP SDK upgraded to 1.17.1
|
|
25
|
+
- Fixed compatibility issues with Smithery specification changes
|
|
26
|
+
- Added comprehensive DataLab shopping category code documentation
|
|
27
|
+
|
|
11
28
|
###### 1.0.2 (2025-04-26)
|
|
12
29
|
|
|
13
30
|
- README updated: cafe article search tool and version history section improved
|
|
@@ -47,6 +64,12 @@ MCP server for Naver Search API and DataLab API integration, enabling comprehens
|
|
|
47
64
|
|
|
48
65
|
### Available tools:
|
|
49
66
|
|
|
67
|
+
#### 🆕 Category Search
|
|
68
|
+
|
|
69
|
+
- **find_category**: Category search tool - No more need to manually check category numbers via URL for trend and shopping insight searches.
|
|
70
|
+
|
|
71
|
+
#### Search Tools
|
|
72
|
+
|
|
50
73
|
- **search_webkr**: Search Naver web documents
|
|
51
74
|
- **search_news**: Search Naver news
|
|
52
75
|
- **search_blog**: Search Naver blogs
|
|
@@ -58,6 +81,9 @@ MCP server for Naver Search API and DataLab API integration, enabling comprehens
|
|
|
58
81
|
- **search_encyc**: Search Naver encyclopedia
|
|
59
82
|
- **search_academic**: Search Naver academic papers
|
|
60
83
|
- **search_local**: Search Naver local places
|
|
84
|
+
|
|
85
|
+
#### DataLab Tools
|
|
86
|
+
|
|
61
87
|
- **datalab_search**: Analyze search term trends
|
|
62
88
|
- **datalab_shopping_category**: Analyze shopping category trends
|
|
63
89
|
- **datalab_shopping_by_device**: Analyze shopping trends by device
|
|
@@ -68,42 +94,249 @@ MCP server for Naver Search API and DataLab API integration, enabling comprehens
|
|
|
68
94
|
- **datalab_shopping_keyword_by_gender**: Analyze shopping keyword trends by gender
|
|
69
95
|
- **datalab_shopping_keyword_by_age**: Analyze shopping keyword trends by age group
|
|
70
96
|
|
|
97
|
+
### DataLab Shopping Category Codes
|
|
98
|
+
|
|
99
|
+
For DataLab shopping analysis tools, you need to use specific **8-digit category codes**. Here are some common examples:
|
|
100
|
+
|
|
101
|
+
#### Popular Category Codes:
|
|
102
|
+
|
|
103
|
+
- **50000000**: Fashion/Clothing (패션의류)
|
|
104
|
+
- **50000002**: Cosmetics/Beauty (화장품/미용)
|
|
105
|
+
- **50000003**: Digital/Home Appliances (디지털/가전)
|
|
106
|
+
- **50000004**: Sports/Leisure (스포츠/레저)
|
|
107
|
+
- **50000005**: Automotive/Tools (자동차/공구)
|
|
108
|
+
- **50000006**: Books/Music/DVDs (도서/음반/DVD)
|
|
109
|
+
- **50000007**: Toys/Hobbies (완구/취미)
|
|
110
|
+
- **50000008**: Food/Beverages (식품/음료)
|
|
111
|
+
- **50000009**: Health/Medical (건강/의료용품)
|
|
112
|
+
- **50000010**: Household/Living (생활/주방용품)
|
|
113
|
+
|
|
114
|
+
#### How to Find Category Codes:
|
|
115
|
+
|
|
116
|
+
1. Visit [Naver Shopping](https://shopping.naver.com)
|
|
117
|
+
2. Navigate to desired category
|
|
118
|
+
3. Check the `cat_id` parameter in the URL
|
|
119
|
+
4. Use the 8-digit code (e.g., `cat_id=50000000` → use `"50000000"`)
|
|
120
|
+
|
|
121
|
+
#### Usage Example:
|
|
122
|
+
|
|
123
|
+
When using shopping category tools, specify category like:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"category": "50000000",
|
|
128
|
+
"startDate": "2024-01-01",
|
|
129
|
+
"endDate": "2024-01-31",
|
|
130
|
+
"timeUnit": "month"
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
#### Complete Category List:
|
|
135
|
+
|
|
136
|
+
For a complete list of category codes, you can download from Naver Shopping Partner Center or extract them by browsing Naver Shopping categories.
|
|
137
|
+
|
|
138
|
+
### 🎯 Business Use Cases & Scenarios
|
|
139
|
+
|
|
140
|
+
#### 🛍️ E-commerce Market Research
|
|
141
|
+
|
|
142
|
+
```javascript
|
|
143
|
+
// Fashion trend discovery
|
|
144
|
+
find_category("fashion") → Check top fashion categories and codes
|
|
145
|
+
datalab_shopping_category → Analyze seasonal fashion trends
|
|
146
|
+
datalab_shopping_age → Identify fashion target demographics
|
|
147
|
+
datalab_shopping_keywords → Compare "dress" vs "jacket" vs "coat"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### 📱 Digital Marketing Strategy
|
|
151
|
+
|
|
152
|
+
```javascript
|
|
153
|
+
// Beauty industry analysis
|
|
154
|
+
find_category("cosmetics") → Find beauty categories
|
|
155
|
+
datalab_shopping_gender → 95% female vs 5% male shoppers
|
|
156
|
+
datalab_shopping_device → Mobile dominance in beauty shopping
|
|
157
|
+
datalab_shopping_keywords → "tint" vs "lipstick" keyword performance
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### 🏢 Business Intelligence & Competitive Analysis
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// Tech product insights
|
|
164
|
+
find_category("smartphone") → Check electronics categories
|
|
165
|
+
datalab_shopping_category → Track iPhone vs Galaxy trends
|
|
166
|
+
datalab_shopping_age → 20-30s as main smartphone buyers
|
|
167
|
+
datalab_shopping_device → PC vs mobile shopping behavior
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
#### 📊 Seasonal Business Planning
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
// Holiday shopping analysis
|
|
174
|
+
find_category("gift") → Gift categories
|
|
175
|
+
datalab_shopping_category → Black Friday, Christmas trends
|
|
176
|
+
datalab_shopping_keywords → "Mother's Day gift" vs "birthday gift"
|
|
177
|
+
datalab_shopping_age → Age-based gift purchasing patterns
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### 🎯 Customer Persona Development
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
// Fitness market analysis
|
|
184
|
+
find_category("exercise") → Sports/fitness categories
|
|
185
|
+
datalab_shopping_gender → Male vs female fitness spending
|
|
186
|
+
datalab_shopping_age → Primary fitness demographics (20-40s)
|
|
187
|
+
datalab_shopping_keywords → "home workout" vs "gym" trend analysis
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### 📈 Advanced Analysis Scenarios
|
|
191
|
+
|
|
192
|
+
#### Market Entry Strategy
|
|
193
|
+
|
|
194
|
+
1. **Category Discovery**: Use `find_category` to explore market segments
|
|
195
|
+
2. **Trend Analysis**: Identify growing vs declining categories
|
|
196
|
+
3. **Demographic Targeting**: Age/gender analysis for customer targeting
|
|
197
|
+
4. **Competitive Intelligence**: Keyword performance comparison
|
|
198
|
+
5. **Device Strategy**: Mobile vs PC shopping optimization
|
|
199
|
+
|
|
200
|
+
#### Product Launch Planning
|
|
201
|
+
|
|
202
|
+
1. **Market Validation**: Category growth trends and seasonality
|
|
203
|
+
2. **Target Customers**: Demographic analysis for product positioning
|
|
204
|
+
3. **Marketing Channels**: Device preferences for advertising strategy
|
|
205
|
+
4. **Competitive Landscape**: Keyword competition and opportunities
|
|
206
|
+
5. **Pricing Strategy**: Category performance and price correlation
|
|
207
|
+
|
|
208
|
+
#### Performance Monitoring
|
|
209
|
+
|
|
210
|
+
1. **Category Health**: Monitor product category trends
|
|
211
|
+
2. **Keyword Tracking**: Track brand and product keyword performance
|
|
212
|
+
3. **Demographic Shifts**: Monitor changing customer demographics
|
|
213
|
+
4. **Seasonal Patterns**: Plan inventory and marketing campaigns
|
|
214
|
+
5. **Competitive Benchmarking**: Compare performance against category averages
|
|
215
|
+
|
|
216
|
+
### Quick Reference: Popular Category Codes
|
|
217
|
+
|
|
218
|
+
| Category | Code | Korean |
|
|
219
|
+
| ------------------- | -------- | ------------- |
|
|
220
|
+
| Fashion/Clothing | 50000000 | 패션의류 |
|
|
221
|
+
| Cosmetics/Beauty | 50000002 | 화장품/미용 |
|
|
222
|
+
| Digital/Electronics | 50000003 | 디지털/가전 |
|
|
223
|
+
| Sports/Leisure | 50000004 | 스포츠/레저 |
|
|
224
|
+
| Food/Beverages | 50000008 | 식품/음료 |
|
|
225
|
+
| Health/Medical | 50000009 | 건강/의료용품 |
|
|
226
|
+
|
|
227
|
+
💡 **Tip**: Use `find_category` with fuzzy searches like "beauty", "fashion", "electronics" to easily find categories.
|
|
228
|
+
|
|
71
229
|
## Installation
|
|
72
230
|
|
|
73
|
-
###
|
|
231
|
+
### Step 1: Download Source Code
|
|
74
232
|
|
|
75
|
-
To
|
|
233
|
+
To use this MCP server, you need to download the source code first:
|
|
76
234
|
|
|
77
|
-
|
|
235
|
+
#### Clone with Git
|
|
78
236
|
|
|
79
237
|
```bash
|
|
80
|
-
|
|
238
|
+
git clone https://github.com/isnow890/naver-search-mcp.git
|
|
239
|
+
cd naver-search-mcp
|
|
240
|
+
npm install
|
|
241
|
+
npm run build
|
|
81
242
|
```
|
|
82
243
|
|
|
83
|
-
|
|
244
|
+
#### Or Download ZIP File
|
|
245
|
+
|
|
246
|
+
1. Download the latest version from [GitHub Releases](https://github.com/isnow890/naver-search-mcp/releases)
|
|
247
|
+
2. Extract the ZIP file to your desired location
|
|
248
|
+
3. Navigate to the extracted folder in terminal:
|
|
84
249
|
|
|
85
250
|
```bash
|
|
86
|
-
|
|
251
|
+
cd /path/to/naver-search-mcp
|
|
252
|
+
npm install
|
|
253
|
+
npm run build
|
|
87
254
|
```
|
|
88
255
|
|
|
89
|
-
|
|
256
|
+
### Step 2: Claude Desktop Configuration
|
|
90
257
|
|
|
91
|
-
|
|
92
|
-
|
|
258
|
+
After installation, you'll need the following information:
|
|
259
|
+
|
|
260
|
+
- **NAVER_CLIENT_ID**: Client ID from Naver Developers
|
|
261
|
+
- **NAVER_CLIENT_SECRET**: Client Secret from Naver Developers
|
|
262
|
+
- **Installation Path**: Absolute path to the downloaded folder
|
|
263
|
+
|
|
264
|
+
#### Windows Configuration
|
|
265
|
+
|
|
266
|
+
Add to Claude Desktop config file (`%APPDATA%\Claude\claude_desktop_config.json`):
|
|
267
|
+
|
|
268
|
+
```json
|
|
269
|
+
{
|
|
270
|
+
"mcpServers": {
|
|
271
|
+
"naver-search": {
|
|
272
|
+
"type": "stdio",
|
|
273
|
+
"command": "cmd",
|
|
274
|
+
"args": [
|
|
275
|
+
"/c",
|
|
276
|
+
"node",
|
|
277
|
+
"C:\\path\\to\\naver-search-mcp\\dist\\src\\index.js"
|
|
278
|
+
],
|
|
279
|
+
"cwd": "C:\\path\\to\\naver-search-mcp",
|
|
280
|
+
"env": {
|
|
281
|
+
"NAVER_CLIENT_ID": "your-naver-client-id",
|
|
282
|
+
"NAVER_CLIENT_SECRET": "your-naver-client-secret"
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
#### macOS/Linux Configuration
|
|
290
|
+
|
|
291
|
+
Add to Claude Desktop config file (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"mcpServers": {
|
|
296
|
+
"naver-search": {
|
|
297
|
+
"type": "stdio",
|
|
298
|
+
"command": "node",
|
|
299
|
+
"args": ["/path/to/naver-search-mcp/dist/src/index.js"],
|
|
300
|
+
"cwd": "/path/to/naver-search-mcp",
|
|
301
|
+
"env": {
|
|
302
|
+
"NAVER_CLIENT_ID": "your-naver-client-id",
|
|
303
|
+
"NAVER_CLIENT_SECRET": "your-naver-client-secret"
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
93
308
|
```
|
|
94
309
|
|
|
95
|
-
|
|
310
|
+
### Step 3: Path Configuration Important Notes
|
|
311
|
+
|
|
312
|
+
⚠️ **Important**: You must change the following paths in the above configuration to your actual installation paths:
|
|
313
|
+
|
|
314
|
+
- **Windows**: Change `C:\\path\\to\\naver-search-mcp` to your actual downloaded folder path
|
|
315
|
+
- **macOS/Linux**: Change `/path/to/naver-search-mcp` to your actual downloaded folder path
|
|
316
|
+
|
|
317
|
+
Finding your path:
|
|
96
318
|
|
|
97
319
|
```bash
|
|
98
|
-
|
|
320
|
+
# Check current location
|
|
321
|
+
pwd
|
|
322
|
+
|
|
323
|
+
# Absolute path examples
|
|
324
|
+
# Windows: C:\Users\username\Downloads\naver-search-mcp
|
|
325
|
+
# macOS: /Users/username/Downloads/naver-search-mcp
|
|
326
|
+
# Linux: /home/username/Downloads/naver-search-mcp
|
|
99
327
|
```
|
|
100
328
|
|
|
101
|
-
|
|
329
|
+
### Step 4: Restart Claude Desktop
|
|
330
|
+
|
|
331
|
+
After completing the configuration, completely close and restart Claude Desktop to activate the Naver Search MCP server.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Alternative Installation Methods
|
|
102
336
|
|
|
103
|
-
|
|
104
|
-
- NAVER_CLIENT_SECRET
|
|
337
|
+
### Option 2: NPX Installation (For v1.0.30 and below)
|
|
105
338
|
|
|
106
|
-
|
|
339
|
+
For older versions that support NPX installation:
|
|
107
340
|
|
|
108
341
|
#### Environment Variables
|
|
109
342
|
|
|
@@ -117,22 +350,7 @@ export NAVER_CLIENT_ID=your_client_id
|
|
|
117
350
|
export NAVER_CLIENT_SECRET=your_client_secret
|
|
118
351
|
```
|
|
119
352
|
|
|
120
|
-
####
|
|
121
|
-
|
|
122
|
-
```bash
|
|
123
|
-
npx @isnow890/naver-search-mcp
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
#### Run with Docker
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
docker run -i --rm \
|
|
130
|
-
-e NAVER_CLIENT_ID=your_client_id \
|
|
131
|
-
-e NAVER_CLIENT_SECRET=your_client_secret \
|
|
132
|
-
mcp/naver-search
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
## Claude Desktop Configuration
|
|
353
|
+
#### Claude Desktop Configuration for NPX
|
|
136
354
|
|
|
137
355
|
Add to `claude_desktop_config.json`:
|
|
138
356
|
|
|
@@ -151,7 +369,7 @@ Add to `claude_desktop_config.json`:
|
|
|
151
369
|
}
|
|
152
370
|
```
|
|
153
371
|
|
|
154
|
-
|
|
372
|
+
#### Cursor AI Configuration for NPX
|
|
155
373
|
|
|
156
374
|
Add to `mcp.json`:
|
|
157
375
|
|
|
@@ -170,7 +388,39 @@ Add to `mcp.json`:
|
|
|
170
388
|
}
|
|
171
389
|
```
|
|
172
390
|
|
|
173
|
-
|
|
391
|
+
### Option 3: Legacy Smithery Installation (Only for v1.0.30 and below)
|
|
392
|
+
|
|
393
|
+
⚠️ **Note**: This method only works for versions 1.0.30 and below due to platform compatibility issues.
|
|
394
|
+
|
|
395
|
+
#### For Claude Desktop:
|
|
396
|
+
```bash
|
|
397
|
+
npx -y @smithery/cli@latest install @isnow890/naver-search-mcp --client claude
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
#### For other AI clients:
|
|
401
|
+
```bash
|
|
402
|
+
# Cursor
|
|
403
|
+
npx -y @smithery/cli@latest install @isnow890/naver-search-mcp --client cursor
|
|
404
|
+
|
|
405
|
+
# Windsurf
|
|
406
|
+
npx -y @smithery/cli@latest install @isnow890/naver-search-mcp --client windsurf
|
|
407
|
+
|
|
408
|
+
# Cline
|
|
409
|
+
npx -y @smithery/cli@latest install @isnow890/naver-search-mcp --client cline
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Option 4: Docker Installation
|
|
413
|
+
|
|
414
|
+
For containerized deployment:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
docker run -i --rm \
|
|
418
|
+
-e NAVER_CLIENT_ID=your_client_id \
|
|
419
|
+
-e NAVER_CLIENT_SECRET=your_client_secret \
|
|
420
|
+
mcp/naver-search
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Docker configuration for Claude Desktop:
|
|
174
424
|
|
|
175
425
|
```json
|
|
176
426
|
{
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# 카테고리 데이터 폴더
|
|
2
|
+
|
|
3
|
+
이 폴더에 `categories.xlsx` 파일을 배치하세요.
|
|
4
|
+
|
|
5
|
+
## 파일 구조
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
data/
|
|
9
|
+
└── categories.xlsx
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Excel 파일 형식
|
|
13
|
+
|
|
14
|
+
| 카테고리번호 | 대분류 | 중분류 | 소분류 | 세분류 |
|
|
15
|
+
|-------------|--------|--------|--------|--------|
|
|
16
|
+
| 50003307 | 가구/인테리어 | DIY자재/용품 | 가구부속품 | 가구다리 |
|
|
17
|
+
|
|
18
|
+
- 첫 번째 행(헤더)은 자동으로 건너뜁니다
|
|
19
|
+
- 카테고리번호는 필수입니다
|
|
20
|
+
- 대분류는 필수, 나머지 분류는 선택사항입니다
|
|
21
|
+
|
|
22
|
+
## 사용법
|
|
23
|
+
|
|
24
|
+
1. `categories.xlsx` 파일을 이 폴더에 배치
|
|
25
|
+
2. MCP 서버 시작시 자동으로 로드됨
|
|
26
|
+
3. 파일 수정시 자동으로 캐시 갱신됨
|
|
Binary file
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
// Bundle-safe directory resolution
|
|
6
|
+
let __dirname;
|
|
7
|
+
try {
|
|
8
|
+
if (typeof import.meta.url !== 'undefined') {
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
__dirname = path.dirname(__filename);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
// Fallback for bundled environments
|
|
14
|
+
__dirname = path.join(process.cwd(), 'dist', 'src', 'handlers');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
// Final fallback
|
|
19
|
+
__dirname = path.join(process.cwd(), 'dist', 'src', 'handlers');
|
|
20
|
+
}
|
|
21
|
+
// Cache management
|
|
22
|
+
let categoryCache = null;
|
|
23
|
+
let cacheTimestamp = 0;
|
|
24
|
+
/**
|
|
25
|
+
* Load and cache category data from Excel file
|
|
26
|
+
*/
|
|
27
|
+
async function loadCategoryData() {
|
|
28
|
+
// Use __dirname to get the directory of the current module, then go to data folder
|
|
29
|
+
const dataPath = path.resolve(__dirname, '..', '..', 'data');
|
|
30
|
+
// Find any Excel file in data directory
|
|
31
|
+
const files = fs.readdirSync(dataPath);
|
|
32
|
+
const excelFiles = files.filter(file => file.endsWith('.xlsx') || file.endsWith('.xls'));
|
|
33
|
+
if (excelFiles.length === 0) {
|
|
34
|
+
throw new Error(`카테고리 Excel 파일을 찾을 수 없습니다. data 폴더에 .xlsx 또는 .xls 파일을 넣어주세요: ${dataPath}`);
|
|
35
|
+
}
|
|
36
|
+
// Use the first Excel file found
|
|
37
|
+
const filePath = path.join(dataPath, excelFiles[0]);
|
|
38
|
+
console.error(`Using Excel file: ${excelFiles[0]}`);
|
|
39
|
+
// Check if file exists (redundant but safe)
|
|
40
|
+
if (!fs.existsSync(filePath)) {
|
|
41
|
+
throw new Error(`카테고리 파일을 찾을 수 없습니다: ${filePath}`);
|
|
42
|
+
}
|
|
43
|
+
const fileStats = fs.statSync(filePath);
|
|
44
|
+
// Return cached data if file hasn't changed
|
|
45
|
+
if (categoryCache && fileStats.mtime.getTime() <= cacheTimestamp) {
|
|
46
|
+
return categoryCache;
|
|
47
|
+
}
|
|
48
|
+
console.error('Loading category data from Excel file...');
|
|
49
|
+
// Read Excel file - improved ES modules compatibility
|
|
50
|
+
let XLSX;
|
|
51
|
+
try {
|
|
52
|
+
// Try dynamic import first (modern ES modules)
|
|
53
|
+
try {
|
|
54
|
+
XLSX = await import('xlsx');
|
|
55
|
+
// Handle default export structure
|
|
56
|
+
if (XLSX.default) {
|
|
57
|
+
XLSX = XLSX.default;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (importError) {
|
|
61
|
+
// Fallback to createRequire for bundled environments
|
|
62
|
+
const require = createRequire(import.meta.url);
|
|
63
|
+
XLSX = require('xlsx');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
throw new Error(`xlsx 패키지 로드 실패. 패키지가 설치되지 않았거나 호환성 문제입니다. 세부사항: ${error instanceof Error ? error.message : String(error)}`);
|
|
68
|
+
}
|
|
69
|
+
const workbook = XLSX.readFile(filePath);
|
|
70
|
+
const sheetName = workbook.SheetNames[0];
|
|
71
|
+
const worksheet = workbook.Sheets[sheetName];
|
|
72
|
+
// Convert to JSON (skip header row)
|
|
73
|
+
const rawData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
|
74
|
+
const dataRows = rawData.slice(1); // Skip header row
|
|
75
|
+
// Parse data
|
|
76
|
+
categoryCache = dataRows
|
|
77
|
+
.filter(row => row.length >= 2 && row[0]) // Filter out empty rows
|
|
78
|
+
.map(row => ({
|
|
79
|
+
code: String(row[0] || '').trim(),
|
|
80
|
+
level1: String(row[1] || '').trim(),
|
|
81
|
+
level2: String(row[2] || '').trim(),
|
|
82
|
+
level3: String(row[3] || '').trim(),
|
|
83
|
+
level4: String(row[4] || '').trim(),
|
|
84
|
+
}))
|
|
85
|
+
.filter(item => item.code); // Only include rows with valid codes
|
|
86
|
+
cacheTimestamp = fileStats.mtime.getTime();
|
|
87
|
+
console.error(`Loaded ${categoryCache.length} categories from Excel file`);
|
|
88
|
+
return categoryCache;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Smart category search with fuzzy matching and ranking
|
|
92
|
+
*/
|
|
93
|
+
async function smartCategorySearch(query, maxResults = 10) {
|
|
94
|
+
const categories = await loadCategoryData();
|
|
95
|
+
const searchQuery = query.toLowerCase().trim();
|
|
96
|
+
const results = [];
|
|
97
|
+
for (const category of categories) {
|
|
98
|
+
let bestScore = 0;
|
|
99
|
+
let bestMatchType = '';
|
|
100
|
+
// Check all levels for matches
|
|
101
|
+
const levels = [
|
|
102
|
+
{ value: category.level1, name: '대분류' },
|
|
103
|
+
{ value: category.level2, name: '중분류' },
|
|
104
|
+
{ value: category.level3, name: '소분류' },
|
|
105
|
+
{ value: category.level4, name: '세분류' }
|
|
106
|
+
];
|
|
107
|
+
for (const level of levels) {
|
|
108
|
+
if (!level.value)
|
|
109
|
+
continue;
|
|
110
|
+
const levelText = level.value.toLowerCase();
|
|
111
|
+
let score = 0;
|
|
112
|
+
let matchType = '';
|
|
113
|
+
// Level-based bonus (대분류 우선)
|
|
114
|
+
let levelBonus = 0;
|
|
115
|
+
if (level.name === '대분류')
|
|
116
|
+
levelBonus = 50;
|
|
117
|
+
else if (level.name === '중분류')
|
|
118
|
+
levelBonus = 30;
|
|
119
|
+
else if (level.name === '소분류')
|
|
120
|
+
levelBonus = 20;
|
|
121
|
+
else if (level.name === '세분류')
|
|
122
|
+
levelBonus = 10;
|
|
123
|
+
// Exact match (highest priority)
|
|
124
|
+
if (levelText === searchQuery) {
|
|
125
|
+
score = 100 + levelBonus;
|
|
126
|
+
matchType = `정확일치(${level.name})`;
|
|
127
|
+
}
|
|
128
|
+
// Starts with (high priority)
|
|
129
|
+
else if (levelText.startsWith(searchQuery)) {
|
|
130
|
+
score = 80 + levelBonus;
|
|
131
|
+
matchType = `시작일치(${level.name})`;
|
|
132
|
+
}
|
|
133
|
+
// Contains (medium priority)
|
|
134
|
+
else if (levelText.includes(searchQuery)) {
|
|
135
|
+
score = 60 + levelBonus;
|
|
136
|
+
matchType = `포함일치(${level.name})`;
|
|
137
|
+
}
|
|
138
|
+
// Fuzzy match for similar terms
|
|
139
|
+
else {
|
|
140
|
+
const similarity = calculateSimilarity(searchQuery, levelText);
|
|
141
|
+
if (similarity > 0.6) {
|
|
142
|
+
score = Math.floor(similarity * 40) + levelBonus; // 0.6-1.0 -> 24-40 points + level bonus
|
|
143
|
+
matchType = `유사일치(${level.name})`;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (score > bestScore) {
|
|
147
|
+
bestScore = score;
|
|
148
|
+
bestMatchType = matchType;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (bestScore > 0) {
|
|
152
|
+
results.push({
|
|
153
|
+
...category,
|
|
154
|
+
score: bestScore,
|
|
155
|
+
matchType: bestMatchType
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// Sort by score (descending), then by category code (ascending for 대분류 priority)
|
|
160
|
+
return results
|
|
161
|
+
.sort((a, b) => {
|
|
162
|
+
if (b.score !== a.score) {
|
|
163
|
+
return b.score - a.score; // Higher score first
|
|
164
|
+
}
|
|
165
|
+
// If same score, prioritize by category code (대분류 codes are typically smaller)
|
|
166
|
+
return a.code.localeCompare(b.code);
|
|
167
|
+
})
|
|
168
|
+
.slice(0, maxResults);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Calculate similarity between two strings (simple implementation)
|
|
172
|
+
*/
|
|
173
|
+
function calculateSimilarity(str1, str2) {
|
|
174
|
+
const longer = str1.length > str2.length ? str1 : str2;
|
|
175
|
+
const shorter = str1.length > str2.length ? str2 : str1;
|
|
176
|
+
if (longer.length === 0)
|
|
177
|
+
return 1.0;
|
|
178
|
+
const editDistance = levenshteinDistance(longer, shorter);
|
|
179
|
+
return (longer.length - editDistance) / longer.length;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Calculate Levenshtein distance between two strings
|
|
183
|
+
*/
|
|
184
|
+
function levenshteinDistance(str1, str2) {
|
|
185
|
+
const matrix = Array(str2.length + 1).fill(null).map(() => Array(str1.length + 1).fill(null));
|
|
186
|
+
for (let i = 0; i <= str1.length; i++)
|
|
187
|
+
matrix[0][i] = i;
|
|
188
|
+
for (let j = 0; j <= str2.length; j++)
|
|
189
|
+
matrix[j][0] = j;
|
|
190
|
+
for (let j = 1; j <= str2.length; j++) {
|
|
191
|
+
for (let i = 1; i <= str1.length; i++) {
|
|
192
|
+
const substitutionCost = str1[i - 1] === str2[j - 1] ? 0 : 1;
|
|
193
|
+
matrix[j][i] = Math.min(matrix[j][i - 1] + 1, matrix[j - 1][i] + 1, matrix[j - 1][i - 1] + substitutionCost);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return matrix[str2.length][str1.length];
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* 카테고리 검색 핸들러
|
|
200
|
+
*/
|
|
201
|
+
export const findCategoryHandler = async ({ query, max_results = 10 }) => {
|
|
202
|
+
try {
|
|
203
|
+
const results = await smartCategorySearch(query, max_results);
|
|
204
|
+
if (results.length === 0) {
|
|
205
|
+
return {
|
|
206
|
+
message: `"${query}"와 관련된 카테고리를 찾을 수 없습니다. 다른 검색어를 시도해보세요.`,
|
|
207
|
+
suggestions: ["패션", "화장품", "가구", "스마트폰", "가전제품", "스포츠", "도서", "자동차", "식품", "뷰티"]
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const responseData = {
|
|
211
|
+
message: `"${query}" 검색 결과 (${results.length}개, 관련도순 정렬)`,
|
|
212
|
+
total_found: results.length,
|
|
213
|
+
categories: results.map(cat => ({
|
|
214
|
+
code: cat.code,
|
|
215
|
+
category: [cat.level1, cat.level2, cat.level3, cat.level4].filter(Boolean).join(' > '),
|
|
216
|
+
match_type: cat.matchType,
|
|
217
|
+
score: cat.score,
|
|
218
|
+
levels: {
|
|
219
|
+
대분류: cat.level1 || '',
|
|
220
|
+
중분류: cat.level2 || '',
|
|
221
|
+
소분류: cat.level3 || '',
|
|
222
|
+
세분류: cat.level4 || ''
|
|
223
|
+
}
|
|
224
|
+
})),
|
|
225
|
+
next_steps: {
|
|
226
|
+
trend_analysis: `이제 datalab_shopping_category 도구로 각 카테고리의 트렌드 분석이 가능합니다`,
|
|
227
|
+
age_analysis: `datalab_shopping_age 도구로 연령별 쇼핑 패턴을 분석할 수 있습니다`,
|
|
228
|
+
gender_analysis: `datalab_shopping_gender 도구로 성별 쇼핑 패턴을 분석할 수 있습니다`,
|
|
229
|
+
device_analysis: `datalab_shopping_device 도구로 디바이스별 쇼핑 패턴을 분석할 수 있습니다`
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
return responseData;
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
throw new Error(`카테고리 검색 중 오류 발생: ${error.message}`);
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
export const categoryToolHandlers = {
|
|
239
|
+
find_category: findCategoryHandler,
|
|
240
|
+
};
|
|
@@ -7,15 +7,42 @@ const client = NaverSearchClient.getInstance();
|
|
|
7
7
|
* index.ts에서 도구 실행 분기 없이 바로 사용
|
|
8
8
|
*/
|
|
9
9
|
export const datalabToolHandlers = {
|
|
10
|
-
datalab_search: (args) =>
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
datalab_search: (args) => {
|
|
11
|
+
console.error("datalab_search called with args:", JSON.stringify(args, null, 2));
|
|
12
|
+
return handleSearchTrend(args);
|
|
13
|
+
},
|
|
14
|
+
datalab_shopping_category: (args) => {
|
|
15
|
+
console.error("datalab_shopping_category called with args:", JSON.stringify(args, null, 2));
|
|
16
|
+
return handleShoppingCategoryTrend(args);
|
|
17
|
+
},
|
|
18
|
+
datalab_shopping_by_device: (args) => {
|
|
19
|
+
console.error("datalab_shopping_by_device called with args:", JSON.stringify(args, null, 2));
|
|
20
|
+
return handleShoppingByDeviceTrend(args);
|
|
21
|
+
},
|
|
22
|
+
datalab_shopping_by_gender: (args) => {
|
|
23
|
+
console.error("datalab_shopping_by_gender called with args:", JSON.stringify(args, null, 2));
|
|
24
|
+
return handleShoppingByGenderTrend(args);
|
|
25
|
+
},
|
|
26
|
+
datalab_shopping_by_age: (args) => {
|
|
27
|
+
console.error("datalab_shopping_by_age called with args:", JSON.stringify(args, null, 2));
|
|
28
|
+
return handleShoppingByAgeTrend(args);
|
|
29
|
+
},
|
|
30
|
+
datalab_shopping_keywords: (args) => {
|
|
31
|
+
console.error("datalab_shopping_keywords called with args:", JSON.stringify(args, null, 2));
|
|
32
|
+
return handleShoppingKeywordsTrend(args);
|
|
33
|
+
},
|
|
34
|
+
datalab_shopping_keyword_by_device: (args) => {
|
|
35
|
+
console.error("datalab_shopping_keyword_by_device called with args:", JSON.stringify(args, null, 2));
|
|
36
|
+
return handleShoppingKeywordByDeviceTrend(args);
|
|
37
|
+
},
|
|
38
|
+
datalab_shopping_keyword_by_gender: (args) => {
|
|
39
|
+
console.error("datalab_shopping_keyword_by_gender called with args:", JSON.stringify(args, null, 2));
|
|
40
|
+
return handleShoppingKeywordByGenderTrend(args);
|
|
41
|
+
},
|
|
42
|
+
datalab_shopping_keyword_by_age: (args) => {
|
|
43
|
+
console.error("datalab_shopping_keyword_by_age called with args:", JSON.stringify(args, null, 2));
|
|
44
|
+
return handleShoppingKeywordByAgeTrend(args);
|
|
45
|
+
},
|
|
19
46
|
};
|
|
20
47
|
/**
|
|
21
48
|
* 검색어 트렌드 핸들러
|
|
@@ -3,17 +3,50 @@ import { SearchArgsSchema } from "../schemas/search.schemas.js";
|
|
|
3
3
|
// 클라이언트 인스턴스
|
|
4
4
|
const client = NaverSearchClient.getInstance();
|
|
5
5
|
export const searchToolHandlers = {
|
|
6
|
-
search_webkr: (args) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
search_webkr: (args) => {
|
|
7
|
+
console.error("search_webkr called with args:", JSON.stringify(args, null, 2));
|
|
8
|
+
return handleWebKrSearch(SearchArgsSchema.parse(args));
|
|
9
|
+
},
|
|
10
|
+
search_news: (args) => {
|
|
11
|
+
console.error("search_news called with args:", JSON.stringify(args, null, 2));
|
|
12
|
+
return handleNewsSearch(SearchArgsSchema.parse(args));
|
|
13
|
+
},
|
|
14
|
+
search_blog: (args) => {
|
|
15
|
+
console.error("search_blog called with args:", JSON.stringify(args, null, 2));
|
|
16
|
+
return handleBlogSearch(SearchArgsSchema.parse(args));
|
|
17
|
+
},
|
|
18
|
+
search_shop: (args) => {
|
|
19
|
+
console.error("search_shop called with args:", JSON.stringify(args, null, 2));
|
|
20
|
+
return handleShopSearch(SearchArgsSchema.parse(args));
|
|
21
|
+
},
|
|
22
|
+
search_image: (args) => {
|
|
23
|
+
console.error("search_image called with args:", JSON.stringify(args, null, 2));
|
|
24
|
+
return handleImageSearch(SearchArgsSchema.parse(args));
|
|
25
|
+
},
|
|
26
|
+
search_kin: (args) => {
|
|
27
|
+
console.error("search_kin called with args:", JSON.stringify(args, null, 2));
|
|
28
|
+
return handleKinSearch(SearchArgsSchema.parse(args));
|
|
29
|
+
},
|
|
30
|
+
search_book: (args) => {
|
|
31
|
+
console.error("search_book called with args:", JSON.stringify(args, null, 2));
|
|
32
|
+
return handleBookSearch(SearchArgsSchema.parse(args));
|
|
33
|
+
},
|
|
34
|
+
search_encyc: (args) => {
|
|
35
|
+
console.error("search_encyc called with args:", JSON.stringify(args, null, 2));
|
|
36
|
+
return handleEncycSearch(SearchArgsSchema.parse(args));
|
|
37
|
+
},
|
|
38
|
+
search_academic: (args) => {
|
|
39
|
+
console.error("search_academic called with args:", JSON.stringify(args, null, 2));
|
|
40
|
+
return handleAcademicSearch(SearchArgsSchema.parse(args));
|
|
41
|
+
},
|
|
42
|
+
search_local: (args) => {
|
|
43
|
+
console.error("search_local called with args:", JSON.stringify(args, null, 2));
|
|
44
|
+
return handleLocalSearch(args);
|
|
45
|
+
},
|
|
46
|
+
search_cafearticle: (args) => {
|
|
47
|
+
console.error("search_cafearticle called with args:", JSON.stringify(args, null, 2));
|
|
48
|
+
return handleCafeArticleSearch(SearchArgsSchema.parse(args));
|
|
49
|
+
},
|
|
17
50
|
};
|
|
18
51
|
/**
|
|
19
52
|
* 전문자료 검색 핸들러
|
package/dist/src/index.js
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
import { NaverSearchClient } from "./clients/naver-search.client.js";
|
|
4
|
-
import { searchTools } from "./tools/search.tools.js";
|
|
5
|
-
import { datalabTools } from "./tools/datalab.tools.js";
|
|
6
5
|
import { searchToolHandlers } from "./handlers/search.handlers.js";
|
|
7
6
|
import { datalabToolHandlers } from "./handlers/datalab.handlers.js";
|
|
7
|
+
import { SearchArgsSchema, NaverLocalSearchParamsSchema, } from "./schemas/search.schemas.js";
|
|
8
|
+
import { DatalabSearchSchema, DatalabShoppingSchema, DatalabShoppingDeviceSchema, DatalabShoppingGenderSchema, DatalabShoppingAgeSchema, DatalabShoppingKeywordsSchema, DatalabShoppingKeywordDeviceSchema, DatalabShoppingKeywordGenderSchema, DatalabShoppingKeywordAgeSchema, } from "./schemas/datalab.schemas.js";
|
|
9
|
+
import { FindCategorySchema } from "./schemas/category.schemas.js";
|
|
10
|
+
import { findCategoryHandler } from "./handlers/category.handlers.js";
|
|
8
11
|
// Configuration schema for Smithery
|
|
9
12
|
export const configSchema = z.object({
|
|
10
13
|
NAVER_CLIENT_ID: z.string().describe("Naver API Client ID"),
|
|
11
|
-
NAVER_CLIENT_SECRET: z.string().describe("Naver API Client Secret")
|
|
14
|
+
NAVER_CLIENT_SECRET: z.string().describe("Naver API Client Secret"),
|
|
12
15
|
});
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
// Global server instance to prevent memory leaks
|
|
17
|
+
let globalServerInstance = null;
|
|
18
|
+
export function createNaverSearchServer({ config, }) {
|
|
19
|
+
// Reuse existing server instance to prevent memory leaks
|
|
20
|
+
if (globalServerInstance) {
|
|
21
|
+
return globalServerInstance;
|
|
22
|
+
}
|
|
23
|
+
// Create a new MCP server only once
|
|
15
24
|
const server = new McpServer({
|
|
16
25
|
name: "naver-search",
|
|
17
|
-
version: "1.0.
|
|
26
|
+
version: "1.0.40",
|
|
18
27
|
});
|
|
19
28
|
// Initialize Naver client with config
|
|
20
29
|
const client = NaverSearchClient.getInstance();
|
|
@@ -22,51 +31,292 @@ export default function ({ config }) {
|
|
|
22
31
|
clientId: config.NAVER_CLIENT_ID,
|
|
23
32
|
clientSecret: config.NAVER_CLIENT_SECRET,
|
|
24
33
|
});
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const handler = toolHandlers[tool.name];
|
|
34
|
-
if (!handler)
|
|
35
|
-
throw new Error(`Unknown tool: ${tool.name}`);
|
|
36
|
-
const result = await handler(args);
|
|
37
|
-
return {
|
|
38
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
catch (error) {
|
|
42
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
43
|
-
return {
|
|
44
|
-
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
45
|
-
isError: true,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
});
|
|
34
|
+
server.registerTool("search_news", {
|
|
35
|
+
description: "📰 Search latest Korean news articles from major outlets. Perfect for current events, breaking news, and recent developments. Covers politics, economy, society, and international news. (네이버 뉴스 검색 - 최신 뉴스와 시사 정보)",
|
|
36
|
+
inputSchema: SearchArgsSchema.shape,
|
|
37
|
+
}, async (args) => {
|
|
38
|
+
const result = await searchToolHandlers.search_news(args);
|
|
39
|
+
return {
|
|
40
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
41
|
+
};
|
|
49
42
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
43
|
+
server.registerTool("search_blog", {
|
|
44
|
+
description: "✍️ Search personal blogs and reviews for authentic user experiences. Great for product reviews, personal stories, detailed tutorials, and real user opinions. Find genuine Korean perspectives. (네이버 블로그 검색 - 실제 사용자 후기와 개인적 경험)",
|
|
45
|
+
inputSchema: SearchArgsSchema.shape,
|
|
46
|
+
}, async (args) => {
|
|
47
|
+
const result = await searchToolHandlers.search_blog(args);
|
|
48
|
+
return {
|
|
49
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
server.registerTool("search_shop", {
|
|
53
|
+
description: "🛒 Search Naver Shopping for products, prices, and shopping deals. Compare prices across vendors, find product specifications, and discover shopping trends in Korea. (네이버 쇼핑 검색 - 상품 정보와 가격 비교)",
|
|
54
|
+
inputSchema: SearchArgsSchema.shape,
|
|
55
|
+
}, async (args) => {
|
|
56
|
+
const result = await searchToolHandlers.search_shop(args);
|
|
57
|
+
return {
|
|
58
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
server.registerTool("search_image", {
|
|
62
|
+
description: "🖼️ Search for images with Korean context and relevance. Find visual content, infographics, charts, and photos related to your search terms. Great for visual research and content discovery. (네이버 이미지 검색 - 시각적 컨텐츠 발견)",
|
|
63
|
+
inputSchema: SearchArgsSchema.shape,
|
|
64
|
+
}, async (args) => {
|
|
65
|
+
const result = await searchToolHandlers.search_image(args);
|
|
66
|
+
return {
|
|
67
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
server.registerTool("search_kin", {
|
|
71
|
+
description: "❓ Search Naver KnowledgeiN for Q&A and community-driven answers. Find solutions to problems, get expert advice, and discover community insights on various topics. (네이버 지식iN 검색 - 질문과 답변, 커뮤니티 지식)",
|
|
72
|
+
inputSchema: SearchArgsSchema.shape,
|
|
73
|
+
}, async (args) => {
|
|
74
|
+
const result = await searchToolHandlers.search_kin(args);
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
server.registerTool("search_book", {
|
|
80
|
+
description: "📚 Search for books, publications, and literary content. Find book reviews, author information, publication details, and reading recommendations in Korean literature and translated works. (네이버 책 검색 - 도서 정보와 서평)",
|
|
81
|
+
inputSchema: SearchArgsSchema.shape,
|
|
82
|
+
}, async (args) => {
|
|
83
|
+
const result = await searchToolHandlers.search_book(args);
|
|
84
|
+
return {
|
|
85
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
server.registerTool("search_encyc", {
|
|
89
|
+
description: "📖 Search Naver Encyclopedia for authoritative knowledge and definitions. Best for academic research, getting reliable information, and understanding Korean concepts and terminology. (네이버 지식백과 검색 - 신뢰할 수 있는 정보와 정의)",
|
|
90
|
+
inputSchema: SearchArgsSchema.shape,
|
|
91
|
+
}, async (args) => {
|
|
92
|
+
const result = await searchToolHandlers.search_encyc(args);
|
|
93
|
+
return {
|
|
94
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
95
|
+
};
|
|
96
|
+
});
|
|
97
|
+
server.registerTool("search_academic", {
|
|
98
|
+
description: "🎓 Search academic papers, research documents, and scholarly content. Access Korean academic resources, research papers, theses, and professional publications. (네이버 전문자료 검색 - 학술 논문과 전문 자료)",
|
|
99
|
+
inputSchema: SearchArgsSchema.shape,
|
|
100
|
+
}, async (args) => {
|
|
101
|
+
const result = await searchToolHandlers.search_academic(args);
|
|
102
|
+
return {
|
|
103
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
104
|
+
};
|
|
105
|
+
});
|
|
106
|
+
server.registerTool("search_local", {
|
|
107
|
+
description: "📍 Search for local businesses, restaurants, and places in Korea. Find location information, reviews, contact details, and business hours for Korean establishments. (네이버 지역 검색 - 지역 업체와 장소 정보)",
|
|
108
|
+
inputSchema: NaverLocalSearchParamsSchema.shape,
|
|
109
|
+
}, async (args) => {
|
|
110
|
+
const result = await searchToolHandlers.search_local(args);
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
server.registerTool("search_cafearticle", {
|
|
116
|
+
description: "☕ Search Naver Cafe articles for community discussions and specialized content. Find niche communities, hobby groups, and specialized discussions on various topics. (네이버 카페글 검색 - 커뮤니티 토론과 전문 정보)",
|
|
117
|
+
inputSchema: SearchArgsSchema.shape,
|
|
118
|
+
}, async (args) => {
|
|
119
|
+
const result = await searchToolHandlers.search_cafearticle(args);
|
|
120
|
+
return {
|
|
121
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
122
|
+
};
|
|
123
|
+
});
|
|
124
|
+
// Register datalab tools
|
|
125
|
+
server.registerTool("datalab_search", {
|
|
126
|
+
description: "📊 Analyze search keyword trends over time using Naver DataLab. Track popularity changes, seasonal patterns, and compare multiple keywords. Perfect for market research and trend analysis. (네이버 데이터랩 검색어 트렌드 분석)",
|
|
127
|
+
inputSchema: DatalabSearchSchema.shape,
|
|
128
|
+
}, async (args) => {
|
|
129
|
+
const result = await datalabToolHandlers.datalab_search(args);
|
|
130
|
+
return {
|
|
131
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
server.registerTool("datalab_shopping_category", {
|
|
135
|
+
description: "🛍️ STEP 2: Analyze shopping category trends over time. Use find_category first to get category codes. BUSINESS CASES: Market size analysis, seasonal trend identification, category performance comparison. EXAMPLE: Compare '패션의류' vs '화장품' trends over 6 months. (네이버 쇼핑 카테고리별 트렌드 분석 - 먼저 find_category 도구로 카테고리 코드를 찾으세요)",
|
|
136
|
+
inputSchema: DatalabShoppingSchema.shape,
|
|
137
|
+
}, async (args) => {
|
|
138
|
+
const result = await datalabToolHandlers.datalab_shopping_category(args);
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
server.registerTool("datalab_shopping_by_device", {
|
|
144
|
+
description: "📱 Analyze shopping trends by device (PC vs Mobile). Use find_category first. BUSINESS CASES: Mobile commerce strategy, responsive design priority, device-specific campaigns. EXAMPLE: 'PC 사용자가 더 많이 구매하는 카테고리는?' (기기별 쇼핑 트렌드 분석 - 먼저 find_category 도구로 카테고리 코드를 찾으세요)",
|
|
145
|
+
inputSchema: DatalabShoppingDeviceSchema.pick({
|
|
146
|
+
startDate: true,
|
|
147
|
+
endDate: true,
|
|
148
|
+
timeUnit: true,
|
|
149
|
+
category: true,
|
|
150
|
+
device: true,
|
|
151
|
+
}).shape,
|
|
152
|
+
}, async (args) => {
|
|
153
|
+
const result = await datalabToolHandlers.datalab_shopping_by_device(args);
|
|
154
|
+
return {
|
|
155
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
server.registerTool("datalab_shopping_by_gender", {
|
|
159
|
+
description: "👥 Analyze shopping trends by gender (Male vs Female). Use find_category first. BUSINESS CASES: Gender-targeted marketing, product positioning, demographic analysis. EXAMPLE: '화장품 쇼핑에서 남녀 비율은?' (성별 쇼핑 트렌드 분석 - 먼저 find_category 도구로 카테고리 코드를 찾으세요)",
|
|
160
|
+
inputSchema: DatalabShoppingGenderSchema.pick({
|
|
161
|
+
startDate: true,
|
|
162
|
+
endDate: true,
|
|
163
|
+
timeUnit: true,
|
|
164
|
+
category: true,
|
|
165
|
+
gender: true,
|
|
166
|
+
}).shape,
|
|
167
|
+
}, async (args) => {
|
|
168
|
+
const result = await datalabToolHandlers.datalab_shopping_by_gender(args);
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
171
|
+
};
|
|
70
172
|
});
|
|
173
|
+
server.registerTool("datalab_shopping_by_age", {
|
|
174
|
+
description: "👶👦👨👴 Analyze shopping trends by age groups (10s, 20s, 30s, 40s, 50s, 60s+). Use find_category first. BUSINESS CASES: Age-targeted products, generational preferences, lifecycle marketing. EXAMPLE: '개발 도구는 어느 연령대가 많이 구매하나?' (연령별 쇼핑 트렌드 - 먼저 find_category 도구로 카테고리 코드를 찾으세요)",
|
|
175
|
+
inputSchema: DatalabShoppingAgeSchema.pick({
|
|
176
|
+
startDate: true,
|
|
177
|
+
endDate: true,
|
|
178
|
+
timeUnit: true,
|
|
179
|
+
category: true,
|
|
180
|
+
ages: true,
|
|
181
|
+
}).shape,
|
|
182
|
+
}, async (args) => {
|
|
183
|
+
const result = await datalabToolHandlers.datalab_shopping_by_age(args);
|
|
184
|
+
return {
|
|
185
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
server.registerTool("datalab_shopping_keywords", {
|
|
189
|
+
description: "🔍 Compare specific keywords within a shopping category. Use find_category first. BUSINESS CASES: Product keyword optimization, competitor analysis, search trend identification. EXAMPLE: Within '패션' category, compare '원피스' vs '자켓' vs '드레스' trends. (카테고리 내 키워드 비교 - 먼저 find_category 도구로 카테고리 코드를 찾으세요)",
|
|
190
|
+
inputSchema: DatalabShoppingKeywordsSchema.shape,
|
|
191
|
+
}, async (args) => {
|
|
192
|
+
const result = await datalabToolHandlers.datalab_shopping_keywords(args);
|
|
193
|
+
return {
|
|
194
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
server.registerTool("datalab_shopping_keyword_by_device", {
|
|
198
|
+
description: "📱🔍 Analyze keyword performance by device within shopping categories. Use find_category first to get category codes. Perfect for understanding mobile vs desktop shopping behavior for specific products. (쇼핑 키워드 기기별 트렌드 - 먼저 find_category 도구로 카테고리 코드를 찾으세요)",
|
|
199
|
+
inputSchema: DatalabShoppingKeywordDeviceSchema.shape,
|
|
200
|
+
}, async (args) => {
|
|
201
|
+
const result = await datalabToolHandlers.datalab_shopping_keyword_by_device(args);
|
|
202
|
+
return {
|
|
203
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
204
|
+
};
|
|
205
|
+
});
|
|
206
|
+
server.registerTool("datalab_shopping_keyword_by_gender", {
|
|
207
|
+
description: "👥🔍 Analyze keyword performance by gender within shopping categories. Use find_category first to get category codes. Essential for gender-targeted marketing and product positioning strategies. (쇼핑 키워드 성별 트렌드 - 먼저 find_category 도구로 카테고리 코드를 찾으세요)",
|
|
208
|
+
inputSchema: DatalabShoppingKeywordGenderSchema.shape,
|
|
209
|
+
}, async (args) => {
|
|
210
|
+
const result = await datalabToolHandlers.datalab_shopping_keyword_by_gender(args);
|
|
211
|
+
return {
|
|
212
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
213
|
+
};
|
|
214
|
+
});
|
|
215
|
+
server.registerTool("datalab_shopping_keyword_by_age", {
|
|
216
|
+
description: "👶👦👨👴🔍 Analyze keyword performance by age groups within shopping categories. Use find_category first to get category codes. Perfect for age-targeted marketing and understanding generational shopping preferences. (쇼핑 키워드 연령별 트렌드 - 먼저 find_category 도구로 카테고리 코드를 찾으세요)",
|
|
217
|
+
inputSchema: DatalabShoppingKeywordAgeSchema.shape,
|
|
218
|
+
}, async (args) => {
|
|
219
|
+
const result = await datalabToolHandlers.datalab_shopping_keyword_by_age(args);
|
|
220
|
+
return {
|
|
221
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
// Register category search tool
|
|
225
|
+
server.registerTool("find_category", {
|
|
226
|
+
description: "🚀 STEP 1: Find shopping categories with Korean search terms. Search in KOREAN (패션, 화장품, 가전제품, etc.) to find category codes needed for datalab tools. Smart fuzzy matching finds similar categories even with partial matches. (카테고리 검색: 한국어로 검색하여 데이터랩 분석에 필요한 카테고리 코드를 찾아주는 필수 도구)",
|
|
227
|
+
inputSchema: FindCategorySchema.shape,
|
|
228
|
+
}, async (args) => {
|
|
229
|
+
const result = await findCategoryHandler(args);
|
|
230
|
+
return {
|
|
231
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
// Cache the server instance
|
|
235
|
+
globalServerInstance = server;
|
|
71
236
|
return server.server;
|
|
72
237
|
}
|
|
238
|
+
// Export default for Smithery compatibility
|
|
239
|
+
export default createNaverSearchServer;
|
|
240
|
+
// Main function to run the server when executed directly
|
|
241
|
+
async function main() {
|
|
242
|
+
try {
|
|
243
|
+
console.error("Starting Naver Search MCP Server...");
|
|
244
|
+
// Get config from environment variables - check for empty strings too
|
|
245
|
+
const clientId = process.env.NAVER_CLIENT_ID?.trim();
|
|
246
|
+
const clientSecret = process.env.NAVER_CLIENT_SECRET?.trim();
|
|
247
|
+
console.error("Environment variables:", {
|
|
248
|
+
NAVER_CLIENT_ID: process.env.NAVER_CLIENT_ID
|
|
249
|
+
? `[${process.env.NAVER_CLIENT_ID.length} chars]`
|
|
250
|
+
: "undefined",
|
|
251
|
+
NAVER_CLIENT_SECRET: process.env.NAVER_CLIENT_SECRET
|
|
252
|
+
? `[${process.env.NAVER_CLIENT_SECRET.length} chars]`
|
|
253
|
+
: "undefined",
|
|
254
|
+
});
|
|
255
|
+
if (!clientId || !clientSecret) {
|
|
256
|
+
throw new Error(`Missing required environment variables:
|
|
257
|
+
NAVER_CLIENT_ID: ${clientId ? "provided" : "missing"}
|
|
258
|
+
NAVER_CLIENT_SECRET: ${clientSecret ? "provided" : "missing"}
|
|
259
|
+
|
|
260
|
+
Please set these environment variables before running the server.`);
|
|
261
|
+
}
|
|
262
|
+
const config = {
|
|
263
|
+
NAVER_CLIENT_ID: clientId,
|
|
264
|
+
NAVER_CLIENT_SECRET: clientSecret,
|
|
265
|
+
};
|
|
266
|
+
console.error("Config loaded successfully");
|
|
267
|
+
// Validate config
|
|
268
|
+
const validatedConfig = configSchema.parse(config);
|
|
269
|
+
console.error("Config validated successfully");
|
|
270
|
+
// Create server instance
|
|
271
|
+
const serverFactory = createNaverSearchServer({ config: validatedConfig });
|
|
272
|
+
console.error("Server factory created");
|
|
273
|
+
// Create transport and run server
|
|
274
|
+
const transport = new StdioServerTransport();
|
|
275
|
+
console.error("Transport created, connecting...");
|
|
276
|
+
await serverFactory.connect(transport);
|
|
277
|
+
console.error("Server connected and running");
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
console.error("Error in main function:", error);
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Run main function if this file is executed directly
|
|
285
|
+
// Note: Always run main in CLI mode since this is an MCP server
|
|
286
|
+
console.error("Starting MCP server initialization...");
|
|
287
|
+
console.error("process.argv:", process.argv);
|
|
288
|
+
// Check if running as main module - compatible with both ESM and CommonJS
|
|
289
|
+
let isMainModule = false;
|
|
290
|
+
try {
|
|
291
|
+
// Try ESM approach first
|
|
292
|
+
if (typeof import.meta !== "undefined" && import.meta.url) {
|
|
293
|
+
console.error("import.meta.url:", import.meta.url);
|
|
294
|
+
isMainModule =
|
|
295
|
+
import.meta.url === `file://${process.argv[1]}` ||
|
|
296
|
+
import.meta.url.endsWith(process.argv[1]) ||
|
|
297
|
+
process.argv[1].endsWith("index.js");
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
// Fallback for CommonJS or when import.meta is not available
|
|
301
|
+
isMainModule =
|
|
302
|
+
process.argv[1].endsWith("index.js") ||
|
|
303
|
+
process.argv[1].includes("naver-search-mcp");
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
// Fallback for environments where import.meta causes issues
|
|
308
|
+
isMainModule =
|
|
309
|
+
process.argv[1].endsWith("index.js") ||
|
|
310
|
+
process.argv[1].includes("naver-search-mcp");
|
|
311
|
+
}
|
|
312
|
+
console.error("isMainModule:", isMainModule);
|
|
313
|
+
if (isMainModule) {
|
|
314
|
+
console.error("Running as main module, starting server...");
|
|
315
|
+
main().catch((error) => {
|
|
316
|
+
console.error("Server failed to start:", error);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
console.error("Not running as main module, skipping server start");
|
|
322
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// Find category schema - Simple and powerful!
|
|
3
|
+
export const FindCategorySchema = z.object({
|
|
4
|
+
query: z.string().describe("Korean category search query (한국어 카테고리 검색어, 예: '패션', '화장품', '가구', '스마트폰'). Supports exact match and fuzzy search with level-based priority"),
|
|
5
|
+
max_results: z.number().optional().default(10).describe("Maximum number of results to return (반환할 최대 결과 수, 기본값: 10)")
|
|
6
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isnow890/naver-search-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.40",
|
|
4
4
|
"description": "Naver Search MCP Server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "./src/index.ts",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"dist"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"prebuild": "shx rm -rf dist",
|
|
13
|
-
"build": "tsc",
|
|
12
|
+
"prebuild": "npx shx rm -rf dist",
|
|
13
|
+
"build": "tsc && shx cp -r data dist/",
|
|
14
14
|
"prepare": "npm run build",
|
|
15
15
|
"watch": "tsc --watch",
|
|
16
16
|
"start": "node dist/src/index.js"
|
|
@@ -19,12 +19,13 @@
|
|
|
19
19
|
"node": ">=18"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@modelcontextprotocol/sdk": "1.
|
|
22
|
+
"@modelcontextprotocol/sdk": "1.17.1",
|
|
23
23
|
"axios": "^1.6.7",
|
|
24
|
-
"zod": "^3.
|
|
25
|
-
"zod-to-json-schema": "^3.23.
|
|
24
|
+
"zod": "^3.23.0",
|
|
25
|
+
"zod-to-json-schema": "^3.23.0",
|
|
26
26
|
"sharp": "^0.33.2",
|
|
27
|
-
"dotenv": "^16.4.5"
|
|
27
|
+
"dotenv": "^16.4.5",
|
|
28
|
+
"xlsx": "0.18.5"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@types/node": "^22",
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
|
-
import { DatalabSearchSchema, DatalabShoppingSchema, DatalabShoppingDeviceSchema, DatalabShoppingGenderSchema, DatalabShoppingAgeSchema, DatalabShoppingKeywordsSchema, DatalabShoppingKeywordDeviceSchema, DatalabShoppingKeywordGenderSchema, DatalabShoppingKeywordAgeSchema } from "../schemas/datalab.schemas.js";
|
|
3
|
-
/**
|
|
4
|
-
* 데이터랩 관련 도구 정의
|
|
5
|
-
*/
|
|
6
|
-
export const datalabTools = [
|
|
7
|
-
{
|
|
8
|
-
name: "datalab_search",
|
|
9
|
-
description: "Perform a trend analysis on Naver search keywords. (네이버 검색어 트렌드 분석)",
|
|
10
|
-
inputSchema: zodToJsonSchema(DatalabSearchSchema),
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
name: "datalab_shopping_category",
|
|
14
|
-
description: "Perform a trend analysis on Naver Shopping category. (네이버 쇼핑 카테고리별 트렌드 분석)",
|
|
15
|
-
inputSchema: zodToJsonSchema(DatalabShoppingSchema),
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: "datalab_shopping_by_device",
|
|
19
|
-
description: "Perform a trend analysis on Naver Shopping by device. (네이버 쇼핑 기기별 트렌드 분석)",
|
|
20
|
-
inputSchema: zodToJsonSchema(DatalabShoppingDeviceSchema.pick({
|
|
21
|
-
startDate: true,
|
|
22
|
-
endDate: true,
|
|
23
|
-
timeUnit: true,
|
|
24
|
-
category: true,
|
|
25
|
-
device: true,
|
|
26
|
-
})),
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
name: "datalab_shopping_by_gender",
|
|
30
|
-
description: "Perform a trend analysis on Naver Shopping by gender. (네이버 쇼핑 성별 트렌드 분석)",
|
|
31
|
-
inputSchema: zodToJsonSchema(DatalabShoppingGenderSchema.pick({
|
|
32
|
-
startDate: true,
|
|
33
|
-
endDate: true,
|
|
34
|
-
timeUnit: true,
|
|
35
|
-
category: true,
|
|
36
|
-
gender: true,
|
|
37
|
-
})),
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: "datalab_shopping_by_age",
|
|
41
|
-
description: "Perform a trend analysis on Naver Shopping by age. (네이버 쇼핑 연령별 트렌드 분석)",
|
|
42
|
-
inputSchema: zodToJsonSchema(DatalabShoppingAgeSchema.pick({
|
|
43
|
-
startDate: true,
|
|
44
|
-
endDate: true,
|
|
45
|
-
timeUnit: true,
|
|
46
|
-
category: true,
|
|
47
|
-
ages: true,
|
|
48
|
-
})),
|
|
49
|
-
},
|
|
50
|
-
{
|
|
51
|
-
name: "datalab_shopping_keywords",
|
|
52
|
-
description: "Perform a trend analysis on Naver Shopping keywords. (네이버 쇼핑 키워드별 트렌드 분석)",
|
|
53
|
-
inputSchema: zodToJsonSchema(DatalabShoppingKeywordsSchema),
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: "datalab_shopping_keyword_by_device",
|
|
57
|
-
description: "Perform a trend analysis on Naver Shopping keywords by device. (네이버 쇼핑 키워드 기기별 트렌드 분석)",
|
|
58
|
-
inputSchema: zodToJsonSchema(DatalabShoppingKeywordDeviceSchema),
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
name: "datalab_shopping_keyword_by_gender",
|
|
62
|
-
description: "Perform a trend analysis on Naver Shopping keywords by gender. (네이버 쇼핑 키워드 성별 트렌드 분석)",
|
|
63
|
-
inputSchema: zodToJsonSchema(DatalabShoppingKeywordGenderSchema),
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: "datalab_shopping_keyword_by_age",
|
|
67
|
-
description: "Perform a trend analysis on Naver Shopping keywords by age. (네이버 쇼핑 키워드 연령별 트렌드 분석)",
|
|
68
|
-
inputSchema: zodToJsonSchema(DatalabShoppingKeywordAgeSchema),
|
|
69
|
-
},
|
|
70
|
-
];
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
2
|
-
import { SearchArgsSchema, NaverLocalSearchParamsSchema, } from "../schemas/search.schemas.js";
|
|
3
|
-
/**
|
|
4
|
-
* 검색 관련 도구 정의
|
|
5
|
-
*/
|
|
6
|
-
export const searchTools = [
|
|
7
|
-
{
|
|
8
|
-
name: "search_webkr",
|
|
9
|
-
description: "Perform a search on Naver Web Documents. (네이버 웹문서 검색)",
|
|
10
|
-
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
name: "search_news",
|
|
14
|
-
description: "Perform a search on Naver News. (네이버 뉴스 검색)",
|
|
15
|
-
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
name: "search_blog",
|
|
19
|
-
description: "Perform a search on Naver Blog. (네이버 블로그 검색)",
|
|
20
|
-
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
name: "search_shop",
|
|
24
|
-
description: "Perform a search on Naver Shopping. (네이버 쇼핑 검색)",
|
|
25
|
-
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
name: "search_image",
|
|
29
|
-
description: "Perform a search on Naver Image. (네이버 이미지 검색)",
|
|
30
|
-
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
name: "search_kin",
|
|
34
|
-
description: "Perform a search on Naver KnowledgeiN. (네이버 지식iN 검색)",
|
|
35
|
-
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
name: "search_book",
|
|
39
|
-
description: "Perform a search on Naver Book. (네이버 책 검색)",
|
|
40
|
-
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: "search_encyc",
|
|
44
|
-
description: "Perform a search on Naver Encyclopedia. (네이버 지식백과 검색)",
|
|
45
|
-
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: "search_academic",
|
|
49
|
-
description: "Perform a search on Naver Academic. (네이버 전문자료 검색)",
|
|
50
|
-
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: "search_local",
|
|
54
|
-
description: "Perform a search on Naver Local. (네이버 지역 검색)",
|
|
55
|
-
inputSchema: zodToJsonSchema(NaverLocalSearchParamsSchema),
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
name: "search_cafearticle",
|
|
59
|
-
description: "Perform a search on Naver Cafe Articles. (네이버 카페글 검색)",
|
|
60
|
-
inputSchema: zodToJsonSchema(SearchArgsSchema),
|
|
61
|
-
},
|
|
62
|
-
];
|