@isnow890/naver-search-mcp 1.0.48 → 1.0.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +85 -8
  3. package/dist/data/categories.json +0 -0
  4. package/dist/src/clients/naver-api-core.client.d.ts +13 -2
  5. package/dist/src/clients/naver-api-core.client.js +42 -12
  6. package/dist/src/clients/naver-api-endpoints.d.ts +16 -0
  7. package/dist/src/clients/naver-api-endpoints.js +22 -0
  8. package/dist/src/clients/naver-api-error.d.ts +5 -0
  9. package/dist/src/clients/naver-api-error.js +33 -0
  10. package/dist/src/clients/naver-search.client.d.ts +2 -6
  11. package/dist/src/clients/naver-search.client.js +9 -15
  12. package/dist/src/config/credentials.d.ts +20 -0
  13. package/dist/src/config/credentials.js +53 -0
  14. package/dist/src/handlers/category.handlers.d.ts +0 -0
  15. package/dist/src/handlers/category.handlers.js +0 -0
  16. package/dist/src/handlers/datalab.handlers.d.ts +0 -0
  17. package/dist/src/handlers/datalab.handlers.js +0 -0
  18. package/dist/src/handlers/search.handlers.d.ts +0 -12
  19. package/dist/src/handlers/search.handlers.js +0 -30
  20. package/dist/src/index.d.ts +12 -6
  21. package/dist/src/index.js +45 -54
  22. package/dist/src/schemas/category.schemas.d.ts +0 -0
  23. package/dist/src/schemas/category.schemas.js +0 -0
  24. package/dist/src/schemas/datalab.schemas.d.ts +0 -0
  25. package/dist/src/schemas/datalab.schemas.js +0 -0
  26. package/dist/src/schemas/search.schemas.d.ts +4 -15
  27. package/dist/src/schemas/search.schemas.js +5 -13
  28. package/dist/src/types/datalab.types.d.ts +0 -0
  29. package/dist/src/types/datalab.types.js +0 -0
  30. package/dist/src/types/search.types.d.ts +0 -8
  31. package/dist/src/types/search.types.js +0 -0
  32. package/dist/src/types/time.types.d.ts +0 -0
  33. package/dist/src/types/time.types.js +0 -0
  34. package/dist/src/utils/date.utils.d.ts +0 -0
  35. package/dist/src/utils/date.utils.js +0 -0
  36. package/package.json +3 -2
package/LICENSE CHANGED
File without changes
package/README.md CHANGED
@@ -7,6 +7,25 @@
7
7
 
8
8
  MCP server for Naver Search API and DataLab API integration, enabling comprehensive search across various Naver services and data trend analysis.
9
9
 
