@isnow890/naver-search-mcp 1.0.30 → 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 CHANGED
@@ -1,13 +1,24 @@
1
1
  # Naver Search MCP Server
2
2
 
3
3
  [![한국어](https://img.shields.io/badge/한국어-README-yellow)](README-ko.md)
4
+
5
+ [![Trust Score](https://archestra.ai/mcp-catalog/api/badge/quality/isnow890/naver-search-mcp)](https://archestra.ai/mcp-catalog/isnow890__naver-search-mcp)
4
6
  [![smithery badge](https://smithery.ai/badge/@isnow890/naver-search-mcp)](https://smithery.ai/server/@isnow890/naver-search-mcp)
5
7
  [![MCP.so](https://img.shields.io/badge/MCP.so-Naver%20Search%20MCP-blue)](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
+
11
22
  ###### 1.0.30 (2025-08-04)
12
23
 
13
24
  - MCP SDK upgraded to 1.17.1
@@ -53,7 +64,12 @@ MCP server for Naver Search API and DataLab API integration, enabling comprehens
53
64
 
54
65
  ### Available tools:
55
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
+
56
71
  #### Search Tools
72
+
57
73
  - **search_webkr**: Search Naver web documents
58
74
  - **search_news**: Search Naver news
59
75
  - **search_blog**: Search Naver blogs
@@ -67,6 +83,7 @@ MCP server for Naver Search API and DataLab API integration, enabling comprehens
67
83
  - **search_local**: Search Naver local places
68
84
 
69
85
  #### DataLab Tools
86
+
70
87
  - **datalab_search**: Analyze search term trends
71
88
  - **datalab_shopping_category**: Analyze shopping category trends
72
89
  - **datalab_shopping_by_device**: Analyze shopping trends by device
@@ -82,6 +99,7 @@ MCP server for Naver Search API and DataLab API integration, enabling comprehens
82
99
  For DataLab shopping analysis tools, you need to use specific **8-digit category codes**. Here are some common examples:
83
100
 
84
101
  #### Popular Category Codes:
102
+
85
103
  - **50000000**: Fashion/Clothing (패션의류)
86
104
  - **50000002**: Cosmetics/Beauty (화장품/미용)
87
105
  - **50000003**: Digital/Home Appliances (디지털/가전)
@@ -94,13 +112,16 @@ For DataLab shopping analysis tools, you need to use specific **8-digit category
94
112
  - **50000010**: Household/Living (생활/주방용품)
95
113
 
96
114
  #### How to Find Category Codes:
115
+
97
116
  1. Visit [Naver Shopping](https://shopping.naver.com)
98
117
  2. Navigate to desired category
99
118
  3. Check the `cat_id` parameter in the URL
100
119
  4. Use the 8-digit code (e.g., `cat_id=50000000` → use `"50000000"`)
101
120
 
102
121
  #### Usage Example:
122
+
103
123
  When using shopping category tools, specify category like:
124
+
104
125
  ```json
105
126
  {
106
127
  "category": "50000000",
@@ -111,44 +132,211 @@ When using shopping category tools, specify category like:
111
132
  ```
112
133
 
113
134
  #### Complete Category List:
135
+
114
136
  For a complete list of category codes, you can download from Naver Shopping Partner Center or extract them by browsing Naver Shopping categories.
115
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
+
116
229
  ## Installation
117
230
 
118
- ### Option 1: Quick Install via Smithery (Recommended)
231
+ ### Step 1: Download Source Code
119
232
 
120
- To install Naver Search MCP Server automatically via Smithery, use one of these commands based on your AI client:
233
+ To use this MCP server, you need to download the source code first:
121
234
 
122
- For Claude Desktop:
235
+ #### Clone with Git
123
236
 
124
237
  ```bash
125
- npx -y @smithery/cli@latest install @isnow890/naver-search-mcp --client claude
238
+ git clone https://github.com/isnow890/naver-search-mcp.git
239
+ cd naver-search-mcp
240
+ npm install
241
+ npm run build
126
242
  ```
127
243
 
128
- For Cursor:
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:
129
249
 
130
250
  ```bash
131
- npx -y @smithery/cli@latest install @isnow890/naver-search-mcp --client cursor
251
+ cd /path/to/naver-search-mcp
252
+ npm install
253
+ npm run build
132
254
  ```
133
255
 
134
- For Windsurf:
256
+ ### Step 2: Claude Desktop Configuration
135
257
 
136
- ```bash
137
- npx -y @smithery/cli@latest install @isnow890/naver-search-mcp --client windsurf
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
+ }
138
287
  ```
139
288
 
140
- For Cline:
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
+ }
308
+ ```
309
+
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:
141
318
 
142
319
  ```bash
143
- npx -y @smithery/cli@latest install @isnow890/naver-search-mcp --client cline
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
144
327
  ```
145
328
 
146
- The installer will prompt you for:
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
147
336
 
148
- - NAVER_CLIENT_ID
149
- - NAVER_CLIENT_SECRET
337
+ ### Option 2: NPX Installation (For v1.0.30 and below)
150
338
 
151
- ### Option 2: Manual Installation
339
+ For older versions that support NPX installation:
152
340
 
153
341
  #### Environment Variables
154
342
 
@@ -162,22 +350,7 @@ export NAVER_CLIENT_ID=your_client_id
162
350
  export NAVER_CLIENT_SECRET=your_client_secret
163
351
  ```
164
352
 
165
- #### Run with NPX
166
-
167
- ```bash
168
- npx @isnow890/naver-search-mcp
169
- ```
170
-
171
- #### Run with Docker
172
-
173
- ```bash
174
- docker run -i --rm \
175
- -e NAVER_CLIENT_ID=your_client_id \
176
- -e NAVER_CLIENT_SECRET=your_client_secret \
177
- mcp/naver-search
178
- ```
179
-
180
- ## Claude Desktop Configuration
353
+ #### Claude Desktop Configuration for NPX
181
354
 
182
355
  Add to `claude_desktop_config.json`:
183
356
 
@@ -196,7 +369,7 @@ Add to `claude_desktop_config.json`:
196
369
  }
197
370
  ```
198
371
 
199
- ## Cursor AI Configuration
372
+ #### Cursor AI Configuration for NPX
200
373
 
201
374
  Add to `mcp.json`:
202
375
 
@@ -215,7 +388,39 @@ Add to `mcp.json`:
215
388
  }
216
389
  ```
217
390
 
218
- For Docker:
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:
219
424
 
220
425
  ```json
221
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
+ };
package/dist/src/index.js CHANGED
@@ -4,18 +4,26 @@ import { z } from "zod";
4
4
  import { NaverSearchClient } from "./clients/naver-search.client.js";
5
5
  import { searchToolHandlers } from "./handlers/search.handlers.js";
6
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";
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";
9
11
  // Configuration schema for Smithery
10
12
  export const configSchema = z.object({
11
13
  NAVER_CLIENT_ID: z.string().describe("Naver API Client ID"),
12
- NAVER_CLIENT_SECRET: z.string().describe("Naver API Client Secret")
14
+ NAVER_CLIENT_SECRET: z.string().describe("Naver API Client Secret"),
13
15
  });
14
- function createNaverSearchServer({ config }) {
15
- // Create a new MCP server per MCP spec
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
16
24
  const server = new McpServer({
17
25
  name: "naver-search",
18
- version: "1.0.30",
26
+ version: "1.0.40",
19
27
  });
20
28
  // Initialize Naver client with config
21
29
  const client = NaverSearchClient.getInstance();
@@ -23,166 +31,208 @@ function createNaverSearchServer({ config }) {
23
31
  clientId: config.NAVER_CLIENT_ID,
24
32
  clientSecret: config.NAVER_CLIENT_SECRET,
25
33
  });
26
- // Register search tools using new 1.17.1 API
27
- server.registerTool("search_webkr", {
28
- description: "Perform a search on Naver Web Documents. (네이버 웹문서 검색)",
29
- inputSchema: SearchArgsSchema.shape
30
- }, async (args) => {
31
- const result = await searchToolHandlers.search_webkr(args);
32
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
33
- });
34
34
  server.registerTool("search_news", {
35
- description: "Perform a search on Naver News. (네이버 뉴스 검색)",
36
- inputSchema: SearchArgsSchema.shape
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
37
  }, async (args) => {
38
38
  const result = await searchToolHandlers.search_news(args);
39
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
39
+ return {
40
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
41
+ };
40
42
  });
41
43
  server.registerTool("search_blog", {
42
- description: "Perform a search on Naver Blog. (네이버 블로그 검색)",
43
- inputSchema: SearchArgsSchema.shape
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,
44
46
  }, async (args) => {
45
47
  const result = await searchToolHandlers.search_blog(args);
46
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
48
+ return {
49
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
50
+ };
47
51
  });
48
52
  server.registerTool("search_shop", {
49
- description: "Perform a search on Naver Shopping. (네이버 쇼핑 검색)",
50
- inputSchema: SearchArgsSchema.shape
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,
51
55
  }, async (args) => {
52
56
  const result = await searchToolHandlers.search_shop(args);
53
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
57
+ return {
58
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
59
+ };
54
60
  });
55
61
  server.registerTool("search_image", {
56
- description: "Perform a search on Naver Image. (네이버 이미지 검색)",
57
- inputSchema: SearchArgsSchema.shape
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,
58
64
  }, async (args) => {
59
65
  const result = await searchToolHandlers.search_image(args);
60
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
66
+ return {
67
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
68
+ };
61
69
  });
62
70
  server.registerTool("search_kin", {
63
- description: "Perform a search on Naver KnowledgeiN. (네이버 지식iN 검색)",
64
- inputSchema: SearchArgsSchema.shape
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,
65
73
  }, async (args) => {
66
74
  const result = await searchToolHandlers.search_kin(args);
67
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
75
+ return {
76
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
77
+ };
68
78
  });
69
79
  server.registerTool("search_book", {
70
- description: "Perform a search on Naver Book. (네이버 책 검색)",
71
- inputSchema: SearchArgsSchema.shape
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,
72
82
  }, async (args) => {
73
83
  const result = await searchToolHandlers.search_book(args);
74
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
84
+ return {
85
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
86
+ };
75
87
  });
76
88
  server.registerTool("search_encyc", {
77
- description: "Perform a search on Naver Encyclopedia. (네이버 지식백과 검색)",
78
- inputSchema: SearchArgsSchema.shape
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,
79
91
  }, async (args) => {
80
92
  const result = await searchToolHandlers.search_encyc(args);
81
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
93
+ return {
94
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
95
+ };
82
96
  });
83
97
  server.registerTool("search_academic", {
84
- description: "Perform a search on Naver Academic. (네이버 전문자료 검색)",
85
- inputSchema: SearchArgsSchema.shape
98
+ description: "🎓 Search academic papers, research documents, and scholarly content. Access Korean academic resources, research papers, theses, and professional publications. (네이버 전문자료 검색 - 학술 논문과 전문 자료)",
99
+ inputSchema: SearchArgsSchema.shape,
86
100
  }, async (args) => {
87
101
  const result = await searchToolHandlers.search_academic(args);
88
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
102
+ return {
103
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
104
+ };
89
105
  });
90
106
  server.registerTool("search_local", {
91
- description: "Perform a search on Naver Local. (네이버 지역 검색)",
92
- inputSchema: NaverLocalSearchParamsSchema.shape
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,
93
109
  }, async (args) => {
94
110
  const result = await searchToolHandlers.search_local(args);
95
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
111
+ return {
112
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
113
+ };
96
114
  });
97
115
  server.registerTool("search_cafearticle", {
98
- description: "Perform a search on Naver Cafe Articles. (네이버 카페글 검색)",
99
- inputSchema: SearchArgsSchema.shape
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,
100
118
  }, async (args) => {
101
119
  const result = await searchToolHandlers.search_cafearticle(args);
102
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
120
+ return {
121
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
122
+ };
103
123
  });
104
124
  // Register datalab tools
105
125
  server.registerTool("datalab_search", {
106
- description: "Perform a trend analysis on Naver search keywords. (네이버 검색어 트렌드 분석)",
107
- inputSchema: DatalabSearchSchema.shape
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,
108
128
  }, async (args) => {
109
129
  const result = await datalabToolHandlers.datalab_search(args);
110
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
130
+ return {
131
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
132
+ };
111
133
  });
112
134
  server.registerTool("datalab_shopping_category", {
113
- description: "Perform a trend analysis on Naver Shopping category. (네이버 쇼핑 카테고리별 트렌드 분석)",
114
- inputSchema: DatalabShoppingSchema.shape
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,
115
137
  }, async (args) => {
116
138
  const result = await datalabToolHandlers.datalab_shopping_category(args);
117
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
139
+ return {
140
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
141
+ };
118
142
  });
119
143
  server.registerTool("datalab_shopping_by_device", {
120
- description: "Perform a trend analysis on Naver 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 도구로 카테고리 코드를 찾으세요)",
121
145
  inputSchema: DatalabShoppingDeviceSchema.pick({
122
146
  startDate: true,
123
147
  endDate: true,
124
148
  timeUnit: true,
125
149
  category: true,
126
150
  device: true,
127
- }).shape
151
+ }).shape,
128
152
  }, async (args) => {
129
153
  const result = await datalabToolHandlers.datalab_shopping_by_device(args);
130
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
154
+ return {
155
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
156
+ };
131
157
  });
132
158
  server.registerTool("datalab_shopping_by_gender", {
133
- description: "Perform a trend analysis on Naver 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 도구로 카테고리 코드를 찾으세요)",
134
160
  inputSchema: DatalabShoppingGenderSchema.pick({
135
161
  startDate: true,
136
162
  endDate: true,
137
163
  timeUnit: true,
138
164
  category: true,
139
165
  gender: true,
140
- }).shape
166
+ }).shape,
141
167
  }, async (args) => {
142
168
  const result = await datalabToolHandlers.datalab_shopping_by_gender(args);
143
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
169
+ return {
170
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
171
+ };
144
172
  });
145
173
  server.registerTool("datalab_shopping_by_age", {
146
- description: "Perform a trend analysis on Naver 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 도구로 카테고리 코드를 찾으세요)",
147
175
  inputSchema: DatalabShoppingAgeSchema.pick({
148
176
  startDate: true,
149
177
  endDate: true,
150
178
  timeUnit: true,
151
179
  category: true,
152
180
  ages: true,
153
- }).shape
181
+ }).shape,
154
182
  }, async (args) => {
155
183
  const result = await datalabToolHandlers.datalab_shopping_by_age(args);
156
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
184
+ return {
185
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
186
+ };
157
187
  });
158
188
  server.registerTool("datalab_shopping_keywords", {
159
- description: "Perform a trend analysis on Naver Shopping keywords. (네이버 쇼핑 키워드별 트렌드 분석)",
160
- inputSchema: DatalabShoppingKeywordsSchema.shape
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,
161
191
  }, async (args) => {
162
192
  const result = await datalabToolHandlers.datalab_shopping_keywords(args);
163
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
193
+ return {
194
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
195
+ };
164
196
  });
165
197
  server.registerTool("datalab_shopping_keyword_by_device", {
166
- description: "Perform a trend analysis on Naver Shopping keywords by device. (네이버 쇼핑 키워드 기기별 트렌드 분석)",
167
- inputSchema: DatalabShoppingKeywordDeviceSchema.shape
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,
168
200
  }, async (args) => {
169
201
  const result = await datalabToolHandlers.datalab_shopping_keyword_by_device(args);
170
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
202
+ return {
203
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
204
+ };
171
205
  });
172
206
  server.registerTool("datalab_shopping_keyword_by_gender", {
173
- description: "Perform a trend analysis on Naver Shopping keywords by gender. (네이버 쇼핑 키워드 성별 트렌드 분석)",
174
- inputSchema: DatalabShoppingKeywordGenderSchema.shape
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,
175
209
  }, async (args) => {
176
210
  const result = await datalabToolHandlers.datalab_shopping_keyword_by_gender(args);
177
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
211
+ return {
212
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
213
+ };
178
214
  });
179
215
  server.registerTool("datalab_shopping_keyword_by_age", {
180
- description: "Perform a trend analysis on Naver Shopping keywords by age. (네이버 쇼핑 키워드 연령별 트렌드 분석)",
181
- inputSchema: DatalabShoppingKeywordAgeSchema.shape
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,
182
218
  }, async (args) => {
183
219
  const result = await datalabToolHandlers.datalab_shopping_keyword_by_age(args);
184
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
220
+ return {
221
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
222
+ };
185
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;
186
236
  return server.server;
187
237
  }
188
238
  // Export default for Smithery compatibility
@@ -195,25 +245,29 @@ async function main() {
195
245
  const clientId = process.env.NAVER_CLIENT_ID?.trim();
196
246
  const clientSecret = process.env.NAVER_CLIENT_SECRET?.trim();
197
247
  console.error("Environment variables:", {
198
- NAVER_CLIENT_ID: process.env.NAVER_CLIENT_ID ? `[${process.env.NAVER_CLIENT_ID.length} chars]` : 'undefined',
199
- NAVER_CLIENT_SECRET: process.env.NAVER_CLIENT_SECRET ? `[${process.env.NAVER_CLIENT_SECRET.length} chars]` : 'undefined'
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",
200
254
  });
201
255
  if (!clientId || !clientSecret) {
202
256
  throw new Error(`Missing required environment variables:
203
- NAVER_CLIENT_ID: ${clientId ? 'provided' : 'missing'}
204
- NAVER_CLIENT_SECRET: ${clientSecret ? 'provided' : 'missing'}
257
+ NAVER_CLIENT_ID: ${clientId ? "provided" : "missing"}
258
+ NAVER_CLIENT_SECRET: ${clientSecret ? "provided" : "missing"}
205
259
 
206
260
  Please set these environment variables before running the server.`);
207
261
  }
208
262
  const config = {
209
263
  NAVER_CLIENT_ID: clientId,
210
- NAVER_CLIENT_SECRET: clientSecret
264
+ NAVER_CLIENT_SECRET: clientSecret,
211
265
  };
212
266
  console.error("Config loaded successfully");
213
267
  // Validate config
214
268
  const validatedConfig = configSchema.parse(config);
215
269
  console.error("Config validated successfully");
216
- // Create server instance
270
+ // Create server instance
217
271
  const serverFactory = createNaverSearchServer({ config: validatedConfig });
218
272
  console.error("Server factory created");
219
273
  // Create transport and run server
@@ -230,12 +284,31 @@ async function main() {
230
284
  // Run main function if this file is executed directly
231
285
  // Note: Always run main in CLI mode since this is an MCP server
232
286
  console.error("Starting MCP server initialization...");
233
- console.error("import.meta.url:", import.meta.url);
234
287
  console.error("process.argv:", process.argv);
235
- // Check if running as main module
236
- const isMainModule = import.meta.url === `file://${process.argv[1]}` ||
237
- import.meta.url.endsWith(process.argv[1]) ||
238
- process.argv[1].endsWith("index.js");
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
+ }
239
312
  console.error("isMainModule:", isMainModule);
240
313
  if (isMainModule) {
241
314
  console.error("Running as main module, starting server...");
@@ -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.30",
3
+ "version": "1.0.40",
4
4
  "description": "Naver Search MCP Server",
5
5
  "main": "dist/index.js",
6
6
  "module": "./src/index.ts",
@@ -10,7 +10,7 @@
10
10
  ],
11
11
  "scripts": {
12
12
  "prebuild": "npx shx rm -rf dist",
13
- "build": "tsc",
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"
@@ -24,7 +24,8 @@
24
24
  "zod": "^3.23.0",
25
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
- // Remove zodToJsonSchema import - using Zod schemas directly
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: DatalabSearchSchema.shape,
11
- },
12
- {
13
- name: "datalab_shopping_category",
14
- description: "Perform a trend analysis on Naver Shopping category. (네이버 쇼핑 카테고리별 트렌드 분석)",
15
- inputSchema: DatalabShoppingSchema.shape,
16
- },
17
- {
18
- name: "datalab_shopping_by_device",
19
- description: "Perform a trend analysis on Naver Shopping by device. (네이버 쇼핑 기기별 트렌드 분석)",
20
- inputSchema: DatalabShoppingDeviceSchema.pick({
21
- startDate: true,
22
- endDate: true,
23
- timeUnit: true,
24
- category: true,
25
- device: true,
26
- }).shape,
27
- },
28
- {
29
- name: "datalab_shopping_by_gender",
30
- description: "Perform a trend analysis on Naver Shopping by gender. (네이버 쇼핑 성별 트렌드 분석)",
31
- inputSchema: DatalabShoppingGenderSchema.pick({
32
- startDate: true,
33
- endDate: true,
34
- timeUnit: true,
35
- category: true,
36
- gender: true,
37
- }).shape,
38
- },
39
- {
40
- name: "datalab_shopping_by_age",
41
- description: "Perform a trend analysis on Naver Shopping by age. (네이버 쇼핑 연령별 트렌드 분석)",
42
- inputSchema: DatalabShoppingAgeSchema.pick({
43
- startDate: true,
44
- endDate: true,
45
- timeUnit: true,
46
- category: true,
47
- ages: true,
48
- }).shape,
49
- },
50
- {
51
- name: "datalab_shopping_keywords",
52
- description: "Perform a trend analysis on Naver Shopping keywords. (네이버 쇼핑 키워드별 트렌드 분석)",
53
- inputSchema: DatalabShoppingKeywordsSchema.shape,
54
- },
55
- {
56
- name: "datalab_shopping_keyword_by_device",
57
- description: "Perform a trend analysis on Naver Shopping keywords by device. (네이버 쇼핑 키워드 기기별 트렌드 분석)",
58
- inputSchema: DatalabShoppingKeywordDeviceSchema.shape,
59
- },
60
- {
61
- name: "datalab_shopping_keyword_by_gender",
62
- description: "Perform a trend analysis on Naver Shopping keywords by gender. (네이버 쇼핑 키워드 성별 트렌드 분석)",
63
- inputSchema: DatalabShoppingKeywordGenderSchema.shape,
64
- },
65
- {
66
- name: "datalab_shopping_keyword_by_age",
67
- description: "Perform a trend analysis on Naver Shopping keywords by age. (네이버 쇼핑 키워드 연령별 트렌드 분석)",
68
- inputSchema: DatalabShoppingKeywordAgeSchema.shape,
69
- },
70
- ];
@@ -1,61 +0,0 @@
1
- import { SearchArgsSchema, NaverLocalSearchParamsSchema, } from "../schemas/search.schemas.js";
2
- /**
3
- * 검색 관련 도구 정의
4
- */
5
- export const searchTools = [
6
- {
7
- name: "search_webkr",
8
- description: "Perform a search on Naver Web Documents. (네이버 웹문서 검색)",
9
- inputSchema: SearchArgsSchema.shape,
10
- },
11
- {
12
- name: "search_news",
13
- description: "Perform a search on Naver News. (네이버 뉴스 검색)",
14
- inputSchema: SearchArgsSchema.shape,
15
- },
16
- {
17
- name: "search_blog",
18
- description: "Perform a search on Naver Blog. (네이버 블로그 검색)",
19
- inputSchema: SearchArgsSchema.shape,
20
- },
21
- {
22
- name: "search_shop",
23
- description: "Perform a search on Naver Shopping. (네이버 쇼핑 검색)",
24
- inputSchema: SearchArgsSchema.shape,
25
- },
26
- {
27
- name: "search_image",
28
- description: "Perform a search on Naver Image. (네이버 이미지 검색)",
29
- inputSchema: SearchArgsSchema.shape,
30
- },
31
- {
32
- name: "search_kin",
33
- description: "Perform a search on Naver KnowledgeiN. (네이버 지식iN 검색)",
34
- inputSchema: SearchArgsSchema.shape,
35
- },
36
- {
37
- name: "search_book",
38
- description: "Perform a search on Naver Book. (네이버 책 검색)",
39
- inputSchema: SearchArgsSchema.shape,
40
- },
41
- {
42
- name: "search_encyc",
43
- description: "Perform a search on Naver Encyclopedia. (네이버 지식백과 검색)",
44
- inputSchema: SearchArgsSchema.shape,
45
- },
46
- {
47
- name: "search_academic",
48
- description: "Perform a search on Naver Academic. (네이버 전문자료 검색)",
49
- inputSchema: SearchArgsSchema.shape,
50
- },
51
- {
52
- name: "search_local",
53
- description: "Perform a search on Naver Local. (네이버 지역 검색)",
54
- inputSchema: NaverLocalSearchParamsSchema.shape,
55
- },
56
- {
57
- name: "search_cafearticle",
58
- description: "Perform a search on Naver Cafe Articles. (네이버 카페글 검색)",
59
- inputSchema: SearchArgsSchema.shape,
60
- },
61
- ];