@modootoday/datalab-extension-mcp 1.8.13 → 1.8.16

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.en.md CHANGED
@@ -22,33 +22,36 @@ Set this up once per computer.
22
22
 
23
23
  > A work computer may block installs by policy. Check with your IT team first to avoid wasting time.
24
24
 
25
- ## Three steps
25
+ ## Four steps
26
26
 
27
- | Step | What | Where |
28
- | ----------------- | ------------------------------------------------ | ------------------------ |
29
- | **1. Turn it on** | Flip a switch and note the host management token | DataLab Tools side panel |
30
- | **2. Install** | Run one command and paste the token | Terminal |
31
- | **3. Verify** | Ask a test question in your AI app | Your AI app |
27
+ | Step | What | Where |
28
+ | ----------------- | -------------------------------------------- | ------------------ |
29
+ | **1. Turn it on** | Flip a switch and copy the **install key** | Extension settings |
30
+ | **2. Install** | Run one command and paste the key | Terminal |
31
+ | **3. Connect** | Register **this browser** on the same screen | Extension settings |
32
+ | **4. Verify** | Ask a test question in your AI app | Your AI app |
33
+
34
+ Steps 1 and 3 are the same screen, which tells you how far along you are — leave it open and follow it down.
32
35
 
33
36
  Feel free to read [Safety](#safety) first if you'd like reassurance before starting.
34
37
 
35
38
  ## Step 1 — Turn it on
36
39
 
37
40
  1. Click the DataLab Tools icon at Chrome's top-right to open the **side panel**. (If you don't see it, find it under the puzzle-piece icon.)
38
- 2. In **Settings** on the left, turn on the **"다른 AI 앱에 연결 (MCP)"** (Connect to another AI app) switch.
39
- 3. A consent screen appears. Read it and agree.
41
+ 2. Press **설정** (Settings) at the bottom of the panel to open the **extension settings** screen.
42
+ 3. Turn on the **"다른 AI 앱에 연결 (MCP)"** (Connect to another AI app) switch. A consent screen appears — read it and agree.
40
43
 
41
44
  > "연결된 AI 앱이 요청하면 네이버 데이터가 해당 앱과 그 AI 제공사 서버로 전달돼요."
42
45
  >