10
+ ## ⚠️ Naver is migrating these APIs — read this before you set up
11
+
12
+ Naver is moving Search, Search Trend, and Shopping Insight from the Naver Developers Center to **NAVER API HUB** on NAVER Cloud Platform, and is shutting down three search APIs entirely.
13
+
14
+ | Date | What happens |
15
+ |---|---|
16
+ | **2026-07-31** | Developers Center stops accepting **new** key applications.<br>Shopping / Book / Academic search APIs shut down completely. |
17
+ | **2027-06-30** | Developers Center support ends — **existing keys stop working**. |
18
+
19
+ Source: [Naver Developers Center official notice](https://developers.naver.com/notice/article/32530) (Korean) — "Search API, Search Trend, Shopping Insight 서비스 종료 및 NAVER API HUB 이관 안내".
20
+
21
+ **Setting this up for the first time?** Get your keys from NAVER API HUB. The Developers Center path closes to new applicants on 2026-07-31, so it is no longer the place to start. Step-by-step instructions are in [Option A: NAVER API HUB](#option-a-naver-api-hub-recommended--the-forward-path) below.
22
+
23
+ **Already running with Developers Center keys?** Nothing to change. They keep working until 2027-06-30, and this version supports both platforms from the same install. When you're ready, get a HUB key and swap the two environment variables — no other changes.
24
+
25
+ **Support plan for this server:** both platforms stay supported side by side through Naver's 2027-06-30 cutoff. The Developers Center path will be dropped in a later major version only after that date, once it can no longer work for anyone — so upgrading in the meantime will never take your working setup away.
26
+
27
+ **Three tools were removed in 1.0.49**: `search_shop`, `search_book`, `search_academic`. Naver shuts those search APIs down on 2026-07-31 with no replacement on any platform, so there is nothing this server can do to keep them. Shopping **Insight** (`datalab_shopping_*`) and `find_category` are a *different* API and are **not** affected.
28
+
10
29
  ## Available on ClawHub for OpenClaw
11
30
 
12
31
  You can install this MCP server as an OpenClaw skill from ClawHub:
@@ -15,7 +34,7 @@ You can install this MCP server as an OpenClaw skill from ClawHub:
15
34
  openclaw skills install naver-search-mcp
16
35
  ```
17
36
 
18
- The ClawHub skill uses the published npm package internally and requires `NAVER_CLIENT_ID` and `NAVER_CLIENT_SECRET` in your OpenClaw environment.
37
+ The ClawHub skill uses the published npm package internally and needs **one** credential pair in your OpenClaw environment — either the NAVER API HUB pair (`NCP_APIGW_API_KEY_ID` / `NCP_APIGW_API_KEY`) or the Developers Center pair (`NAVER_CLIENT_ID` / `NAVER_CLIENT_SECRET`). OpenClaw's `apiKey` field maps to `NAVER_CLIENT_SECRET`; HUB users should set both HUB variables explicitly in the environment instead of using `apiKey`.
19
38
 
20
39
  ## Quick Start: Use Without API Key
21
40
 
@@ -35,14 +54,16 @@ You can use this server immediately without API keys through [Kakao PlayMCP](htt
35
54
  - **search_news**: Search Naver news
36
55
  - **search_blog**: Search Naver blogs
37
56
  - **search_cafearticle**: Search Naver cafe articles
38
- - **search_shop**: Search Naver shopping
39
57
  - **search_image**: Search Naver images
40
58
  - **search_kin**: Search Naver KnowledgeiN
41
- - **search_book**: Search Naver books
42
59
  - **search_encyc**: Search Naver encyclopedia
43
- - **search_academic**: Search Naver academic papers
44
60
  - **search_local**: Search Naver local places
45
61
 
62
+ > **Removed in 1.0.49:** `search_shop`, `search_book`, `search_academic`.
63
+ > Naver shuts down the Shopping / Book / Academic search APIs on 2026-07-31 with
64
+ > no replacement on any platform. This is not a limitation of this server.
65
+ > Shopping **Insight** (`datalab_shopping_*`) is a different API and is unaffected.
66
+
46
67
  #### DataLab Tools
47
68
 
48
69
  - **datalab_search**: Analyze search term trends
@@ -57,18 +78,67 @@ You can use this server immediately without API keys through [Kakao PlayMCP](htt
57
78
 
58
79
  ## Getting API Keys
59
80
 
81
+ Two platforms issue keys for this server. Set **one** pair of environment variables — see
82
+ [Configuration](#configuration) below for how the pair you set determines the platform used.
83
+
84
+ ### Option A: NAVER API HUB (recommended — the forward path)
85
+
86
+ 1. Go to the [NAVER Cloud Platform console](https://www.ncloud.com) and sign up or log in.
87
+ 2. Click the region & platform selector in the top right of the console, choose your region/platform, and click **Apply**.
88
+ 3. Open **Menu > All Services > Application Services > [NAVER API HUB](https://www.ncloud.com/product/applicationService/naverApiHub)**.
89
+ 4. Click **Application**, select (or create) your application, then under API management click **인증 정보 (Authentication Info)**.
90
+ 5. Copy the **Client ID** and **Client Secret** shown in the popup.
91
+ 6. Use these as `NCP_APIGW_API_KEY_ID` (Client ID) and `NCP_APIGW_API_KEY` (Client Secret) in the configuration below.
92
+
93
+ ### Option B: Naver Developers (legacy — existing keys only)
94
+
95
+ > Naver Developers Center stops accepting new applications on **2026-07-31**. If you don't
96
+ > already have a Client ID/Secret from this platform, use NAVER API HUB above instead.
97
+ > Existing Developers Center keys keep working until 2027-06-30.
98
+
60
99
  1. Visit [Naver Developers](https://developers.naver.com/apps/#/register) and log in with your Naver account
61
100
  2. Click the "Application Registration" (애플리케이션 등록) button
62
101
  3. Fill in the application information:
63
102
  - **Application Name**: Enter any name (e.g., "Naver Search MCP")
64
103
  - **Usage**: Select "Search" (검색)
65
104
  4. In the API Settings section, check ALL of the following APIs:
66
- - **Search** (검색) - Required for blog, news, book, cafe article, web, image, kin, encyclopedia, academic, and local search
105
+ - **Search** (검색) - Required for blog, news, cafe article, web, image, kin, encyclopedia, and local search
67
106
  - **DataLab - Search Trends** (데이터랩 - 검색어 트렌드) - Required for search term trend analysis
68
107
  - **DataLab - Shopping Insight** (데이터랩 - 쇼핑인사이트) - Required for shopping trend analysis
69
108
  5. Click "Register" to complete registration
70
109
  6. After registration, you'll see your **Client ID** and **Client Secret** on the application detail page
71
- 7. Use these credentials in the configuration below
110
+ 7. Use these as `NAVER_CLIENT_ID` / `NAVER_CLIENT_SECRET` in the configuration below
111
+
112
+ ## Configuration
113
+
114
+ ### Credentials
115
+
116
+ Two platforms are supported. Set **one** pair — the server picks the platform from
117
+ which variables you set, since the key strings themselves are indistinguishable.
118
+
119
+ | Variables | Platform | Endpoint |
120
+ |---|---|---|
121
+ | `NCP_APIGW_API_KEY_ID`, `NCP_APIGW_API_KEY` | NAVER API HUB (NCP) | `naverapihub.apigw.ntruss.com` |
122
+ | `NAVER_CLIENT_ID`, `NAVER_CLIENT_SECRET` | Naver Developers (legacy) | `openapi.naver.com` |
123
+
124
+ If both pairs are set, NAVER API HUB wins.
125
+
126
+ The installation examples below use the legacy `NAVER_CLIENT_ID` / `NAVER_CLIENT_SECRET`
127
+ pair; swap in the HUB pair from the table above if that's what you have.
128
+
129
+ ### Migration to NAVER API HUB
130
+
131
+ Naver is moving these APIs from the Developers Center to NAVER API HUB on NAVER Cloud Platform.
132
+
133
+ | Date | What happens |
134
+ |---|---|
135
+ | 2026-06-25 | NAVER API HUB launched |
136
+ | 2026-07-31 | Developers Center stops accepting new applications |
137
+ | 2027-06-30 | Developers Center support ends — existing keys stop working |
138
+
139
+ Existing keys keep working until 2027-06-30. To migrate, get a key from the
140
+ NAVER Cloud Platform console and set `NCP_APIGW_API_KEY_ID` / `NCP_APIGW_API_KEY`.
141
+ Developers Center keys cannot be used against NAVER API HUB.
72
142
 
73
143
  ## Installation
74
144
 
@@ -128,7 +198,7 @@ The ClawHub skill uses the same published npm package internally:
128
198
  npx -y @isnow890/naver-search-mcp
129
199
  ```
130
200
 
131
- Make sure `NAVER_CLIENT_ID` and `NAVER_CLIENT_SECRET` are configured in your OpenClaw environment before using the skill.
201
+ Make sure **one** credential pair is configured in your OpenClaw environment before using the skill — the NAVER API HUB pair (`NCP_APIGW_API_KEY_ID` / `NCP_APIGW_API_KEY`) or the Developers Center pair (`NAVER_CLIENT_ID` / `NAVER_CLIENT_SECRET`). OpenClaw's `apiKey` field maps to `NAVER_CLIENT_SECRET`; HUB users should set both HUB variables explicitly instead.
132
202
 
133
203
  ### Method 3: Local Installation
134
204
 
@@ -239,7 +309,7 @@ After completing the configuration, completely close and restart Claude Desktop
239
309
 
240
310
  ## Prerequisites
241
311
 
242
- - Naver Developers API Key (Client ID and Secret)
312
+ - A credential pair for one platform — NAVER API HUB or Naver Developers (see [Getting API Keys](#getting-api-keys))
243
313
  - Node.js 18 or higher
244
314
  - NPM 8 or higher
245
315
 
@@ -251,6 +321,13 @@ MIT License
251
321
 
252
322
  ## Version History
253
323
 
324
+ ### 1.0.49 (2026-07-26)
325
+
326
+ - NAVER API HUB support: set `NCP_APIGW_API_KEY_ID` / `NCP_APIGW_API_KEY` to call the new platform. Existing `NAVER_CLIENT_ID` / `NAVER_CLIENT_SECRET` keep working unchanged — the platform is chosen by which variable pair you set
327
+ - **Removed** `search_shop`, `search_book`, `search_academic`. Naver shuts those search APIs down on 2026-07-31 with no replacement on any platform
328
+ - API errors now throw a plain `Error` instead of the raw `AxiosError`. Code branching on `error.response?.status` sees `undefined`; the same detail is in `Error.message`
329
+ - Error messages name the platform and HTTP status, and 401s suggest checking whether a key was set on the other platform's variables
330
+
254
331
  ### 1.0.48 (2026-05-12)
255
332
 
256
333
  - Fixed `find_category` data loading when the MCP server is launched by `npx` from a different working directory
File without changes
@@ -1,15 +1,26 @@
1
1
  import { AxiosRequestConfig, AxiosInstance } from "axios";
2
2
  import { NaverSearchConfig } from "../schemas/search.schemas.js";
3
+ import { NaverApiProvider } from "./naver-api-endpoints.js";
3
4
  export declare abstract class NaverApiCoreClient {
4
- protected searchBaseUrl: string;
5
- protected datalabBaseUrl: string;
6
5
  protected config: NaverSearchConfig | null;
7
6
  protected axiosInstance: AxiosInstance;
8
7
  constructor();
9
8
  initialize(config: NaverSearchConfig): void;
9
+ private requireConfig;
10
+ get provider(): NaverApiProvider;
11
+ /** 검색 API base. `${searchBaseUrl}/${type}` 형태로 사용한다. */
12
+ get searchBaseUrl(): string;
13
+ /** 검색어 트렌드 전체 URL. suffix를 붙이지 않는다. */
14
+ get trendUrl(): string;
15
+ /** 쇼핑인사이트 base. `${shoppingBaseUrl}/categories` 형태로 사용한다. */
16
+ get shoppingBaseUrl(): string;
10
17
  protected getHeaders(contentType?: string): AxiosRequestConfig;
11
18
  protected get<T>(url: string, params: any): Promise<T>;
12
19
  protected post<T>(url: string, data: any): Promise<T>;
20
+ /**
21
+ * 두 플랫폼의 오류 응답 형식이 다르므로 파싱하지 않고 그대로 감싼다.
22
+ */
23
+ private wrapError;
13
24
  /**
14
25
  * 리소스 정리 메서드 (메모리 누수 방지)
15
26
  */
@@ -1,58 +1,88 @@
1
1
  import axios from "axios";
2
+ import { getEndpoints, getAuthHeaderNames, } from "./naver-api-endpoints.js";
3
+ import { formatApiError } from "./naver-api-error.js";
2
4
  export class NaverApiCoreClient {
3
5
  constructor() {
4
- this.searchBaseUrl = "https://openapi.naver.com/v1/search";
5
- this.datalabBaseUrl = "https://openapi.naver.com/v1/datalab";
6
6
  this.config = null;
7
7
  // HTTP/HTTPS 에이전트 설정으로 연결 풀링 및 메모리 누수 방지
8
8
  this.axiosInstance = axios.create({
9
9
  timeout: 30000, // 30초 타임아웃
10
10
  maxRedirects: 3,
11
- // HTTP 연결 풀링 설정은 운영체제에서 처리하도록 단순화
12
11
  });
13
12
  }
14
13
  initialize(config) {
15
14
  this.config = config;
16
15
  }
17
- getHeaders(contentType = "application/json") {
16
+ requireConfig() {
18
17
  if (!this.config)
19
18
  throw new Error("NaverApiCoreClient is not initialized.");
19
+ return this.config;
20
+ }
21
+ get provider() {
22
+ return this.requireConfig().provider;
23
+ }
24
+ /** 검색 API base. `${searchBaseUrl}/${type}` 형태로 사용한다. */
25
+ get searchBaseUrl() {
26
+ return getEndpoints(this.provider).search;
27
+ }
28
+ /** 검색어 트렌드 전체 URL. suffix를 붙이지 않는다. */
29
+ get trendUrl() {
30
+ return getEndpoints(this.provider).trend;
31
+ }
32
+ /** 쇼핑인사이트 base. `${shoppingBaseUrl}/categories` 형태로 사용한다. */
33
+ get shoppingBaseUrl() {
34
+ return getEndpoints(this.provider).shopping;
35
+ }
36
+ getHeaders(contentType = "application/json") {
37
+ const config = this.requireConfig();
38
+ const names = getAuthHeaderNames(config.provider);
20
39
  return {
21
40
  headers: {
22
- "X-Naver-Client-Id": this.config.clientId,
23
- "X-Naver-Client-Secret": this.config.clientSecret,
41
+ [names.id]: config.clientId,
42
+ [names.secret]: config.clientSecret,
24
43
  "Content-Type": contentType,
25
44
  },
26
45
  };
27
46
  }
28
47
  async get(url, params) {
48
+ // provider를 요청 시점에 고정한다. 응답이 도착하기 전에 destroyInstance()가
49
+ // config를 비울 수 있고, 그때 this.provider를 읽으면 원래 에러가 사라진다.
50
+ const provider = this.provider;
29
51
  try {
30
52
  const response = await this.axiosInstance.get(url, {
31
53
  params,
32
- ...this.getHeaders()
54
+ ...this.getHeaders(),
33
55
  });
34
56
  return response.data;
35
57
  }
36
58
  catch (error) {
37
- // 연결 정리는 axios가 자동으로 처리하지만 명시적으로 에러 처리
38
- throw error;
59
+ throw this.wrapError(provider, url, error);
39
60
  }
40
61
  }
41
62
  async post(url, data) {
63
+ const provider = this.provider;
42
64
  try {
43
65
  const response = await this.axiosInstance.post(url, data, this.getHeaders());
44
66
  return response.data;
45
67
  }
46
68
  catch (error) {
47
- // 연결 정리는 axios가 자동으로 처리하지만 명시적으로 에러 처리
48
- throw error;
69
+ throw this.wrapError(provider, url, error);
49
70
  }
50
71
  }
72
+ /**
73
+ * 두 플랫폼의 오류 응답 형식이 다르므로 파싱하지 않고 그대로 감싼다.
74
+ */
75
+ wrapError(provider, url, error) {
76
+ const response = error?.response;
77
+ // 응답이 아예 없는 실패(타임아웃, DNS 실패, 연결 거부)는 axios 메시지가 유일한
78
+ // 단서다. 이걸 버리면 모든 네트워크 오류가 똑같은 문장으로 뭉개진다.
79
+ const body = response ? response.data : error?.message;
80
+ return new Error(formatApiError(provider, url, response?.status, body));
81
+ }
51
82
  /**
52
83
  * 리소스 정리 메서드 (메모리 누수 방지)
53
84
  */
54
85
  cleanup() {
55
- // 연결 정리 - axios가 자동으로 처리
56
86
  this.config = null;
57
87
  }
58
88
  }
@@ -0,0 +1,16 @@
1
+ /** 네이버 API 제공 플랫폼 */
2
+ export type NaverApiProvider = "legacy" | "hub";
3
+ export interface NaverApiEndpoints {
4
+ /** 검색 API base. `${search}/${type}` 형태로 사용한다. */
5
+ search: string;
6
+ /** 검색어 트렌드 전체 URL. suffix를 붙이지 않는다. */
7
+ trend: string;
8
+ /** 쇼핑인사이트 base. `${shopping}/categories` 형태로 사용한다. */
9
+ shopping: string;
10
+ }
11
+ export interface NaverAuthHeaderNames {
12
+ id: string;
13
+ secret: string;
14
+ }
15
+ export declare function getEndpoints(provider: NaverApiProvider): NaverApiEndpoints;
16
+ export declare function getAuthHeaderNames(provider: NaverApiProvider): NaverAuthHeaderNames;
@@ -0,0 +1,22 @@
1
+ const ENDPOINTS = {
2
+ legacy: {
3
+ search: "https://openapi.naver.com/v1/search",
4
+ trend: "https://openapi.naver.com/v1/datalab/search",
5
+ shopping: "https://openapi.naver.com/v1/datalab/shopping",
6
+ },
7
+ hub: {
8
+ search: "https://naverapihub.apigw.ntruss.com/search/v1",
9
+ trend: "https://naverapihub.apigw.ntruss.com/search-trend/v1/search",
10
+ shopping: "https://naverapihub.apigw.ntruss.com/shopping/v1",
11
+ },
12
+ };
13
+ const AUTH_HEADER_NAMES = {
14
+ legacy: { id: "X-Naver-Client-Id", secret: "X-Naver-Client-Secret" },
15
+ hub: { id: "X-NCP-APIGW-API-KEY-ID", secret: "X-NCP-APIGW-API-KEY" },
16
+ };
17
+ export function getEndpoints(provider) {
18
+ return ENDPOINTS[provider];
19
+ }
20
+ export function getAuthHeaderNames(provider) {
21
+ return AUTH_HEADER_NAMES[provider];
22
+ }
@@ -0,0 +1,5 @@
1
+ import { NaverApiProvider } from "./naver-api-endpoints.js";
2
+ /**
3
+ * 두 플랫폼의 오류 응답 형식이 다르므로 파싱하지 않고 그대로 실어 보낸다.
4
+ */
5
+ export declare function formatApiError(provider: NaverApiProvider, url: string, status: number | undefined, body: unknown): string;
@@ -0,0 +1,33 @@
1
+ const PROVIDER_LABEL = {
2
+ legacy: "네이버 개발자센터",
3
+ hub: "NAVER API HUB",
4
+ };
5
+ const SWAPPED_KEY_HINT = {
6
+ legacy: "인증에 실패했습니다. NAVER API HUB 키를 NAVER_CLIENT_ID/NAVER_CLIENT_SECRET에 넣지 않았는지 확인하세요. HUB 키는 NCP_APIGW_API_KEY_ID/NCP_APIGW_API_KEY에 넣어야 합니다.",
7
+ hub: "인증에 실패했습니다. 개발자센터 키를 NCP_APIGW_API_KEY_ID/NCP_APIGW_API_KEY에 넣지 않았는지 확인하세요. 개발자센터 키는 NAVER_CLIENT_ID/NAVER_CLIENT_SECRET에 넣어야 합니다.",
8
+ };
9
+ function stringifyBody(body) {
10
+ if (body === undefined || body === null)
11
+ return "";
12
+ if (typeof body === "string")
13
+ return body;
14
+ try {
15
+ return JSON.stringify(body);
16
+ }
17
+ catch {
18
+ return String(body);
19
+ }
20
+ }
21
+ /**
22
+ * 두 플랫폼의 오류 응답 형식이 다르므로 파싱하지 않고 그대로 실어 보낸다.
23
+ */
24
+ export function formatApiError(provider, url, status, body) {
25
+ const statusText = status === undefined ? "no response" : `HTTP ${status}`;
26
+ const lines = [`[${PROVIDER_LABEL[provider]}] ${statusText} — ${url}`];
27
+ const bodyText = stringifyBody(body);
28
+ if (bodyText)
29
+ lines.push(bodyText);
30
+ if (status === 401)
31
+ lines.push(SWAPPED_KEY_HINT[provider]);
32
+ return lines.join("\n");
33
+ }
@@ -1,6 +1,6 @@
1
1
  import { NaverApiCoreClient } from "./naver-api-core.client.js";
2
- import { NaverSearchType, NaverSearchConfig, NaverLocalSearchParams, NaverDocumentSearchParams, SearchArgs } from "../schemas/search.schemas.js";
3
- import { NaverDocumentSearchResponse, NaverLocalSearchResponse } from "../types/search.types.js";
2
+ import { NaverSearchType, NaverSearchConfig, NaverLocalSearchParams, SearchArgs } from "../schemas/search.schemas.js";
3
+ import { NaverLocalSearchResponse } from "../types/search.types.js";
4
4
  import { DatalabSearchRequest, DatalabShoppingResponse, DatalabShoppingCategoryRequest, DatalabShoppingDeviceRequest, DatalabShoppingGenderRequest, DatalabShoppingAgeRequest, DatalabShoppingKeywordsRequest, DatalabShoppingKeywordRequest } from "../types/datalab.types.js";
5
5
  /**
6
6
  * NaverSearchClient - 네이버 API 서비스를 위한 싱글톤 클라이언트
@@ -31,10 +31,6 @@ export declare class NaverSearchClient extends NaverApiCoreClient {
31
31
  * @param params 검색 파라미터
32
32
  */
33
33
  search(type: NaverSearchType, params: SearchArgs): Promise<any>;
34
- /**
35
- * 전문자료 검색 메서드
36
- */
37
- searchAcademic(params: NaverDocumentSearchParams): Promise<NaverDocumentSearchResponse>;
38
34
  /**
39
35
  * 지역 검색 메서드
40
36
  */
@@ -47,12 +47,6 @@ export class NaverSearchClient extends NaverApiCoreClient {
47
47
  async search(type, params) {
48
48
  return this.get(`${this.searchBaseUrl}/${type}`, params);
49
49
  }
50
- /**
51
- * 전문자료 검색 메서드
52
- */
53
- async searchAcademic(params) {
54
- return this.get(`${this.searchBaseUrl}/doc`, params);
55
- }
56
50
  /**
57
51
  * 지역 검색 메서드
58
52
  */
@@ -63,55 +57,55 @@ export class NaverSearchClient extends NaverApiCoreClient {
63
57
  * 검색어 트렌드 분석 메서드
64
58
  */
65
59
  async searchTrend(params) {
66
- return this.post(`${this.datalabBaseUrl}/search`, params);
60
+ return this.post(this.trendUrl, params);
67
61
  }
68
62
  /**
69
63
  * 쇼핑 카테고리 트렌드 분석 메서드
70
64
  */
71
65
  async datalabShoppingCategory(params) {
72
- return this.post(`${this.datalabBaseUrl}/shopping/categories`, params);
66
+ return this.post(`${this.shoppingBaseUrl}/categories`, params);
73
67
  }
74
68
  /**
75
69
  * 쇼핑 기기별 트렌드 분석 메서드
76
70
  */
77
71
  async datalabShoppingByDevice(params) {
78
- return this.post(`${this.datalabBaseUrl}/shopping/category/device`, params);
72
+ return this.post(`${this.shoppingBaseUrl}/category/device`, params);
79
73
  }
80
74
  /**
81
75
  * 쇼핑 성별 트렌드 분석 메서드
82
76
  */
83
77
  async datalabShoppingByGender(params) {
84
- return this.post(`${this.datalabBaseUrl}/shopping/category/gender`, params);
78
+ return this.post(`${this.shoppingBaseUrl}/category/gender`, params);
85
79
  }
86
80
  /**
87
81
  * 쇼핑 연령별 트렌드 분석 메서드
88
82
  */
89
83
  async datalabShoppingByAge(params) {
90
- return this.post(`${this.datalabBaseUrl}/shopping/category/age`, params);
84
+ return this.post(`${this.shoppingBaseUrl}/category/age`, params);
91
85
  }
92
86
  /**
93
87
  * 쇼핑 키워드 트렌드 분석 메서드
94
88
  */
95
89
  async datalabShoppingKeywords(params) {
96
- return this.post(`${this.datalabBaseUrl}/shopping/category/keywords`, params);
90
+ return this.post(`${this.shoppingBaseUrl}/category/keywords`, params);
97
91
  }
98
92
  /**
99
93
  * 쇼핑 키워드 기기별 트렌드 분석 메서드
100
94
  */
101
95
  async datalabShoppingKeywordByDevice(params) {
102
- return this.post(`${this.datalabBaseUrl}/shopping/category/keyword/device`, params);
96
+ return this.post(`${this.shoppingBaseUrl}/category/keyword/device`, params);
103
97
  }
104
98
  /**
105
99
  * 쇼핑 키워드 성별 트렌드 분석 메서드
106
100
  */
107
101
  async datalabShoppingKeywordByGender(params) {
108
- return this.post(`${this.datalabBaseUrl}/shopping/category/keyword/gender`, params);
102
+ return this.post(`${this.shoppingBaseUrl}/category/keyword/gender`, params);
109
103
  }
110
104
  /**
111
105
  * 쇼핑 키워드 연령별 트렌드 분석 메서드
112
106
  */
113
107
  async datalabShoppingKeywordByAge(params) {
114
- return this.post(`${this.datalabBaseUrl}/shopping/category/keyword/age`, params);
108
+ return this.post(`${this.shoppingBaseUrl}/category/keyword/age`, params);
115
109
  }
116
110
  }
117
111
  NaverSearchClient.instance = null;
@@ -0,0 +1,20 @@
1
+ import { NaverApiProvider } from "../clients/naver-api-endpoints.js";
2
+ export interface CredentialEnv {
3
+ NAVER_CLIENT_ID?: string;
4
+ NAVER_CLIENT_SECRET?: string;
5
+ NCP_APIGW_API_KEY_ID?: string;
6
+ NCP_APIGW_API_KEY?: string;
7
+ }
8
+ export interface ResolvedCredentials {
9
+ provider: NaverApiProvider;
10
+ clientId: string;
11
+ clientSecret: string;
12
+ /** 해석은 성공했지만 사용자에게 알릴 것이 있을 때만 채운다. 없으면 키 자체를 넣지 않는다. */
13
+ warning?: string;
14
+ }
15
+ /**
16
+ * 환경변수에서 자격증명을 읽어 사용할 provider를 결정한다.
17
+ * 키 값 자체로는 두 플랫폼을 구분할 수 없으므로, 어느 변수에 들어있는지로 판단한다.
18
+ * HUB 쌍이 legacy 쌍보다 우선한다.
19
+ */
20
+ export declare function resolveCredentials(env: CredentialEnv): ResolvedCredentials;
@@ -0,0 +1,53 @@
1
+ const SETUP_GUIDE = `
2
+ NAVER API HUB(권장): NCP_APIGW_API_KEY_ID, NCP_APIGW_API_KEY
3
+ 발급: NAVER Cloud Platform 콘솔 > NAVER API HUB
4
+ 네이버 개발자센터(2027-06-30 지원 종료): NAVER_CLIENT_ID, NAVER_CLIENT_SECRET
5
+
6
+ 두 자격증명은 서로 호환되지 않습니다. 발급받은 플랫폼에 맞는 환경변수에 넣어주세요.`;
7
+ /**
8
+ * 환경변수에서 자격증명을 읽어 사용할 provider를 결정한다.
9
+ * 키 값 자체로는 두 플랫폼을 구분할 수 없으므로, 어느 변수에 들어있는지로 판단한다.
10
+ * HUB 쌍이 legacy 쌍보다 우선한다.
11
+ */
12
+ export function resolveCredentials(env) {
13
+ const hubId = env.NCP_APIGW_API_KEY_ID?.trim();
14
+ const hubSecret = env.NCP_APIGW_API_KEY?.trim();
15
+ const legacyId = env.NAVER_CLIENT_ID?.trim();
16
+ const legacySecret = env.NAVER_CLIENT_SECRET?.trim();
17
+ if (hubId && hubSecret) {
18
+ return { provider: "hub", clientId: hubId, clientSecret: hubSecret };
19
+ }
20
+ if (hubId || hubSecret) {
21
+ const missing = hubId ? "NCP_APIGW_API_KEY" : "NCP_APIGW_API_KEY_ID";
22
+ // HUB 키가 반쪽이어도 개발자센터 키가 온전하면 서버를 죽이지 않는다.
23
+ // 이관 중에 HUB 키를 반만 넣어둔 기존 사용자를 막지 않되, 실수는 경고로 드러낸다.
24
+ if (legacyId && legacySecret) {
25
+ return {
26
+ provider: "legacy",
27
+ clientId: legacyId,
28
+ clientSecret: legacySecret,
29
+ warning: `NAVER API HUB 자격증명이 반쪽입니다. ${missing} 이(가) 설정되지 않아 개발자센터 키로 동작합니다.
30
+ HUB로 이관하려면 ${missing} 을(를) 마저 설정하고, 이관할 생각이 없다면 남은 HUB 환경변수를 지우세요.`,
31
+ };
32
+ }
33
+ throw new Error(`NAVER API HUB 자격증명이 불완전합니다.
34
+ NCP_APIGW_API_KEY_ID: ${hubId ? "provided" : "missing"}
35
+ NCP_APIGW_API_KEY: ${hubSecret ? "provided" : "missing"}
36
+ 두 값을 함께 설정해야 합니다.`);
37
+ }
38
+ if (legacyId && legacySecret) {
39
+ return {
40
+ provider: "legacy",
41
+ clientId: legacyId,
42
+ clientSecret: legacySecret,
43
+ };
44
+ }
45
+ if (legacyId || legacySecret) {
46
+ throw new Error(`네이버 개발자센터 자격증명이 불완전합니다.
47
+ NAVER_CLIENT_ID: ${legacyId ? "provided" : "missing"}
48
+ NAVER_CLIENT_SECRET: ${legacySecret ? "provided" : "missing"}
49
+ 두 값을 함께 설정해야 합니다.`);
50
+ }
51
+ throw new Error(`자격증명이 설정되지 않았습니다.
52
+ ${SETUP_GUIDE}`);
53
+ }
File without changes
File without changes
File without changes
File without changes
@@ -1,14 +1,6 @@
1
1
  import { NaverLocalSearchParams } from "../schemas/search.schemas.js";
2
2
  import { SearchArgs } from "../schemas/search.schemas.js";
3
3
  export declare const searchToolHandlers: Record<string, (args: any) => Promise<any>>;
4
- /**
5
- * 전문자료 검색 핸들러
6
- */
7
- export declare function handleAcademicSearch(params: SearchArgs): Promise<import("../types/search.types.js").NaverDocumentSearchResponse>;
8
- /**
9
- * 도서 검색 핸들러
10
- */
11
- export declare function handleBookSearch(params: SearchArgs): Promise<any>;
12
4
  /**
13
5
  * 지식백과 검색 핸들러
14
6
  */
@@ -33,10 +25,6 @@ export declare function handleNewsSearch(params: SearchArgs): Promise<any>;
33
25
  * 블로그 검색 핸들러
34
26
  */
35
27
  export declare function handleBlogSearch(params: SearchArgs): Promise<any>;
36
- /**
37
- * 쇼핑 검색 핸들러
38
- */
39
- export declare function handleShopSearch(params: SearchArgs): Promise<any>;
40
28
  /**
41
29
  * 카페글 검색 핸들러
42
30
  */
@@ -15,10 +15,6 @@ export const searchToolHandlers = {
15
15
  console.error("search_blog called with args:", JSON.stringify(args, null, 2));
16
16
  return handleBlogSearch(SearchArgsSchema.parse(args));
17
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
18
  search_image: (args) => {
23
19
  console.error("search_image called with args:", JSON.stringify(args, null, 2));
24
20
  return handleImageSearch(SearchArgsSchema.parse(args));
@@ -27,18 +23,10 @@ export const searchToolHandlers = {
27
23
  console.error("search_kin called with args:", JSON.stringify(args, null, 2));
28
24
  return handleKinSearch(SearchArgsSchema.parse(args));
29
25
  },
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
26
  search_encyc: (args) => {
35
27
  console.error("search_encyc called with args:", JSON.stringify(args, null, 2));
36
28
  return handleEncycSearch(SearchArgsSchema.parse(args));
37
29
  },
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
30
  search_local: (args) => {
43
31
  console.error("search_local called with args:", JSON.stringify(args, null, 2));
44
32
  return handleLocalSearch(args);
@@ -48,18 +36,6 @@ export const searchToolHandlers = {
48
36
  return handleCafeArticleSearch(SearchArgsSchema.parse(args));
49
37
  },
50
38
  };
51
- /**
52
- * 전문자료 검색 핸들러
53
- */
54
- export async function handleAcademicSearch(params) {
55
- return client.searchAcademic(params);
56
- }
57
- /**
58
- * 도서 검색 핸들러
59
- */
60
- export async function handleBookSearch(params) {
61
- return client.search("book", params);
62
- }
63
39
  /**
64
40
  * 지식백과 검색 핸들러
65
41
  */
@@ -96,12 +72,6 @@ export async function handleNewsSearch(params) {
96
72
  export async function handleBlogSearch(params) {
97
73
  return client.search("blog", params);
98
74
  }
99
- /**
100
- * 쇼핑 검색 핸들러
101
- */
102
- export async function handleShopSearch(params) {
103
- return client.search("shop", params);
104
- }
105
75
  /**
106
76
  * 카페글 검색 핸들러
107
77
  */
@@ -2,14 +2,20 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { z } from "zod";
4
4
  export declare const configSchema: z.ZodObject<{
5
- NAVER_CLIENT_ID: z.ZodString;
6
- NAVER_CLIENT_SECRET: z.ZodString;
5
+ NAVER_CLIENT_ID: z.ZodOptional<z.ZodString>;
6
+ NAVER_CLIENT_SECRET: z.ZodOptional<z.ZodString>;
7
+ NCP_APIGW_API_KEY_ID: z.ZodOptional<z.ZodString>;
8
+ NCP_APIGW_API_KEY: z.ZodOptional<z.ZodString>;
7
9
  }, "strip", z.ZodTypeAny, {
8
- NAVER_CLIENT_ID: string;
9
- NAVER_CLIENT_SECRET: string;
10
+ NCP_APIGW_API_KEY?: string | undefined;
11
+ NCP_APIGW_API_KEY_ID?: string | undefined;
12
+ NAVER_CLIENT_ID?: string | undefined;
13
+ NAVER_CLIENT_SECRET?: string | undefined;
10
14
  }, {
11
- NAVER_CLIENT_ID: string;
12
- NAVER_CLIENT_SECRET: string;
15
+ NCP_APIGW_API_KEY?: string | undefined;
16
+ NCP_APIGW_API_KEY_ID?: string | undefined;
17
+ NAVER_CLIENT_ID?: string | undefined;
18
+ NAVER_CLIENT_SECRET?: string | undefined;
13
19
  }>;
14
20
  /**
15
21
  * 서버 인스턴스와 관련 리소스 정리 (메모리 누수 방지)
package/dist/src/index.js CHANGED
@@ -9,10 +9,22 @@ import { SearchArgsSchema, NaverLocalSearchParamsSchema, } from "./schemas/searc
9
9
  import { DatalabSearchSchema, DatalabShoppingSchema, DatalabShoppingDeviceSchema, DatalabShoppingGenderSchema, DatalabShoppingAgeSchema, DatalabShoppingKeywordsSchema, DatalabShoppingKeywordDeviceSchema, DatalabShoppingKeywordGenderSchema, DatalabShoppingKeywordAgeSchema, } from "./schemas/datalab.schemas.js";
10
10
  import { FindCategorySchema } from "./schemas/category.schemas.js";
11
11
  import { findCategoryHandler, clearCategoriesCache } from "./handlers/category.handlers.js";
12
+ import { resolveCredentials } from "./config/credentials.js";
12
13
  // Configuration schema for programmatic server creation and stdio startup
13
14
  export const configSchema = z.object({
14
- NAVER_CLIENT_ID: z.string().describe("Naver API Client ID"),
15
- NAVER_CLIENT_SECRET: z.string().describe("Naver API Client Secret"),
15
+ NAVER_CLIENT_ID: z.string().optional().describe("네이버 개발자센터 Client ID"),
16
+ NAVER_CLIENT_SECRET: z
17
+ .string()
18
+ .optional()
19
+ .describe("네이버 개발자센터 Client Secret"),
20
+ NCP_APIGW_API_KEY_ID: z
21
+ .string()
22
+ .optional()
23
+ .describe("NAVER API HUB Client ID"),
24
+ NCP_APIGW_API_KEY: z
25
+ .string()
26
+ .optional()
27
+ .describe("NAVER API HUB Client Secret"),
16
28
  });
17
29
  // Global server instance to prevent memory leaks
18
30
  let globalServerInstance = null;
@@ -38,7 +50,9 @@ function isConfigChanged(newConfig) {
38
50
  if (!currentConfig)
39
51
  return true;
40
52
  return (currentConfig.NAVER_CLIENT_ID !== newConfig.NAVER_CLIENT_ID ||
41
- currentConfig.NAVER_CLIENT_SECRET !== newConfig.NAVER_CLIENT_SECRET);
53
+ currentConfig.NAVER_CLIENT_SECRET !== newConfig.NAVER_CLIENT_SECRET ||
54
+ currentConfig.NCP_APIGW_API_KEY_ID !== newConfig.NCP_APIGW_API_KEY_ID ||
55
+ currentConfig.NCP_APIGW_API_KEY !== newConfig.NCP_APIGW_API_KEY);
42
56
  }
43
57
  export function createNaverSearchServer({ config, }) {
44
58
  // 설정이 변경된 경우 기존 인스턴스 정리
@@ -53,14 +67,18 @@ export function createNaverSearchServer({ config, }) {
53
67
  // Create a new MCP server only once
54
68
  const server = new McpServer({
55
69
  name: "naver-search",
56
- version: "1.0.44",
70
+ version: "1.0.49",
57
71
  });
58
72
  // Initialize Naver client with config
73
+ const credentials = resolveCredentials(config);
74
+ console.error(`Using ${credentials.provider === "hub"
75
+ ? "NAVER API HUB"
76
+ : "네이버 개발자센터 (2027-06-30 지원 종료 예정)"}`);
77
+ if (credentials.warning) {
78
+ console.error(`[경고] ${credentials.warning}`);
79
+ }
59
80
  const client = NaverSearchClient.getInstance();
60
- client.initialize({
61
- clientId: config.NAVER_CLIENT_ID,
62
- clientSecret: config.NAVER_CLIENT_SECRET,
63
- });
81
+ client.initialize(credentials);
64
82
  server.registerTool("search_webkr", {
65
83
  description: "🌐 Search Korean web documents and general content. Comprehensive search across Korean websites and online content. Find articles, information, and documents from various Korean sources. For recent content or 'today's results', use get_current_korean_time first. (네이버 웹문서 검색 - 한국 웹사이트 종합 검색, 최근 콘텐츠나 오늘 결과를 찾을 때는 먼저 get_current_korean_time으로 현재 시간을 확인하세요)",
66
84
  inputSchema: SearchArgsSchema.shape,
@@ -88,15 +106,6 @@ export function createNaverSearchServer({ config, }) {
88
106
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
89
107
  };
90
108
  });
91
- server.registerTool("search_shop", {
92
- description: "🛒 Search Naver Shopping for products, prices, and shopping deals. Compare prices across vendors, find product specifications, and discover shopping trends in Korea. For current deals or today's specials, use get_current_korean_time first. (네이버 쇼핑 검색 - 상품 정보와 가격 비교, 현재 할인이나 오늘 특가를 찾을 때는 먼저 get_current_korean_time으로 현재 시간을 확인하세요)",
93
- inputSchema: SearchArgsSchema.shape,
94
- }, async (args) => {
95
- const result = await searchToolHandlers.search_shop(args);
96
- return {
97
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
98
- };
99
- });
100
109
  server.registerTool("search_image", {
101
110
  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. For recent images or current visual content, use get_current_korean_time first. (네이버 이미지 검색 - 시각적 컨텐츠 발견, 최근 이미지나 현재 시각 자료를 찾을 때는 먼저 get_current_korean_time으로 현재 시간을 확인하세요)",
102
111
  inputSchema: SearchArgsSchema.shape,
@@ -115,15 +124,6 @@ export function createNaverSearchServer({ config, }) {
115
124
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
116
125
  };
117
126
  });
118
- server.registerTool("search_book", {
119
- description: "📚 Search for books, publications, and literary content. Find book reviews, author information, publication details, and reading recommendations in Korean literature and translated works. For new releases or current bestsellers, use get_current_korean_time first. (네이버 책 검색 - 도서 정보와 서평, 신간도서나 현재 베스트셀러를 찾을 때는 먼저 get_current_korean_time으로 현재 시간을 확인하세요)",
120
- inputSchema: SearchArgsSchema.shape,
121
- }, async (args) => {
122
- const result = await searchToolHandlers.search_book(args);
123
- return {
124
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
125
- };
126
- });
127
127
  server.registerTool("search_encyc", {
128
128
  description: "📖 Search Naver Encyclopedia for authoritative knowledge and definitions. Best for academic research, getting reliable information, and understanding Korean concepts and terminology. For current definitions or recent updates, use get_current_korean_time for context. (네이버 지식백과 검색 - 신뢰할 수 있는 정보와 정의, 현재 정의나 최근 업데이트를 찾을 때는 get_current_korean_time으로 상황을 파악하세요)",
129
129
  inputSchema: SearchArgsSchema.shape,
@@ -133,15 +133,6 @@ export function createNaverSearchServer({ config, }) {
133
133
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
134
134
  };
135
135
  });
136
- server.registerTool("search_academic", {
137
- description: "🎓 Search academic papers, research documents, and scholarly content. Access Korean academic resources, research papers, theses, and professional publications. For recent publications or current research, use get_current_korean_time first. (네이버 전문자료 검색 - 학술 논문과 전문 자료, 최근 발표나 현재 연구를 찾을 때는 먼저 get_current_korean_time으로 현재 시간을 확인하세요)",
138
- inputSchema: SearchArgsSchema.shape,
139
- }, async (args) => {
140
- const result = await searchToolHandlers.search_academic(args);
141
- return {
142
- content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
143
- };
144
- });
145
136
  server.registerTool("search_local", {
146
137
  description: "📍 Search for local businesses, restaurants, and places in Korea. Find location information, reviews, contact details, and business hours for Korean establishments. For current business hours or today's availability, use get_current_korean_time first. (네이버 지역 검색 - 지역 업체와 장소 정보, 현재 영업시간이나 오늘 이용 가능 여부를 확인할 때는 먼저 get_current_korean_time으로 현재 시간을 확인하세요)",
147
138
  inputSchema: NaverLocalSearchParamsSchema.shape,
@@ -314,29 +305,29 @@ function registerShutdownHandlers({ server, transport, }) {
314
305
  async function main() {
315
306
  try {
316
307
  console.error("Starting Naver Search MCP Server...");
317
- // Get config from environment variables - check for empty strings too
318
- const clientId = process.env.NAVER_CLIENT_ID?.trim();
319
- const clientSecret = process.env.NAVER_CLIENT_SECRET?.trim();
308
+ // Get config from environment variables
309
+ const config = {
310
+ NAVER_CLIENT_ID: process.env.NAVER_CLIENT_ID,
311
+ NAVER_CLIENT_SECRET: process.env.NAVER_CLIENT_SECRET,
312
+ NCP_APIGW_API_KEY_ID: process.env.NCP_APIGW_API_KEY_ID,
313
+ NCP_APIGW_API_KEY: process.env.NCP_APIGW_API_KEY,
314
+ };
320
315
  console.error("Environment variables:", {
321
- NAVER_CLIENT_ID: process.env.NAVER_CLIENT_ID
322
- ? `[${process.env.NAVER_CLIENT_ID.length} chars]`
316
+ NAVER_CLIENT_ID: config.NAVER_CLIENT_ID
317
+ ? `[${config.NAVER_CLIENT_ID.length} chars]`
318
+ : "undefined",
319
+ NAVER_CLIENT_SECRET: config.NAVER_CLIENT_SECRET
320
+ ? `[${config.NAVER_CLIENT_SECRET.length} chars]`
321
+ : "undefined",
322
+ NCP_APIGW_API_KEY_ID: config.NCP_APIGW_API_KEY_ID
323
+ ? `[${config.NCP_APIGW_API_KEY_ID.length} chars]`
323
324
  : "undefined",
324
- NAVER_CLIENT_SECRET: process.env.NAVER_CLIENT_SECRET
325
- ? `[${process.env.NAVER_CLIENT_SECRET.length} chars]`
325
+ NCP_APIGW_API_KEY: config.NCP_APIGW_API_KEY
326
+ ? `[${config.NCP_APIGW_API_KEY.length} chars]`
326
327
  : "undefined",
327
328
  });
328
- if (!clientId || !clientSecret) {
329
- throw new Error(`Missing required environment variables:
330
- NAVER_CLIENT_ID: ${clientId ? "provided" : "missing"}
331
- NAVER_CLIENT_SECRET: ${clientSecret ? "provided" : "missing"}
332
-
333
- Please set these environment variables before running the server.`);
334
- }
335
- const config = {
336
- NAVER_CLIENT_ID: clientId,
337
- NAVER_CLIENT_SECRET: clientSecret,
338
- };
339
- console.error("Config loaded successfully");
329
+ // 자격증명 유효성은 여기서 먼저 확인한다 (실패 시 명확한 안내와 함께 종료)
330
+ resolveCredentials(config);
340
331
  // Validate config
341
332
  const validatedConfig = configSchema.parse(config);
342
333
  console.error("Config validated successfully");
File without changes
File without changes
File without changes
File without changes
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- export declare const NaverSearchTypeSchema: z.ZodEnum<["news", "encyc", "blog", "shop", "webkr", "image", "doc", "kin", "book", "cafearticle", "local"]>;
2
+ export declare const NaverSearchTypeSchema: z.ZodEnum<["news", "encyc", "blog", "webkr", "image", "kin", "cafearticle", "local"]>;
3
3
  export declare const NaverSearchParamsSchema: z.ZodObject<{
4
4
  query: z.ZodString;
5
5
  display: z.ZodOptional<z.ZodNumber>;
@@ -33,28 +33,18 @@ export declare const SearchArgsSchema: z.ZodObject<{
33
33
  sort?: "sim" | "date" | undefined;
34
34
  }>;
35
35
  export declare const NaverSearchConfigSchema: z.ZodObject<{
36
+ provider: z.ZodEnum<["legacy", "hub"]>;
36
37
  clientId: z.ZodString;
37
38
  clientSecret: z.ZodString;
38
39
  }, "strip", z.ZodTypeAny, {
40
+ provider: "legacy" | "hub";
39
41
  clientId: string;
40
42
  clientSecret: string;
41
43
  }, {
44
+ provider: "legacy" | "hub";
42
45
  clientId: string;
43
46
  clientSecret: string;
44
47
  }>;
45
- export declare const NaverDocumentSearchParamsSchema: z.ZodObject<{
46
- query: z.ZodString;
47
- display: z.ZodOptional<z.ZodNumber>;
48
- start: z.ZodOptional<z.ZodNumber>;
49
- }, "strip", z.ZodTypeAny, {
50
- query: string;
51
- display?: number | undefined;
52
- start?: number | undefined;
53
- }, {
54
- query: string;
55
- display?: number | undefined;
56
- start?: number | undefined;
57
- }>;
58
48
  export declare const NaverLocalSearchParamsSchema: z.ZodObject<{
59
49
  query: z.ZodString;
60
50
  } & {
@@ -77,4 +67,3 @@ export type NaverSearchParams = z.infer<typeof NaverSearchParamsSchema>;
77
67
  export type SearchArgs = z.infer<typeof SearchArgsSchema>;
78
68
  export type NaverSearchType = z.infer<typeof NaverSearchTypeSchema>;
79
69
  export type NaverSearchConfig = z.infer<typeof NaverSearchConfigSchema>;
80
- export type NaverDocumentSearchParams = z.infer<typeof NaverDocumentSearchParamsSchema>;
@@ -4,12 +4,9 @@ export const NaverSearchTypeSchema = z.enum([
4
4
  "news",
5
5
  "encyc",
6
6
  "blog",
7
- "shop",
8
7
  "webkr",
9
8
  "image",
10
- "doc",
11
9
  "kin",
12
- "book",
13
10
  "cafearticle",
14
11
  "local",
15
12
  ]);
@@ -40,16 +37,11 @@ export const SearchArgsSchema = z.object({
40
37
  });
41
38
  // 네이버 API 인증 정보
42
39
  export const NaverSearchConfigSchema = z.object({
43
- clientId: z.string().describe("네이버 개발자센터에서 발급받은 Client ID"),
44
- clientSecret: z
45
- .string()
46
- .describe("네이버 개발자센터에서 발급받은 Client Secret"),
47
- });
48
- // 전문자료(논문 등) 검색 파라미터
49
- export const NaverDocumentSearchParamsSchema = z.object({
50
- query: z.string().describe("검색어"),
51
- display: z.number().optional().describe("한 번에 가져올 결과 수 (최대 100)"),
52
- start: z.number().optional().describe("검색 시작 위치 (최대 1000)"),
40
+ provider: z
41
+ .enum(["legacy", "hub"])
42
+ .describe("자격증명 발급 플랫폼 (legacy: 개발자센터, hub: NAVER API HUB)"),
43
+ clientId: z.string().describe("발급받은 Client ID"),
44
+ clientSecret: z.string().describe("발급받은 Client Secret"),
53
45
  });
54
46
  // 지역 검색 파라미터
55
47
  export const NaverLocalSearchParamsSchema = SearchArgsSchema.extend({
File without changes
File without changes
@@ -11,14 +11,6 @@ export interface NaverSearchItem {
11
11
  link: string;
12
12
  description: string;
13
13
  }
14
- export interface NaverDocumentSearchResponse extends NaverSearchResponse {
15
- items: NaverDocumentItem[];
16
- }
17
- export interface NaverDocumentItem extends NaverSearchItem {
18
- title: string;
19
- link: string;
20
- description: string;
21
- }
22
14
  export interface NaverEncyclopediaSearchResponse extends NaverSearchResponse {
23
15
  items: NaverEncyclopediaItem[];
24
16
  }
File without changes
File without changes
File without changes
File without changes
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isnow890/naver-search-mcp",
3
- "version": "1.0.48",
3
+ "version": "1.0.49",
4
4
  "description": "Naver Search MCP Server",
5
5
  "main": "dist/src/index.js",
6
6
  "module": "./src/index.ts",
@@ -13,7 +13,8 @@
13
13
  "build": "tsc && shx cp -r data dist/",
14
14
  "prepare": "npm run build",
15
15
  "watch": "tsc --watch",
16
- "start": "node dist/src/index.js"
16
+ "start": "node dist/src/index.js",
17
+ "test": "npm run build && node --test"
17
18
  },
18
19
  "engines": {
19
20
  "node": ">=18"