43
46
  > _(When a connected AI app asks, your Naver data is sent to that app and its AI provider's servers.)_
44
47
 
45
- 4. When the **"MCP 연결" (MCP connection) card** shows in the panel, you're set. You'll use the **MCP host management token** on it in the next step — press **[복사]** (copy) to put it on the clipboard now. ([보기] reveals it if you want to see it.)
48
+ 4. A **numbered 1 · 2 · 3 walkthrough** unfolds below. In box **1 [설치 인증키]** (install key), press **[복사]** (copy) to put the value on the clipboard. ([보기] reveals it if you want to see it.)
46
49
 
47
- > "이 코드는 이 컴퓨터 전용 비밀번호예요. 다른 사람에게 보내지 마세요."
50
+ > "이 값은 이 컴퓨터 전용 비밀번호예요. 다른 사람에게 보내지 마세요."
48
51
  >
49
- > _(This code is a password for this computer only. Don't send it to anyone.)_
52
+ > _(This value is a password for this computer only. Don't send it to anyone.)_
50
53
 
51
- Avoid sharing a screenshot or the copied setup. This token belongs only in AI host configuration and workbench management. Browser registration uses separate one-time codes which are never shown again by the panel.
54
+ Avoid sharing a screenshot or the copied setup. The install key belongs only in AI host configuration and workbench management. It is **not** the registration code from step 3 — browser registration uses separate codes, each shown once and never again.
52
55
 
53
56
  ## Step 2 — Install
54
57
 
@@ -58,26 +61,28 @@ One command finds **every** AI app on your computer and connects them automatica
58
61
  2. **Paste this command and press Enter.** The saved command names an exact bootstrap version. At startup and while idle, the connector checks the canonical release and promotes itself to the current secure, compatible version. Diagnostic environments can opt out with `--no-self-update`.
59
62
 
60
63
  ```
61
- npx -y @modootoday/datalab-extension-mcp@1.8.13 install
64
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 install
62
65
  ```
63
66
 
64
67
  Lines of English scrolling by is normal. This only downloads and runs the connector — it touches nothing else.
65
68
 
66
- 3. **Paste the management token.** The helper asks:
69
+ The helper runs in four chapters — `[1/4]` check the install key, `[2/4]` find AI programs, `[3/4]` connect, `[4/4] 결과` (results).
70
+
71
+ 3. **Paste the install key.** The helper asks:
67
72
 
68
- > "복사한 관리 토큰을 여기에 붙여넣어 주세요:"
73
+ > "복사한 설치 인증키를 붙여넣어 주세요:"
69
74
  >
70
- > _(Paste the management token you copied here:)_
75
+ > _(Paste the install key you copied here:)_
71
76
 
72
- Paste the token from the Step 1 card and press Enter.
77
+ Paste the value from step 1 and press Enter. A malformed paste is asked for once more.
73
78
 
74
79
  4. **Confirm.** It asks exactly one question:
75
80
 
76
- > "위 N개 프로그램에 연결할까요?" (Y/n)
81
+ > "위 N개 프로그램에 연결할까요?" (엔터 = 예 / n = 아니오)
77
82
  >
78
- > _(Connect to the N programs above?)_
83
+ > _(Connect to the N programs above? Enter = yes / n = no)_
79
84
 
80
- Press `Y`. If it found 0 apps, install an AI app first and run the same command again.
85
+ Press Enter. Per-app results follow, then a count: `N개 프로그램에 연결했어요.` If it found 0 apps, install an AI app first and run the same command again.
81
86
 
82
87
  5. When you see this, you're done:
83
88
 
@@ -105,7 +110,7 @@ If the command errors with `'npx' is not recognized`, Node.js isn't installed. T
105
110
  curl -fsSL https://app.datalab.tools/install/mcp.sh | sh
106
111
  ```
107
112
 
108
- When it asks for the management token, paste the one from the Step 1 card. If you have no AI app yet, you can also pick one to install right there (choosing is optional).
113
+ When it asks for the install key, paste the one from step 1. If you have no AI app yet, you can also pick one to install right there (choosing is optional).
109
114
 
110
115
  > Want to see what it does before running it? Open that URL in a browser — it serves the script itself, in plain text.
111
116
 
@@ -114,7 +119,17 @@ When it asks for the management token, paste the one from the Step 1 card. If yo
114
119
  Apps it **connects automatically**: Claude Desktop, Claude Code, ChatGPT desktop, Codex CLI, Gemini CLI, Cursor, Windsurf, Amazon Q Developer, JetBrains Junie, Kiro.
115
120
  Apps it **only prints instructions for** (follow the on-screen guide): VS Code, Zed, Cline, Roo Code, LM Studio, Warp.
116
121
 
117
- ## Step 3 — Verify
122
+ ## Step 3 — Connect
123
+
124
+ Once the install finishes, **box 3 on the extension settings screen** changes on its own. No reload needed.
125
+
126
+ - If a **[이 브라우저 연결하기]** (connect this browser) button is there, press it and you're done — the screen mints the registration code and redeems it for you.
127
+ - If it already reads **"등록됨"** (registered), there is nothing to do.
128
+ - If you **already installed from another browser on this computer**, you get a code box instead of the button. Paste the code made in that browser (see [Putting another Chrome on the workbench](#putting-another-chrome-on-the-workbench)).
129
+
130
+ Registration is once per browser. The connection itself lives only while the side panel is open.
131
+
132
+ ## Step 4 — Verify
118
133
 
119
134
  - If the card at the top of the side panel is **green** and reads "연결됨" (connected), you're set. If it isn't green, go to [Troubleshooting](#troubleshooting).
120
135
  - Ask something like "How did my blog do last week?" in your AI app; if real numbers come back, everything's done.
@@ -130,10 +145,10 @@ Apps it **only prints instructions for** (follow the on-screen guide): VS Code,
130
145
  Installing also places the bundled **writing skills**. An AI app reads the name and description of every installed skill on every start, so the more are installed the less room the conversation has. The default therefore places only the ones that work right away.
131
146
 
132
147
  ```
133
- npx -y @modootoday/datalab-extension-mcp@1.8.13 skills list
134
- npx -y @modootoday/datalab-extension-mcp@1.8.13 skills status
135
- npx -y @modootoday/datalab-extension-mcp@1.8.13 skills attach <name>
136
- npx -y @modootoday/datalab-extension-mcp@1.8.13 skills detach <name>
148
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 skills list
149
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 skills status
150
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 skills attach <name>
151
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 skills detach <name>
137
152
  ```
138
153
 
139
154
  - `list` shows everything in this version, name and description.
@@ -149,21 +164,22 @@ If another tool installed a skill of the same name, it is left alone and said so
149
164
 
150
165
  The **workbench** is this computer, where the connector runs. If you use more than one Chrome profile, each gets a registration code of its own and joins the same workbench. This is one computer only — the connector listens on 127.0.0.1, so no other machine can reach it, and a code is never something to send to another person.
151
166
 
152
- Make the code from the panel with [등록 코드 만들기], or from the terminal.
167
+ Make the code in extension settings → **다른 AI 앱에 연결 (MCP)** → expand **[다른 브라우저 추가 (선택)]** (add another browser, optional) below box 3 → [등록 코드 만들기]. Or from the terminal.
153
168
 
154
169
  ```
155
- npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers add "Work Chrome"
156
- npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers list
157
- npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers revoke <handle>
158
- npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers repair
170
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 browsers add "Work Chrome"
171
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 browsers list
172
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 browsers revoke <handle>
173
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 browsers repair
159
174
  ```
160
175
 
161
- `add` **asks you to paste the management token** — the same value you used to
162
- install, copied from the panel. It then prints a code **once**. The connector
163
- keeps only a fingerprint of it, so it cannot show it to you again.
176
+ `add` **asks you to paste the install key** — the same value you used to
177
+ install, copied from box 1 in settings. It then prints a code **once**. The
178
+ connector keeps only a fingerprint of it, so it cannot show it to you again.
164
179
 
165
- Paste that code into the other browser's side panel, under **이 크롬을 워크벤치에 올리기**
166
- (join an existing connection). That browser does not make a code of its own.
180
+ Open the other browser → extension settings → **다른 AI 앱에 연결 (MCP)** → paste
181
+ that code into **3. 이 브라우저 등록** (register this browser). That browser does
182
+ not make a code of its own.
167
183
 
168
184
  - `list` separates `waiting` (a code exists, nobody has used it) from
169
185
  `connected`.
@@ -208,7 +224,7 @@ The connector is running with the code its AI app handed it at startup. **[다
208
224
  2. Paste it into the AI app's settings
209
225
  3. **Fully quit that AI app and start it again** — not just close the window (Windows: tray icon → quit; Mac: `⌘+Q`; Linux: Quit from the app menu)
210
226
 
211
- Step 3 brings the connector back with the management token stored in the AI app. Browser registration codes are not changed by this procedure.
227
+ Item 3 is what brings the connector back with the install key stored in the AI app. Browser registration codes are not changed by this procedure.
212
228
 
213
229
  ### "연결 통로(포트)를 다른 프로그램이 쓰고 있어요."
214
230
 
@@ -255,7 +271,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
255
271
  - **When the connection disappears** — run the very command you used at the start once more; it safely re-registers. (Some AI apps clean up their settings file and drop the connection.)
256
272
 
257
273
  ```
258
- npx -y @modootoday/datalab-extension-mcp@1.8.13 install
274
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 install
259
275
  ```
260
276
 
261
277
  - **`'npx' is not recognized...`** — Node.js isn't installed. The "No Node.js?" one-liner in Step 2 is the simplest fix.
@@ -285,7 +301,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
285
301
  1. Run this in a terminal (it removes the entries registered in your AI apps).
286
302
 
287
303
  ```
288
- npx -y @modootoday/datalab-extension-mcp@1.8.13 uninstall
304
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 uninstall
289
305
  ```
290
306
 
291
307
  2. Turn **MCP 연결** off in the extension settings. The panel is the bridge, so with it off a leftover config line can look nothing up.
@@ -301,7 +317,7 @@ Deleting the extension also renders any leftover connector inert. A leftover lin
301
317
  - **Does the ChatGPT website work?** No. Only the installed desktop app connects.
302
318
  - **Do I keep it always on?** No. It connects only while the panel is open.
303
319
  - **What data does it read?** Blog stats, keywords, ads — what DataLab Tools shows. Scoped to the Naver account you're logged into in Chrome.
304
- - **Other computers?** Yes, but set it up once per computer; the management token is per-computer too.
320
+ - **Other computers?** Yes, but set it up once per computer; the install key is per-computer too.
305
321
  </details>
306
322
 
307
323
  ## Developer appendix
package/README.md CHANGED
@@ -20,29 +20,32 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
20
20
 
21
21
  > 회사 컴퓨터는 보안 정책 때문에 설치가 막혀 있을 수 있어요. 시작 전에 회사 IT 담당자와 먼저 상의하면 시간을 아낄 수 있어요.
22
22
 
23
- ## 세 단계예요
23
+ ## 네 단계예요
24
24
 
25
- | 단계 | 하는 일 | 어디서 |
26
- | ----------- | ------------------------------------------------ | ------------------------ |
27
- | **1. 켜기** | 확장 패널에서 스위치를 켜고 관리 토큰을 확인해요 | 데이터랩툴즈 사이드 패널 |
28
- | **2. 설치** | 명령 한 줄을 실행하고 토큰을 붙여넣어요 | 터미널 |
29
- | **3. 확인** | AI 앱에서 시험 질문을 해 봐요 | AI 앱 |
25
+ | 단계 | 하는 일 | 어디서 |
26
+ | ----------- | ----------------------------------------- | --------- |
27
+ | **1. 켜기** | 스위치를 켜고 **설치 인증키**를 복사해요 | 확장 설정 |
28
+ | **2. 설치** | 명령 한 줄을 실행하고 인증키를 붙여넣어요 | 터미널 |
29
+ | **3. 연결** | 설정 화면에서 **이 브라우저**를 등록해요 | 확장 설정 |
30
+ | **4. 확인** | AI 앱에서 시험 질문을 해 봐요 | AI 앱 |
31
+
32
+ 1·3단계는 같은 화면에서 이어집니다. 설정 화면이 지금 어디까지 됐는지 알려 주니, 화면을 열어 둔 채로 따라오시면 돼요.
30
33
 
31
34
  시작 전에 안심하고 싶다면 아래 [안전](#안전)을 먼저 읽으셔도 좋아요.
32
35
 
33
36
  ## 1단계 — 켜기
34
37
 
35
38
  1. Chrome 오른쪽 위의 데이터랩툴즈 아이콘을 눌러 **사이드 패널**을 열어요. (안 보이면 퍼즐 조각 아이콘에서 찾을 수 있어요.)
36
- 2. 왼쪽 **설정**에서 **"다른 AI 앱에 연결 (MCP)"** 스위치를 켜요.
37
- 3. 동의 화면이 나오면 내용을 읽고 동의해요.
39
+ 2. 패널 아래쪽 **설정**을 눌러 **확장 설정** 화면을 열어요.
40
+ 3. **"다른 AI 앱에 연결 (MCP)"** 스위치를 켜요. 동의 화면이 나오면 내용을 읽고 동의해요.
38
41
 
39
42
  > "연결된 AI 앱이 요청하면 네이버 데이터가 해당 앱과 그 AI 제공사 서버로 전달돼요."
40
43
 
41
- 4. 패널에 **"MCP 연결" 카드**가 보이면 성공이에요. 다음 단계에서 여기 있는 **MCP 호스트 관리 토큰**을 씁니다. **[복사]** 버튼을 눌러 복사해 두세요(눈으로 확인하고 싶으면 [보기]).
44
+ 4. 아래에 **1 · 2 · 3 번호가 붙은 안내**가 펼쳐져요. **1번 [설치 인증키]** 칸의 **[복사]** 버튼을 누르세요(눈으로 확인하고 싶으면 [보기]).
42
45
 
43
- > "이 코드는 이 컴퓨터 전용 비밀번호예요. 다른 사람에게 보내지 마세요."
46
+ > "이 값은 이 컴퓨터 전용 비밀번호예요. 다른 사람에게 보내지 마세요."
44
47
 
45
- 캡처 공유·채팅 전송은 피해 주세요. 이 토큰은 AI 앱 설정과 워크벤치 관리에만 사용하며, 브라우저 등록 칸에는 붙여넣지 마세요. 브라우저마다 별도 등록 코드가 발급되고 그 코드는 패널에 다시 표시되지 않습니다.
48
+ 캡처 공유·채팅 전송은 피해 주세요. 인증키는 AI 앱 설정과 워크벤치 관리에만 씁니다. **3번의 등록 코드와는 다른 값**이라 등록 코드 칸에 붙여넣으면 안 돼요 — 브라우저마다 별도 등록 코드가 발급되고, 그 코드는 만든 직후 한 번만 보여요.
46
49
 
47
50
  ## 2단계 — 설치
48
51
 
@@ -52,22 +55,24 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
52
55
  2. **아래 명령을 붙여넣고 Enter를 눌러요.** 저장되는 명령은 정확한 시작 버전을 적습니다. 커넥터는 시작할 때와 유휴 상태에서 정본 릴리스를 확인해 현재의 안전하고 호환되는 버전으로 승격합니다. 진단 환경에서는 `--no-self-update`로 승격을 끌 수 있어요.
53
56
 
54
57
  ```
55
- npx -y @modootoday/datalab-extension-mcp@1.8.13 install
58
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 install
56
59
  ```
57
60
 
58
61
  영어 글자가 여러 줄 올라오는 건 정상이에요. 이 명령은 커넥터를 내려받아 실행할 뿐, 다른 건 건드리지 않아요.
59
62
 
60
- 3. **관리 토큰을 붙여넣어요.** 도우미가 이렇게 물어봐요.
63
+ 도우미는 네 대목으로 진행돼요 — `[1/4] 설치 인증키 확인`, `[2/4] 연결할 AI 프로그램 찾기`, `[3/4] 연결하는 중`, `[4/4] 결과`.
64
+
65
+ 3. **설치 인증키를 붙여넣어요.** 도우미가 이렇게 물어봐요.
61
66
 
62
- > "복사한 관리 토큰을 여기에 붙여넣어 주세요:"
67
+ > "복사한 설치 인증키를 붙여넣어 주세요:"
63
68
 
64
- 1단계 카드에서 복사한 토큰을 붙여넣고 Enter를 눌러요.
69
+ 1단계에서 복사한 값을 붙여넣고 Enter를 눌러요. 형식이 어긋나면 한 번 더 물어봐요.
65
70
 
66
71
  4. **연결할지 확인해요.** 딱 하나만 물어봐요.
67
72
 
68
- > "위 N개 프로그램에 연결할까요?" (Y/n)
73
+ > "위 N개 프로그램에 연결할까요?" (엔터 = 예 / n = 아니오)
69
74
 
70
- `Y`를 누르면 앱별 결과가 나와요. 찾은 앱이 0개면 AI 앱을 먼저 설치한 뒤 같은 명령을 다시 실행하면 돼요.
75
+ 엔터를 누르면 앱별 결과와 함께 `N개 프로그램에 연결했어요.` 가 나와요. 찾은 앱이 0개면 AI 앱을 먼저 설치한 뒤 같은 명령을 다시 실행하면 돼요.
71
76
 
72
77
  5. 마지막에 이 안내가 보이면 성공이에요.
73
78
 
@@ -93,7 +98,7 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
93
98
  curl -fsSL https://app.datalab.tools/install/mcp.sh | sh
94
99
  ```
95
100
 
96
- 중간에 관리 토큰을 물어보면 1단계 카드에서 복사한 토큰을 붙여넣어요. 아직 AI 앱이 하나도 없으면, 원하는 AI 프로그램을 그 자리에서 골라 설치할 수도 있어요(고르는 건 자유예요).
101
+ 중간에 설치 인증키를 물어보면 1단계에서 복사한 값을 붙여넣어요. 아직 AI 앱이 하나도 없으면, 원하는 AI 프로그램을 그 자리에서 골라 설치할 수도 있어요(고르는 건 자유예요).
97
102
 
98
103
  > 실행 전에 내용을 확인하고 싶으면 위 주소를 브라우저에 그대로 열어 보세요 — 받아서 실행할 스크립트 원문이 그대로 보여요.
99
104
 
@@ -102,7 +107,17 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
102
107
  **자동으로 연결되는 앱**: Claude Desktop, Claude Code, ChatGPT 데스크톱, Codex CLI, Gemini CLI, Cursor, Windsurf, Amazon Q Developer, JetBrains Junie, Kiro.
103
108
  **안내문만 보여 주는 앱**(화면 안내를 따라 하면 돼요): VS Code, Zed, Cline, Roo Code, LM Studio, Warp.
104
109
 
105
- ## 3단계 — 확인
110
+ ## 3단계 — 연결
111
+
112
+ 설치가 끝나면 **확장 설정 화면의 3번 칸**이 바뀌어 있어요. 새로고침하지 않아도 돼요.
113
+
114
+ - **[이 브라우저 연결하기]** 버튼이 보이면 그것만 누르면 끝이에요. 등록 코드를 만들어 붙여넣는 과정을 화면이 대신 해 줘요.
115
+ - **"등록됨"** 이 이미 떠 있으면 할 일이 없어요.
116
+ - 이 컴퓨터의 **다른 브라우저에서 이미 설치**했다면 버튼 대신 등록 코드 칸이 나와요. 그 브라우저에서 만든 코드를 넣으세요(아래 [워크벤치에 크롬 하나 더 올리기](#워크벤치에-크롬-하나-더-올리기)).
117
+
118
+ 등록은 브라우저마다 한 번이에요. 실제 연결은 사이드 패널을 열어 둔 동안 이루어져요.
119
+
120
+ ## 4단계 — 확인
106
121
 
107
122
  - 사이드 패널 위쪽 카드가 **초록색**이고 "연결됨"이 보이면 된 거예요. 초록색이 아니면 [문제해결](#문제해결)로 가세요.
108
123
  - AI 앱에서 "지난주 내 블로그 어땠어?" 처럼 물어보고 실제 수치가 나오면 전부 끝이에요.
@@ -118,10 +133,10 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
118
133
  설치할 때 **글쓰기 도움말(스킬)** 도 함께 놓입니다. AI 앱은 켜질 때마다 설치된 도움말의 이름과 설명을 전부 읽기 때문에, 많이 깔수록 대화에 쓸 자리가 줄어들어요. 그래서 기본은 **바로 쓸 수 있는 것만** 놓습니다.
119
134
 
120
135
  ```
121
- npx -y @modootoday/datalab-extension-mcp@1.8.13 skills list
122
- npx -y @modootoday/datalab-extension-mcp@1.8.13 skills status
123
- npx -y @modootoday/datalab-extension-mcp@1.8.13 skills attach <이름>
124
- npx -y @modootoday/datalab-extension-mcp@1.8.13 skills detach <이름>
136
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 skills list
137
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 skills status
138
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 skills attach <이름>
139
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 skills detach <이름>
125
140
  ```
126
141
 
127
142
  - `list` 는 이 버전에 들어 있는 전부를 이름과 설명으로 보여 줘요.
@@ -137,18 +152,18 @@ npx -y @modootoday/datalab-extension-mcp@1.8.13 skills detach <이름>
137
152
 
138
153
  **워크벤치**는 커넥터가 도는 이 컴퓨터예요. 크롬 프로필을 여러 개 쓰신다면 프로필마다 등록 코드를 하나씩 만들어 같은 워크벤치에 올릴 수 있어요. **한 컴퓨터 안에서만** 됩니다 — 커넥터는 127.0.0.1 에서만 듣기 때문에 다른 컴퓨터는 닿지 못하고, 코드는 남에게 보낼 것이 아니에요.
139
154
 
140
- 패널에서 [등록 코드 만들기]를 눌러도 되고, 터미널을 쓰셔도 돼요.
155
+ 확장 설정 → **다른 AI 앱에 연결 (MCP)** → 3번 아래의 **[다른 브라우저 추가 (선택)]** 를 펼쳐 [등록 코드 만들기]를 눌러도 되고, 터미널을 쓰셔도 돼요.
141
156
 
142
157
  ```
143
- npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers add "회사 크롬"
144
- npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers list
145
- npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers revoke <handle>
146
- npx -y @modootoday/datalab-extension-mcp@1.8.13 browsers repair
158
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 browsers add "회사 크롬"
159
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 browsers list
160
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 browsers revoke <handle>
161
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 browsers repair
147
162
  ```
148
163
 
149
- `add` 를 실행하면 **관리 토큰을 붙여넣으라고 물어봐요** — 설치할 때와 같은 값이고, 패널의 `복사` 버튼으로 얻습니다. 그러면 브라우저 등록 코드가 **한 번만** 출력돼요. 커넥터는 그 코드를 저장하지 않고 지문만 남기므로 다시 보여 줄 수 없어요.
164
+ `add` 를 실행하면 **설치 인증키를 붙여넣으라고 물어봐요** — 설치할 때와 같은 값이고, 설정 화면 1번의 `복사` 버튼으로 얻습니다. 그러면 브라우저 등록 코드가 **한 번만** 출력돼요. 커넥터는 그 코드를 저장하지 않고 지문만 남기므로 다시 보여 줄 수 없어요.
150
165
 
151
- 만든 코드는 올릴 크롬의 사이드 패널 → **이 크롬을 워크벤치에 올리기** 에 붙여넣습니다. 그 브라우저는 자기 코드를 따로 만들지 않아요.
166
+ 만든 코드는 올릴 크롬을 열고 → 확장 설정 → **다른 AI 앱에 연결 (MCP)** → **3. 이 브라우저 등록** 칸에 붙여넣습니다. 그 브라우저는 자기 코드를 따로 만들지 않아요.
152
167
 
153
168
  - `list` 는 `waiting`(코드는 만들었지만 아직 아무도 안 붙음)과 `connected` 를 구분해서 보여 줘요.
154
169
  - `revoke` 뒤에는 그 브라우저가 다시 붙지 못해요. 다시 넣으려면 **`revoke` 한 뒤 새로 `add`** 하세요 — 같은 브라우저에 코드만 새로 줘도 거절돼요.
@@ -181,7 +196,7 @@ AI 앱이 켜질 때 받은 옛 코드로 커넥터가 돌고 있어서 그래
181
196
  2. 그 내용을 AI 앱 설정에 붙여넣은 뒤
182
197
  3. **그 AI 앱을 완전히 종료했다가 다시 켜요** (창 닫기 말고 — Windows 트레이 종료 / Mac `⌘+Q` / Linux 앱 메뉴 Quit)
183
198
 
184
- 3번까지 해야 커넥터가 AI 앱 설정에 저장된 관리 토큰으로 다시 떠요. 브라우저 등록 코드는 이 과정에서 바꾸지 않습니다.
199
+ 3번까지 해야 커넥터가 AI 앱 설정에 저장된 설치 인증키로 다시 떠요. 브라우저 등록 코드는 이 과정에서 바꾸지 않습니다.
185
200
 
186
201
  ### "연결 통로(포트)를 다른 프로그램이 쓰고 있어요."
187
202
 
@@ -222,7 +237,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
222
237
  - **연결이 사라졌을 때** — 처음 실행했던 그 명령을 한 번 더 실행하면 안전하게 다시 등록돼요. (일부 AI 앱이 설정 파일을 정리하며 연결을 지우기도 해요.)
223
238
 
224
239
  ```
225
- npx -y @modootoday/datalab-extension-mcp@1.8.13 install
240
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 install
226
241
  ```
227
242
 
228
243
  - **`'npx'을(는) 내부 또는 외부 명령...`** — Node.js가 없는 거예요. 위 2단계의 "Node.js가 없다면" 한 줄 설치가 가장 간단해요.
@@ -242,7 +257,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
242
257
  - **조회는 그냥 되고, 고치는 건 허락받아요.** 데이터 조회는 바로 돼요. **네이버 글쓰기 창**과 사진·영상 편집기의 내용을 고치려면 사이드패널이나 설정에서 **AI 편집 허용**을 켜야 해요. 두 가지 중에 고르면 됩니다 — 이번 연결에만 켜면 패널을 닫을 때 다시 꺼지고, **항상 허용**으로 저장하면 매번 켜지 않아도 돼요(끄는 것도 같은 자리에서 합니다). 글쓰기 창에서는 제목·본문 넣기와 되돌리기가 열려요. 본문을 통째로 바꾸거나 사진을 넣는 것, 편집기 창을 닫는 것, 지우기는 **항상 허용에 포함되지 않아요** — 그 설정으로는 열리지 않습니다. 요금이 드는 기능은 어떤 설정으로도 열리지 않고, 실행 전에 **매번** 물어봐요.
243
258
  - **발행은 하지 않아요.** 글을 올리거나 공개하는 기능은 연결되지 않습니다 — 고친 내용은 글쓰기 창에 남고, 올릴지는 사용자가 직접 정해요.
244
259
  - **아이디·비밀번호·쿠키를 저장하지 않아요.** 네이버에 직접 접속하지도 않아요. 모든 조회는 내가 로그인해 둔 내 브라우저 안에서 일어나요.
245
- - **내 컴퓨터 안에서만 동작해요.** 밖에서는 접속할 수 없고, 관리 토큰과 확장 확인이라는 이중 잠금이 걸려 있어요.
260
+ - **내 컴퓨터 안에서만 동작해요.** 밖에서는 접속할 수 없고, 설치 인증키와 확장 확인이라는 이중 잠금이 걸려 있어요.
246
261
  - **데이터가 나가는 방식** — "연결된 AI 앱이 요청하면 네이버 데이터가 해당 앱과 그 AI 제공사 서버로 전달돼요." 이게 AI가 답하는 방식이라 처음 켤 때 미리 동의를 받아요. 전달된 데이터를 얼마나 보관·학습하는지는 각 AI 제공사 정책을 따르니, 쓰시는 AI 앱의 개인정보 정책을 확인해 주세요.
247
262
  - **설정에는 정확한 버전이 기록되고, 실행 시 안전한 최신 버전을 확인해요.** 커넥터는 시작할 때와 유휴 상태에서 정본 버전을 확인해 최신 보안·호환 버전으로 승격합니다. 특정 버전을 유지해야 하는 진단 환경은 `--no-self-update`를 사용할 수 있어요.
248
263
  - **소스가 전부 공개된 오픈소스예요.** 다만 이미 같은 권한으로 실행 중인 악성코드까지 막아 주지는 못해요 — 이 커넥터의 잠금은 원격·타 출처 접근을 막는 장치예요.
@@ -252,7 +267,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
252
267
  1. 터미널에서 아래 명령을 실행해요(AI 앱에 등록된 설정을 지워 줘요).
253
268
 
254
269
  ```
255
- npx -y @modootoday/datalab-extension-mcp@1.8.13 uninstall
270
+ npx -y @modootoday/datalab-extension-mcp@1.8.16 uninstall
256
271
  ```
257
272
 
258
273
  2. 확장 설정에서 **MCP 연결을 꺼요.** 패널이 브릿지라, 꺼 두면 어딘가 설정 줄이 남아 있어도 AI 앱이 아무것도 조회할 수 없어요.
@@ -268,7 +283,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
268
283
  - **ChatGPT 웹사이트에서도 되나요?** 아니요. 컴퓨터에 설치한 데스크톱 앱만 연결돼요.
269
284
  - **항상 켜 둬야 하나요?** 아니요. 패널이 열려 있을 때만 연결돼요.
270
285
  - **어떤 데이터를 읽나요?** 블로그 통계·키워드·광고 등 데이터랩툴즈가 보여 주는 것들이에요. 기준은 지금 Chrome에 로그인해 둔 네이버 계정이에요.
271
- - **다른 컴퓨터에서도 되나요?** 네. 컴퓨터마다 한 번씩 설치하고, 관리 토큰도 컴퓨터마다 따로예요.
286
+ - **다른 컴퓨터에서도 되나요?** 네. 컴퓨터마다 한 번씩 설치하고, 설치 인증키도 컴퓨터마다 따로예요.
272
287
  </details>
273
288
 
274
289
  ## 개발자 부록
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  DEFAULT_HOST,
3
3
  DEFAULT_PORT
4
- } from "./chunk-QHEJF6FK.js";
4
+ } from "./chunk-G34WU5WC.js";
5
5
  import {
6
6
  TOKEN_PROMPT_GUIDE,
7
7
  TOKEN_PROMPT_QUESTION,
8
8
  TOKEN_REQUIRED_NON_INTERACTIVE
9
- } from "./chunk-CQRDBJXM.js";
9
+ } from "./chunk-5IYKI4EG.js";
10
10
  import "./chunk-MLKGABMK.js";
11
11
 
12
12
  // src/browsers.ts
@@ -2,9 +2,10 @@ import {
2
2
  DEFAULT_HOST,
3
3
  DEFAULT_PORT,
4
4
  mediaToolDescriptors,
5
+ readTakeoverSecret,
5
6
  spawnDaemon,
6
7
  tryConnect
7
- } from "./chunk-QHEJF6FK.js";
8
+ } from "./chunk-G34WU5WC.js";
8
9
  import {
9
10
  __export
10
11
  } from "./chunk-MLKGABMK.js";
@@ -15630,6 +15631,15 @@ async function ensureDaemonRunning(deps = {}) {
15630
15631
  const readVersion = deps.readVersion ?? defaultReadVersion;
15631
15632
  const shutdown = deps.shutdown ?? defaultShutdown;
15632
15633
  const checkAuthority = deps.checkAuthority ?? defaultCheckAuthority;
15634
+ const readTakeover = deps.readTakeover ?? (() => readTakeoverSecret());
15635
+ const waitForPortRelease = async () => {
15636
+ for (let i = 0; i < attempts; i += 1) {
15637
+ const socket = await connect(host, port, PROBE_TIMEOUT_MS);
15638
+ if (!socket) break;
15639
+ socket.destroy();
15640
+ await sleep(intervalMs);
15641
+ }
15642
+ };
15633
15643
  const spawnAndWait = async () => {
15634
15644
  spawn(daemonEntry, ["serve"]);
15635
15645
  for (let i = 0; i < attempts; i += 1) {
@@ -15659,8 +15669,15 @@ async function ensureDaemonRunning(deps = {}) {
15659
15669
  }
15660
15670
  if (!isOlderVersion(running, selfVersion)) {
15661
15671
  if (await checkAuthority(base, token)) return;
15672
+ const secret = readTakeover();
15673
+ if (secret !== null && await shutdown(base, secret)) {
15674
+ log("retiring the connector that holds this port with a different token");
15675
+ await waitForPortRelease();
15676
+ await spawnAndWait();
15677
+ return;
15678
+ }
15662
15679
  throw new Error(
15663
- `connector authority conflict on port ${String(port)}; every MCP host on this machine must use the same DataLab workbench token`
15680
+ `connector authority conflict on port ${String(port)}; a connector started with a different DataLab workbench token holds it. Quit every AI app so it idles out (about 5 minutes at zero connections), then start this one again.`
15664
15681
  );
15665
15682
  }
15666
15683
  log(`updating the connector: replacing ${running} with ${selfVersion}`);
@@ -15673,12 +15690,7 @@ async function ensureDaemonRunning(deps = {}) {
15673
15690
  `connector authority conflict on port ${String(port)}; the running connector did not accept this workbench token`
15674
15691
  );
15675
15692
  }
15676
- for (let i = 0; i < attempts; i += 1) {
15677
- const socket = await connect(host, port, PROBE_TIMEOUT_MS);
15678
- if (!socket) break;
15679
- socket.destroy();
15680
- await sleep(intervalMs);
15681
- }
15693
+ await waitForPortRelease();
15682
15694
  await spawnAndWait();
15683
15695
  }
15684
15696
  function createAdapterServer(deps = {}) {
@@ -5,23 +5,15 @@ import { spawn as nodeSpawn } from "child_process";
5
5
  import { promises as fs } from "fs";
6
6
  import { homedir } from "os";
7
7
  import { createInterface } from "readline";
8
- var ART = [
9
- "\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557",
10
- "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
11
- "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
12
- "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557",
13
- "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D",
14
- "\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D",
15
- "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \uB370\uC774\uD130\uB7A9\uD234\uC988 \xB7 datalab.tools"
16
- ];
8
+ var WORDMARK = "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \uB370\uC774\uD130\uB7A9\uD234\uC988 \xB7 datalab.tools";
17
9
  function printBanner(out, subtitle) {
18
10
  out("");
19
- for (const row of ART) {
20
- out(row);
21
- }
22
- out("");
11
+ out(WORDMARK);
23
12
  out(` [\uB370\uC774\uD130\uB7A9\uD234\uC988] ${subtitle}`);
13
+ }
14
+ function printStep(out, step, total, title) {
24
15
  out("");
16
+ out(`[${String(step)}/${String(total)}] ${title}`);
25
17
  }
26
18
  var INSTALL_SUBTITLE = "\uCEE4\uB125\uD130 \uC124\uCE58 \uB3C4\uC6B0\uBBF8";
27
19
  var UNINSTALL_SUBTITLE = "\uCEE4\uB125\uD130 \uC815\uB9AC \uB3C4\uC6B0\uBBF8";
@@ -1059,7 +1051,7 @@ async function placeSkills(io, payloadRoot, detectedIds, version, all = false) {
1059
1051
  refused.push(`${payload.slug}: ${REFUSAL_MESSAGE[out.refusal ?? ""]}`);
1060
1052
  }
1061
1053
  }
1062
- const heldNote = held > 0 ? ` ${held}\uAC1C\uB294 \uC870\uAC74\uBD80\uB77C \uBE90\uC5B4\uC694 \u2014 skills list \uB85C \uD655\uC778\uD558\uC138\uC694.` : "";
1054
+ const heldNote = held > 0 ? ` (\uC870\uAC74\uBD80 ${held}\uAC1C \uC81C\uC678)` : "";
1063
1055
  results.push({
1064
1056
  hostId: host.id,
1065
1057
  displayName: host.displayName,
@@ -1115,10 +1107,10 @@ var NO_HOSTS_DETECTED = "\uC5F0\uACB0\uD560 \uC218 \uC788\uB294 AI \uD504\uB85C\
1115
1107
  var SUPPORTED_APPS_HEADER = "\uC9C0\uC6D0\uD558\uB294 \uD504\uB85C\uADF8\uB7A8\uACFC \uB2E4\uC6B4\uB85C\uB4DC \uC8FC\uC18C\uC608\uC694:";
1116
1108
  var AFTER_INSTALL_RETRY = "\uD504\uB85C\uADF8\uB7A8\uC744 \uC124\uCE58\uD55C \uB4A4 \uAC19\uC740 \uBA85\uB839\uC5B4\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
1117
1109
  var ALREADY_INSTALLED_HINT = "\uC774\uBBF8 \uC124\uCE58\uD588\uB294\uB370 \uBAA9\uB85D\uC5D0 \uC5C6\uB2E4\uBA74, \uC124\uC815 \uD30C\uC77C\uC774 \uC544\uC9C1 \uB9CC\uB4E4\uC5B4\uC9C0\uC9C0 \uC54A\uC558\uC744 \uC218 \uC788\uC5B4\uC694. \uC124\uC815 \uD30C\uC77C\uC740 \uD504\uB85C\uADF8\uB7A8\uC744 \uC124\uCE58\uD560 \uB54C\uAC00 \uC544\uB2C8\uB77C \uCC98\uC74C \uC4F8 \uB54C \uB9CC\uB4E4\uC5B4\uC838\uC694:\n - ChatGPT \uB370\uC2A4\uD06C\uD1B1: \uC124\uC815 \u2192 MCP \uC11C\uBC84 \u2192 \uC11C\uBC84 \uCD94\uAC00\uB97C \uD55C \uBC88 \uC2E4\uD589\n - VS Code: \uBA85\uB839 \uD314\uB808\uD2B8(Ctrl+Shift+P) \u2192 'MCP: Open User Configuration' \uC2E4\uD589\n\uADF8 \uB4A4 \uAC19\uC740 \uBA85\uB839\uC5B4\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 \uCC3E\uC744 \uC218 \uC788\uC5B4\uC694.";
1118
- var TOKEN_PROMPT_GUIDE = "\uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uC0AC\uC774\uB4DC \uD328\uB110\uC744 \uC5F4\uACE0 'MCP \uC5F0\uACB0'\uC758 MCP \uD638\uC2A4\uD2B8 \uAD00\uB9AC \uD1A0\uD070\uC744 \uBCF5\uC0AC\uD574 \uC8FC\uC138\uC694.";
1119
- var TOKEN_PROMPT_QUESTION = "\uBCF5\uC0AC\uD55C \uAD00\uB9AC \uD1A0\uD070\uC744 \uC5EC\uAE30\uC5D0 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694:";
1120
- var TOKEN_PROMPT_RETRY = "\uD1A0\uD070 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC544\uC694. \uCE74\uB4DC\uC758 \uD1A0\uD070\uC744 \uB2E4\uC2DC \uBCF5\uC0AC\uD574\uC11C \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.";
1121
- var TOKEN_REQUIRED_NON_INTERACTIVE = "\uAD00\uB9AC \uD1A0\uD070\uC774 \uD544\uC694\uD574\uC694. \uD06C\uB86C\uC5D0\uC11C \uB370\uC774\uD130\uB7A9\uD234\uC988 \uC0AC\uC774\uB4DC \uD328\uB110\uC744 \uC5F4\uACE0 'MCP \uC5F0\uACB0'\uC758 [\uC124\uCE58 \uBA85\uB839 \uBCF5\uC0AC] \uBC84\uD2BC\uC744 \uB20C\uB7EC, \uBCF5\uC0AC\uB41C \uBA85\uB839\uC744 \uADF8\uB300\uB85C \uBD99\uC5EC\uB123\uC5B4 \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
1110
+ var TOKEN_PROMPT_GUIDE = "\uD06C\uB86C \uD655\uC7A5 \uC124\uC815 \u2192 '\uB2E4\uB978 AI \uC571\uC5D0 \uC5F0\uACB0 (MCP)' \u2192 1\uBC88 [\uC124\uCE58 \uC778\uC99D\uD0A4]\uC758 \uAC12\uC744 \uBCF5\uC0AC\uD574 \uC8FC\uC138\uC694.";
1111
+ var TOKEN_PROMPT_QUESTION = "\uBCF5\uC0AC\uD55C \uC124\uCE58 \uC778\uC99D\uD0A4\uB97C \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694:";
1112
+ var TOKEN_PROMPT_RETRY = "\uC778\uC99D\uD0A4 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC544\uC694. \uC124\uC815 \uD654\uBA74\uC5D0\uC11C \uB2E4\uC2DC \uBCF5\uC0AC\uD574 \uBD99\uC5EC\uB123\uC5B4 \uC8FC\uC138\uC694.";
1113
+ var TOKEN_REQUIRED_NON_INTERACTIVE = "\uC124\uCE58 \uC778\uC99D\uD0A4\uAC00 \uD544\uC694\uD574\uC694. \uD06C\uB86C \uD655\uC7A5 \uC124\uC815 \u2192 '\uB2E4\uB978 AI \uC571\uC5D0 \uC5F0\uACB0 (MCP)' \u2192 2\uBC88 \uBA85\uB839\uC758 [\uBCF5\uC0AC] \uBC84\uD2BC\uC744 \uB20C\uB7EC, \uBCF5\uC0AC\uB41C \uBA85\uB839\uC744 \uADF8\uB300\uB85C \uBD99\uC5EC\uB123\uC5B4 \uC2E4\uD589\uD574 \uC8FC\uC138\uC694.";
1122
1114
  var CLI_OFFER_INTRO = "\uC6D0\uD558\uC2DC\uBA74 \uC544\uB798 AI \uD504\uB85C\uADF8\uB7A8 \uC911 \uD558\uB098\uB97C \uC9C0\uAE08 \uBC14\uB85C \uC124\uCE58\uD560 \uC218 \uC788\uC5B4\uC694. Node.js \uAE30\uBC18\uC774\uB77C \uC774 \uC790\uB9AC\uC5D0\uC11C \uC124\uCE58\uB3FC\uC694 \u2014 \uC548 \uD558\uC154\uB3C4 \uAD1C\uCC2E\uC544\uC694.";
1123
1115
  var CLI_OFFER_SKIP_LABEL = "0. \uC9C0\uAE08\uC740 \uC124\uCE58\uD558\uC9C0 \uC54A\uAE30";
1124
1116
  var CLI_OFFER_QUESTION = "\uC124\uCE58\uD560 \uD504\uB85C\uADF8\uB7A8\uC758 \uBC88\uD638\uB97C \uC785\uB825\uD574 \uC8FC\uC138\uC694";
@@ -1589,10 +1581,11 @@ function describeError(error) {
1589
1581
  if (isPermissionError(error)) {
1590
1582
  return PERMISSION_DENIED_HINT;
1591
1583
  }
1584
+ const next = " \uAC19\uC740 \uBA85\uB839\uC744 \uD55C \uBC88 \uB354 \uC2E4\uD589\uD574 \uBCF4\uC2DC\uACE0, \uADF8\uB798\uB3C4 \uC548 \uB418\uBA74 \uC54C\uB824\uC8FC\uC138\uC694.";
1592
1585
  if (error instanceof Error && error.message !== "") {
1593
- return `\uC608\uC0C1\uD558\uC9C0 \uBABB\uD55C \uBB38\uC81C\uAC00 \uC0DD\uACBC\uC5B4\uC694. (${error.message})`;
1586
+ return `\uC608\uC0C1\uD558\uC9C0 \uBABB\uD55C \uBB38\uC81C\uAC00 \uC0DD\uACBC\uC5B4\uC694.${next} (${error.message})`;
1594
1587
  }
1595
- return "\uC608\uC0C1\uD558\uC9C0 \uBABB\uD55C \uBB38\uC81C\uAC00 \uC0DD\uACBC\uC5B4\uC694.";
1588
+ return `\uC608\uC0C1\uD558\uC9C0 \uBABB\uD55C \uBB38\uC81C\uAC00 \uC0DD\uACBC\uC5B4\uC694.${next}`;
1596
1589
  }
1597
1590
  function answeredNo(answer) {
1598
1591
  const normalized = answer.trim().toLowerCase();
@@ -1789,6 +1782,7 @@ async function resolveInstallCredentials(opts, io) {
1789
1782
  }
1790
1783
  async function runInstall(opts, io) {
1791
1784
  printBanner((line) => io.out(line), INSTALL_SUBTITLE);
1785
+ printStep((line) => io.out(line), 1, 4, "\uC124\uCE58 \uC778\uC99D\uD0A4 \uD655\uC778");
1792
1786
  const resolved = await resolveInstallCredentials(opts, io);
1793
1787
  if (resolved === null) return 1;
1794
1788
  const validationError = validateInstallOptions(resolved);
@@ -1796,6 +1790,7 @@ async function runInstall(opts, io) {
1796
1790
  io.out(validationError);
1797
1791
  return 1;
1798
1792
  }
1793
+ io.out(" \uC778\uC99D\uD0A4\uB97C \uD655\uC778\uD588\uC5B4\uC694.");
1799
1794
  const entryOpts = {
1800
1795
  version: resolved.version,
1801
1796
  token: resolved.token,
@@ -1804,6 +1799,8 @@ async function runInstall(opts, io) {
1804
1799
  if (resolved.port !== void 0) {
1805
1800
  entryOpts.port = resolved.port;
1806
1801
  }
1802
+ printStep((line) => io.out(line), 2, 4, "\uC5F0\uACB0\uD560 AI \uD504\uB85C\uADF8\uB7A8 \uCC3E\uAE30");
1803
+ io.out(" \uC124\uCE58\uB41C \uD504\uB85C\uADF8\uB7A8\uC744 \uD655\uC778\uD558\uB294 \uC911\uC774\uC5D0\uC694\u2026");
1807
1804
  let detected = filterByRequestedHosts(await detectHosts(io), opts.hosts);
1808
1805
  if (detected.length === 0) {
1809
1806
  const chosen = await offerCliInstall(io);
@@ -1828,6 +1825,7 @@ async function runInstall(opts, io) {
1828
1825
  return 0;
1829
1826
  }
1830
1827
  }
1828
+ printStep((line) => io.out(line), 3, 4, "\uC5F0\uACB0\uD558\uB294 \uC911");
1831
1829
  const results = [];
1832
1830
  for (const d of detected) {
1833
1831
  try {
@@ -1868,7 +1866,7 @@ async function runInstall(opts, io) {
1868
1866
  )
1869
1867
  );
1870
1868
  }
1871
- io.out("");
1869
+ printStep((line) => io.out(line), 4, 4, "\uACB0\uACFC");
1872
1870
  for (const result of results) {
1873
1871
  printResult(io, result);
1874
1872
  if (result.status === "success" && result.tier === 1) {
@@ -1878,6 +1876,16 @@ async function runInstall(opts, io) {
1878
1876
  }
1879
1877
  }
1880
1878
  }
1879
+ const connected = results.filter(
1880
+ (r) => r.status === "success" && r.surface !== "skills"
1881
+ ).length;
1882
+ const failed = results.filter(
1883
+ (r) => r.status === "failed" && r.tier !== 3
1884
+ ).length;
1885
+ io.out("");
1886
+ io.out(
1887
+ failed === 0 ? `${String(connected)}\uAC1C \uD504\uB85C\uADF8\uB7A8\uC5D0 \uC5F0\uACB0\uD588\uC5B4\uC694.` : `${String(connected)}\uAC1C \uC5F0\uACB0, ${String(failed)}\uAC1C \uC2E4\uD328.`
1888
+ );
1881
1889
  for (const d of detected) {
1882
1890
  if (d.tier === 3 && d.snippet !== void 0) {
1883
1891
  await printSnippetBlock(io, d.snippet, entryOpts);
@@ -2116,7 +2124,7 @@ function createNodeIo() {
2116
2124
  });
2117
2125
  try {
2118
2126
  return await new Promise((resolve) => {
2119
- rl.question(`${question} (Y/n) `, resolve);
2127
+ rl.question(`${question} (\uC5D4\uD130 = \uC608 / n = \uC544\uB2C8\uC624) `, resolve);
2120
2128
  });
2121
2129
  } finally {
2122
2130
  rl.close();
@@ -1,4 +1,4 @@
1
- // ../mcp-server/dist/chunk-HS7SL22A.js
1
+ // ../mcp-server/dist/chunk-FEN47OJU.js
2
2
  import { createHash } from "crypto";
3
3
  import {
4
4
  appendFileSync,
@@ -26,8 +26,13 @@ import { basename, isAbsolute } from "path";
26
26
  import { createConnection } from "net";
27
27
  import { spawn } from "child_process";
28
28
  import { closeSync } from "fs";
29
- import { createRequire } from "module";
29
+ import { randomBytes as randomBytes2 } from "crypto";
30
+ import { chmodSync, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
31
+ import { homedir as homedir2 } from "os";
30
32
  import { join as join4 } from "path";
33
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
34
+ import { createRequire } from "module";
35
+ import { join as join5 } from "path";
31
36
  var __defProp = Object.defineProperty;
32
37
  var __export = (target, all) => {
33
38
  for (var name in all)
@@ -17640,7 +17645,8 @@ function createHttpBridge(opts) {
17640
17645
  if (req.method === "POST" && url2.pathname === "/mcp/shutdown") {
17641
17646
  void readJson(req, MAX_BODY_BYTES).then((body) => {
17642
17647
  const token = body?.token;
17643
- if (!opts.bridge.verifyRootToken(typeof token === "string" ? token : "")) {
17648
+ const presented = typeof token === "string" ? token : "";
17649
+ if (!opts.bridge.verifyRootToken(presented) && !(opts.verifyTakeover?.(presented) ?? false)) {
17644
17650
  json2(res, 403, { error: "unauthorized" });
17645
17651
  return;
17646
17652
  }
@@ -18140,6 +18146,35 @@ var Lifecycle = class {
18140
18146
  }
18141
18147
  }
18142
18148
  };
18149
+ var FILE_NAME3 = "takeover";
18150
+ var OWNER_ONLY = 384;
18151
+ function takeoverPath(home = homedir2()) {
18152
+ return join4(logDir(home), FILE_NAME3);
18153
+ }
18154
+ function writeTakeoverSecret(home = homedir2()) {
18155
+ try {
18156
+ mkdirSync2(logDir(home), { recursive: true });
18157
+ const secret = randomBytes2(32).toString("hex");
18158
+ const path = takeoverPath(home);
18159
+ writeFileSync(path, `${secret}
18160
+ `, { encoding: "utf8", mode: OWNER_ONLY });
18161
+ try {
18162
+ chmodSync(path, OWNER_ONLY);
18163
+ } catch {
18164
+ }
18165
+ return secret;
18166
+ } catch {
18167
+ return null;
18168
+ }
18169
+ }
18170
+ function readTakeoverSecret(home = homedir2()) {
18171
+ try {
18172
+ const raw = readFileSync2(takeoverPath(home), "utf8").trim();
18173
+ return raw === "" ? null : raw;
18174
+ } catch {
18175
+ return null;
18176
+ }
18177
+ }
18143
18178
  var NAME = MCP_SERVER_HEALTH_NAME;
18144
18179
  function readVersion() {
18145
18180
  return createRequire(import.meta.url)("../package.json").version;
@@ -18156,6 +18191,7 @@ function runDaemon(env, deps = {}) {
18156
18191
  }
18157
18192
  const { port, host, token, extensionIds } = resolved.config;
18158
18193
  const version2 = deps.version ?? readVersion();
18194
+ let takeoverSecret = null;
18159
18195
  const lifecycle = new Lifecycle({
18160
18196
  idleMs: deps.idleMs,
18161
18197
  onIdle: () => {
@@ -18201,7 +18237,7 @@ function runDaemon(env, deps = {}) {
18201
18237
  log
18202
18238
  });
18203
18239
  const stateDir = logDir(deps.home);
18204
- const mediaStage = createMediaStage({ root: join4(stateDir, "media") });
18240
+ const mediaStage = createMediaStage({ root: join5(stateDir, "media") });
18205
18241
  const registry2 = createBrowserRegistry({
18206
18242
  path: ledgerPath(deps.home),
18207
18243
  claimed: claimedBrowsers,
@@ -18223,6 +18259,7 @@ function runDaemon(env, deps = {}) {
18223
18259
  release: () => lifecycle.release(),
18224
18260
  bump: () => lifecycle.bump()
18225
18261
  },
18262
+ verifyTakeover: (presented) => takeoverSecret !== null && presented.length === takeoverSecret.length && timingSafeEqual2(Buffer.from(presented), Buffer.from(takeoverSecret)),
18226
18263
  onShutdown: () => {
18227
18264
  log("shutdown requested by an updating adapter; stepping aside");
18228
18265
  void http.close().finally(() => exit(0));
@@ -18240,6 +18277,12 @@ function runDaemon(env, deps = {}) {
18240
18277
  () => {
18241
18278
  log(`listening on http://${host}:${port} \u2014 waiting for the side panel`);
18242
18279
  lifecycle.start();
18280
+ takeoverSecret = writeTakeoverSecret(deps.home);
18281
+ if (takeoverSecret === null) {
18282
+ log(
18283
+ "could not write the takeover file; a host configured with a different token will not be able to retire this connector automatically"
18284
+ );
18285
+ }
18243
18286
  },
18244
18287
  () => {
18245
18288
  log("another instance already owns the port; exiting");
@@ -18268,5 +18311,6 @@ export {
18268
18311
  DEFAULT_HOST,
18269
18312
  tryConnect,
18270
18313
  spawnDaemon,
18314
+ readTakeoverSecret,
18271
18315
  runDaemon
18272
18316
  };
package/dist/cli.js CHANGED
@@ -2,10 +2,10 @@
2
2
  import {
3
3
  dispatchCli,
4
4
  runAdapter
5
- } from "./chunk-M24B42VT.js";
5
+ } from "./chunk-2XHQ7OBB.js";
6
6
  import {
7
7
  runDaemon
8
- } from "./chunk-QHEJF6FK.js";
8
+ } from "./chunk-G34WU5WC.js";
9
9
  import "./chunk-MLKGABMK.js";
10
10
 
11
11
  // src/cli.ts
@@ -17,7 +17,7 @@ function log(message) {
17
17
  `);
18
18
  }
19
19
  async function runInstaller(sub, argv) {
20
- const { createNodeIo, runInstall, runUninstall } = await import("./dist-R3VRQN24.js");
20
+ const { createNodeIo, runInstall, runUninstall } = await import("./dist-TFZ67X6Q.js");
21
21
  const opts = { version: VERSION };
22
22
  for (let i = 0; i < argv.length; i += 1) {
23
23
  const flag = argv[i];
@@ -62,8 +62,8 @@ async function main() {
62
62
  // Loaded lazily, like the installer: the adapter path is what an MCP host
63
63
  // spawns and must not pay for a command only an operator types.
64
64
  browsers: async (argv) => {
65
- const { runBrowsers } = await import("./browsers-M2DRBQB6.js");
66
- const { createNodeIo } = await import("./dist-R3VRQN24.js");
65
+ const { runBrowsers } = await import("./browsers-NCWXRWHL.js");
66
+ const { createNodeIo } = await import("./dist-TFZ67X6Q.js");
67
67
  const io = createNodeIo();
68
68
  const code = await runBrowsers(argv, {
69
69
  env: process.env,
@@ -88,7 +88,7 @@ async function main() {
88
88
  attachSkill,
89
89
  detachSkill,
90
90
  skillsPayloadRoot
91
- } = await import("./dist-R3VRQN24.js");
91
+ } = await import("./dist-TFZ67X6Q.js");
92
92
  const io = createNodeIo();
93
93
  const root = skillsPayloadRoot();
94
94
  const code = await runSkills(argv, {
@@ -118,5 +118,7 @@ async function main() {
118
118
  }
119
119
  main().catch((err) => {
120
120
  log(err instanceof Error ? err.stack ?? err.message : String(err));
121
- process.exit(1);
121
+ process.exitCode = 1;
122
+ const bail = setTimeout(() => process.exit(1), 2e3);
123
+ bail.unref?.();
122
124
  });
@@ -49,7 +49,7 @@ import {
49
49
  upsertServerKey,
50
50
  validateInstallOptions,
51
51
  validateUninstallOptions
52
- } from "./chunk-CQRDBJXM.js";
52
+ } from "./chunk-5IYKI4EG.js";
53
53
  import "./chunk-MLKGABMK.js";
54
54
  export {
55
55
  BACKUP_KEEP,
package/dist/index.d.ts CHANGED
@@ -50,6 +50,11 @@ interface EnsureRunningDeps {
50
50
  log?: (message: string) => void;
51
51
  attempts?: number;
52
52
  intervalMs?: number;
53
+ /**
54
+ * Reads the running daemon's takeover secret from disk. Injected in tests;
55
+ * defaults to the real file.
56
+ */
57
+ readTakeover?: () => string | null;
53
58
  /**
54
59
  * This adapter's own version. When set, a running daemon older than this is
55
60
  * replaced; one at this version or newer is left alone, so an older adapter
package/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import {
3
3
  dispatchCli,
4
4
  ensureDaemonRunning,
5
5
  runAdapter
6
- } from "./chunk-M24B42VT.js";
7
- import "./chunk-QHEJF6FK.js";
6
+ } from "./chunk-2XHQ7OBB.js";
7
+ import "./chunk-G34WU5WC.js";
8
8
  import "./chunk-MLKGABMK.js";
9
9
  export {
10
10
  createAdapterServer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modootoday/datalab-extension-mcp",
3
- "version": "1.8.13",
3
+ "version": "1.8.16",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",