@modootoday/datalab-extension-mcp 1.4.3 → 1.5.1

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
@@ -55,10 +55,10 @@ Avoid sharing a screenshot or the copied setup. If it may have leaked, press **[
55
55
  One command finds **every** AI app on your computer and connects them automatically. Nothing to do per app.
56
56
 
57
57
  1. **Open a terminal** — on Windows type `cmd` in the search box → "Command Prompt"; on Mac press `⌘+Space` and type `Terminal`; on Linux open your terminal app (often `Ctrl+Alt+T`).
58
- 2. **Paste this command and press Enter.** (`@1.4.3` is the pinned version, so the connector never changes behind your back.)
58
+ 2. **Paste this command and press Enter.** (`@1.5.1` is the pinned version, so the connector never changes behind your back.)
59
59
 
60
60
  ```
61
- npx -y @modootoday/datalab-extension-mcp@1.4.3 install
61
+ npx -y @modootoday/datalab-extension-mcp@1.5.1 install
62
62
  ```
63
63
 
64
64
  Lines of English scrolling by is normal. This only downloads and runs the connector — it touches nothing else.
@@ -206,7 +206,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
206
206
  - **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.)
207
207
 
208
208
  ```
209
- npx -y @modootoday/datalab-extension-mcp@1.4.3 install
209
+ npx -y @modootoday/datalab-extension-mcp@1.5.1 install
210
210
  ```
211
211
 
212
212
  - **`'npx' is not recognized...`** — Node.js isn't installed. The "No Node.js?" one-liner in Step 2 is the simplest fix.
@@ -236,7 +236,7 @@ Start it again after a full quit and it picks the tools up. If they still aren't
236
236
  1. Run this in a terminal (it removes the entries registered in your AI apps).
237
237
 
238
238
  ```
239
- npx -y @modootoday/datalab-extension-mcp@1.4.3 uninstall
239
+ npx -y @modootoday/datalab-extension-mcp@1.5.1 uninstall
240
240
  ```
241
241
 
242
242
  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.
package/README.md CHANGED
@@ -49,10 +49,10 @@ Claude 같은 AI 앱을 데이터랩툴즈에 연결해 주는 작은 무료 프
49
49
  명령 한 줄이면 컴퓨터에 있는 AI 앱을 **모두 자동으로** 찾아 연결해 줘요. 앱마다 따로 할 일은 없어요.
50
50
 
51
51
  1. **터미널을 열어요** — Windows는 검색창에 `cmd` → "명령 프롬프트", Mac은 `⌘+스페이스`로 `터미널`, Linux는 터미널 앱(보통 `Ctrl+Alt+T`)이에요.
52
- 2. **아래 명령을 붙여넣고 Enter를 눌러요.** (`@1.4.3`은 커넥터가 나도 모르게 바뀌지 않도록 고정한 버전이에요.)
52
+ 2. **아래 명령을 붙여넣고 Enter를 눌러요.** (`@1.5.1`은 커넥터가 나도 모르게 바뀌지 않도록 고정한 버전이에요.)
53
53
 
54
54
  ```
55
- npx -y @modootoday/datalab-extension-mcp@1.4.3 install
55
+ npx -y @modootoday/datalab-extension-mcp@1.5.1 install
56
56
  ```
57
57
 
58
58
  영어 글자가 여러 줄 올라오는 건 정상이에요. 이 명령은 커넥터를 내려받아 실행할 뿐, 다른 건 건드리지 않아요.
@@ -180,7 +180,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
180
180
  - **연결이 사라졌을 때** — 처음 실행했던 그 명령을 한 번 더 실행하면 안전하게 다시 등록돼요. (일부 AI 앱이 설정 파일을 정리하며 연결을 지우기도 해요.)
181
181
 
182
182
  ```
183
- npx -y @modootoday/datalab-extension-mcp@1.4.3 install
183
+ npx -y @modootoday/datalab-extension-mcp@1.5.1 install
184
184
  ```
185
185
 
186
186
  - **`'npx'을(는) 내부 또는 외부 명령...`** — Node.js가 없는 거예요. 위 2단계의 "Node.js가 없다면" 한 줄 설치가 가장 간단해요.
@@ -210,7 +210,7 @@ AI가 "그런 기능은 없다" 고 답하면, **"도구 목록을 확인해 보
210
210
  1. 터미널에서 아래 명령을 실행해요(AI 앱에 등록된 설정을 지워 줘요).
211
211
 
212
212
  ```
213
- npx -y @modootoday/datalab-extension-mcp@1.4.3 uninstall
213
+ npx -y @modootoday/datalab-extension-mcp@1.5.1 uninstall
214
214
  ```
215
215
 
216
216
  2. 확장 설정에서 **MCP 연결을 꺼요.** 패널이 브릿지라, 꺼 두면 어딘가 설정 줄이 남아 있어도 AI 앱이 아무것도 조회할 수 없어요.
@@ -2,7 +2,7 @@ import {
2
2
  __export
3
3
  } from "./chunk-MLKGABMK.js";
4
4
 
5
- // ../mcp-server/dist/chunk-NRS76HC5.js
5
+ // ../mcp-server/dist/chunk-TPEUGB2T.js
6
6
  import {
7
7
  createServer
8
8
  } from "http";
@@ -14612,6 +14612,24 @@ var READ_ONLY_TOOLS = [
14612
14612
  "ad_keyword_stats",
14613
14613
  "ad_min_exposure_bid",
14614
14614
  "ad_performance_balance",
14615
+ // 🔴 These three read the advertiser's own account rather than pricing a
14616
+ // hypothetical bid — balance, spend, and per-campaign/keyword delivery.
14617
+ // Read-only by
14618
+ // construction: the client exposes no charge, refund or campaign-write path,
14619
+ // and asks for no gender/age breakdown even though /api/stats offers one.
14620
+ "ad_bizmoney",
14621
+ "ad_campaign_stats",
14622
+ "ad_keyword_spend",
14623
+ // --- The seller's own SmartStore money, via the official Commerce API and
14624
+ // the user's own application pair. Read-only by construction: the service
14625
+ // exposes settlement and sales reads and nothing else, and 🔴 it drops the
14626
+ // payout account (accountNo / depositorName / bankType) that the settlement
14627
+ // response carries, so no host ever receives it. ---
14628
+ "commerce_settlement",
14629
+ "commerce_sales",
14630
+ // 🔴 Counts only — the order feed's ids stop at the service boundary.
14631
+ "commerce_orders",
14632
+ "commerce_product_issues",
14615
14633
  // --- Aggregate comment analytics ---
14616
14634
  "comment_category_spread",
14617
14635
  "comment_country",
@@ -14757,6 +14775,13 @@ var READ_ONLY_TOOLS = [
14757
14775
  "benchmark_gap",
14758
14776
  "content_calendar",
14759
14777
  "internal_links",
14778
+ // Hands back the procedure for one step of a full write, shaped by what the
14779
+ // caller measured in the step before. Reads nothing and calls no model.
14780
+ "write_pipeline_step",
14781
+ // 🔴 Writes the title in the panel, where a pick IS the confirmation — but the
14782
+ // pick lives in the consent step, and MCP never reaches it: only a
14783
+ // needs-confirm result continues there, and this one returns prompt material.
14784
+ "title_optimize",
14760
14785
  // --- Grouped with the writing tools, but a pure collector. ---
14761
14786
  // Calls no model itself — it only gathers public data. Admitted after
14762
14787
  // checking behaviour rather than trusting the category label.
@@ -14829,6 +14854,11 @@ var TOOL_TIERS = Object.freeze({
14829
14854
  // 🔴 Only whole-body replacement is destructive: the others append, so the
14830
14855
  // autosave point still holds, while replacing the body overwrites it.
14831
14856
  editor_insert_draft: "write",
14857
+ // 🔴 `write`, and it stays that way because it REFUSES an occupied body rather
14858
+ // than overwriting it. The day it gains a replace fallback, a destructive act
14859
+ // hides under a session toggle the user granted once — so that fallback is a
14860
+ // review-block, not a convenience.
14861
+ editor_place_draft: "write",
14832
14862
  // Opening a window edits no document, but it does create a tab in the user's
14833
14863
  // browser, and a session that has not enabled editing can do nothing with the
14834
14864
  // window anyway — one tier keeps it one thing for the user to allow.
@@ -14840,9 +14870,6 @@ var TOOL_TIERS = Object.freeze({
14840
14870
  // tier is the only barrier, and the user approves each image's source.
14841
14871
  editor_insert_image: "destructive",
14842
14872
  editor_set_title: "write",
14843
- // 🔴 Not an editor tool, but tiered like one: picking a title candidate IS
14844
- // the confirmation and overwrites the title, so it matches editor_set_title.
14845
- title_optimize: "write",
14846
14873
  editor_undo: "write",
14847
14874
  editor_replace: "destructive"
14848
14875
  });
@@ -15019,7 +15046,10 @@ var RESEARCH = [
15019
15046
  "seo_aeo_geo_spec",
15020
15047
  "content_calendar",
15021
15048
  // The last step of that job: turn the design into an actual draft.
15022
- "write_draft"
15049
+ "write_draft",
15050
+ // Names which of the above comes next given what the caller just measured —
15051
+ // the part of the job that cannot be written down ahead of time.
15052
+ "write_pipeline_step"
15023
15053
  ];
15024
15054
  var EDITOR_CONTROL = [
15025
15055
  // 🔴 The tool that opens a tab belongs in the group that requires one: a
@@ -15032,9 +15062,12 @@ var EDITOR_CONTROL = [
15032
15062
  "editor_undo",
15033
15063
  "editor_replace",
15034
15064
  "editor_insert_draft",
15065
+ // Lands a draft from any window state without ever overwriting — refuses and
15066
+ // names the tool to use instead when the body is occupied.
15067
+ "editor_place_draft",
15035
15068
  "editor_set_title",
15036
- // Both act on the open draft: title_optimize overwrites the title the moment
15037
- // a candidate is picked, and internal_links reads the current draft.
15069
+ // Both act on the open draft: title_optimize proposes titles for it, and
15070
+ // internal_links reads it.
15038
15071
  "title_optimize",
15039
15072
  "internal_links",
15040
15073
  "editor_insert_image",
@@ -15046,6 +15079,7 @@ var BY_PREFIX = [
15046
15079
  { slug: "place", prefixes: ["place_"] },
15047
15080
  { slug: "shopping", prefixes: ["shopping_"] },
15048
15081
  { slug: "ad-bidding", prefixes: ["ad_"] },
15082
+ { slug: "store-settlement", prefixes: ["commerce_"] },
15049
15083
  { slug: "comment-stats", prefixes: ["comment_"] },
15050
15084
  { slug: "photo-editor", prefixes: ["photo_"] },
15051
15085
  { slug: "video-editor", prefixes: ["video_"] },
@@ -15076,7 +15110,12 @@ var META = [
15076
15110
  {
15077
15111
  slug: "keyword-research",
15078
15112
  label: "\uD0A4\uC6CC\uB4DC\xB7\uAE00\uAC10 \uB9AC\uC11C\uCE58",
15079
- summary: "\uBB34\uC5C7\uC744 \uC4F8\uC9C0 \u2014 \uD0A4\uC6CC\uB4DC \uC218\uC694\xB7\uC5F0\uAD00\uC5B4\xB7\uC790\uB3D9\uC644\uC131\xB7\uBC1C\uD589\uB7C9\xB7\uC9C0\uC2DDiN \uC9C8\uBB38\xB7\uC9C0\uC5ED \uD2B8\uB80C\uB4DC"
15113
+ summary: "\uBB34\uC5C7\uC744 \uC4F8\uC9C0 \u2014 \uD0A4\uC6CC\uB4DC \uC218\uC694\xB7\uC5F0\uAD00\uC5B4\xB7\uC790\uB3D9\uC644\uC131\xB7\uBC1C\uD589\uB7C9\xB7\uC9C0\uC2DDiN \uC9C8\uBB38\xB7\uC9C0\uC5ED \uD2B8\uB80C\uB4DC",
15114
+ steps: [
15115
+ "search_keywords \uB85C \uC218\uC694\uB97C \uBCF8\uB2E4",
15116
+ "content_volume \uC73C\uB85C \uC774\uBBF8 \uC5BC\uB9C8\uB098 \uC4F0\uC600\uB294\uC9C0 \uBCF8\uB2E4 \u2014 \uC218\uC694\uB9CC \uBCF4\uACE0 \uACE0\uB974\uBA74 \uD3EC\uD654\uB41C \uD0A4\uC6CC\uB4DC\uB97C \uACE0\uB978\uB2E4",
15117
+ "keyword_opportunity \uB85C \uB458\uC744 \uACAC\uC918 \uBE48\uD2C8\uC744 \uCC3E\uB294\uB2E4"
15118
+ ]
15080
15119
  },
15081
15120
  {
15082
15121
  slug: "serp-watch",
@@ -15105,6 +15144,13 @@ var META = [
15105
15144
  // data only when the user's search-ad account credentials are present.
15106
15145
  summary: "\uC785\uCC30\uAC00\uC640 \uC608\uC0C1 \uC131\uACFC \u2014 \uBB34\uB8CC, \uAC80\uC0C9\uAD11\uACE0 \uACC4\uC815 \uC790\uACA9\uC99D\uBA85 \uD544\uC694"
15107
15146
  },
15147
+ {
15148
+ slug: "store-settlement",
15149
+ label: "\uC2A4\uD1A0\uC5B4 \uC815\uC0B0\xB7\uB9E4\uCD9C",
15150
+ // The seller's own money. Nothing here writes, and the payout account the
15151
+ // settlement response carries is dropped before it reaches a host.
15152
+ summary: "\uC815\uC0B0 \uC608\uC815\uC77C\uACFC \uAE08\uC561, \uC77C\uBCC4 \uB9E4\uCD9C \u2014 \uCEE4\uBA38\uC2A4API \uC790\uACA9\uC99D\uBA85 \uD544\uC694"
15153
+ },
15108
15154
  {
15109
15155
  slug: "comment-stats",
15110
15156
  label: "\uB313\uAE00 \uBC18\uC751 \uD1B5\uACC4",
@@ -15116,7 +15162,13 @@ var META = [
15116
15162
  summary: "\uC5F4\uB824 \uC788\uB294 \uB124\uC774\uBC84 \uAE00\uC4F0\uAE30 \uCC3D\uC758 \uC6D0\uACE0\uB97C \uC77D\uACE0 \uACE0\uCE5C\uB2E4",
15117
15163
  needsTab: true,
15118
15164
  openerTool: "editor_open_window",
15119
- reducerTool: "editor_reduce_windows"
15165
+ reducerTool: "editor_reduce_windows",
15166
+ steps: [
15167
+ "editor_read \uB85C \uC9C0\uAE08 \uAE00\uC5D0 \uBB34\uC5C7\uC774 \uC788\uB294\uC9C0 \uBA3C\uC800 \uD655\uC778\uD55C\uB2E4 \u2014 \uBE48 \uC904 \uC54C\uACE0 \uB36E\uC5B4\uC4F0\uB294 \uAC83\uC774 \uAC00\uC7A5 \uD754\uD55C \uC0AC\uACE0\uB2E4",
15168
+ "\uACE0\uCE60 \uB0B4\uC6A9\uC744 \uB9CC\uB4E0\uB2E4",
15169
+ "editor_place_draft \uB85C \uB123\uB294\uB2E4. \uC774\uBBF8 \uB0B4\uC6A9\uC774 \uC788\uC73C\uBA74 \uC9C0\uC6B0\uC9C0 \uC54A\uACE0 \uAC70\uC808\uD558\uBA70 \uB2E4\uC74C \uB3C4\uAD6C\uB97C \uC54C\uB824 \uC900\uB2E4",
15170
+ "\uC804\uCCB4\uB97C \uAC08\uC544\uC5CE\uC5B4\uC57C \uD560 \uB54C\uB9CC, \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB3C\uC5B4\uBCF8 \uB4A4 editor_replace \uB97C \uC4F4\uB2E4"
15171
+ ]
15120
15172
  },
15121
15173
  {
15122
15174
  slug: "photo-editor",
@@ -15135,7 +15187,11 @@ var META = [
15135
15187
  {
15136
15188
  slug: "gallery",
15137
15189
  label: "\uC774\uBBF8\uC9C0 \uAC24\uB7EC\uB9AC",
15138
- summary: "\uB0B4\uAC00 \uB9CC\uB4E0 \uC0AC\uC9C4 \uD30C\uC77C\uC744 \uAC24\uB7EC\uB9AC\uC5D0 \uB123\uC5B4 \uB450 \uD3B8\uC9D1\uAE30\uC5D0\uC11C \uC4F4\uB2E4 \u2014 \uD3B8\uC9D1\uAE30 \uCC3D\uC774 \uC5C6\uC5B4\uB3C4 \uB41C\uB2E4"
15190
+ summary: "\uB0B4\uAC00 \uB9CC\uB4E0 \uC0AC\uC9C4 \uD30C\uC77C\uC744 \uAC24\uB7EC\uB9AC\uC5D0 \uB123\uC5B4 \uB450 \uD3B8\uC9D1\uAE30\uC5D0\uC11C \uC4F4\uB2E4 \u2014 \uD3B8\uC9D1\uAE30 \uCC3D\uC774 \uC5C6\uC5B4\uB3C4 \uB41C\uB2E4",
15191
+ steps: [
15192
+ "gallery_image_add \uB85C \uAC24\uB7EC\uB9AC\uC5D0 \uBA3C\uC800 \uB123\uB294\uB2E4",
15193
+ "\uD3B8\uC9D1\uAE30 \uCABD \uB3C4\uAD6C\uB85C \uADF8 ref \uB97C \uAC00\uC838\uB2E4 \uC4F4\uB2E4 \u2014 \uAC24\uB7EC\uB9AC\uB97C \uAC70\uCE58\uBA74 \uD3B8\uC9D1\uAE30 \uCC3D\uC774 \uC5C6\uC5B4\uB3C4 \uBBF8\uB9AC \uC900\uBE44\uD560 \uC218 \uC788\uB2E4"
15194
+ ]
15139
15195
  }
15140
15196
  ];
15141
15197
  var FALLBACK_SLUG = "other";
@@ -16454,6 +16510,7 @@ function runDaemon(env, deps = {}) {
16454
16510
  }
16455
16511
 
16456
16512
  // src/adapter.ts
16513
+ import { spawnSync } from "child_process";
16457
16514
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
16458
16515
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
16459
16516
  import {
@@ -31105,6 +31162,24 @@ var READ_ONLY_TOOLS2 = [
31105
31162
  "ad_keyword_stats",
31106
31163
  "ad_min_exposure_bid",
31107
31164
  "ad_performance_balance",
31165
+ // 🔴 These three read the advertiser's own account rather than pricing a
31166
+ // hypothetical bid — balance, spend, and per-campaign/keyword delivery.
31167
+ // Read-only by
31168
+ // construction: the client exposes no charge, refund or campaign-write path,
31169
+ // and asks for no gender/age breakdown even though /api/stats offers one.
31170
+ "ad_bizmoney",
31171
+ "ad_campaign_stats",
31172
+ "ad_keyword_spend",
31173
+ // --- The seller's own SmartStore money, via the official Commerce API and
31174
+ // the user's own application pair. Read-only by construction: the service
31175
+ // exposes settlement and sales reads and nothing else, and 🔴 it drops the
31176
+ // payout account (accountNo / depositorName / bankType) that the settlement
31177
+ // response carries, so no host ever receives it. ---
31178
+ "commerce_settlement",
31179
+ "commerce_sales",
31180
+ // 🔴 Counts only — the order feed's ids stop at the service boundary.
31181
+ "commerce_orders",
31182
+ "commerce_product_issues",
31108
31183
  // --- Aggregate comment analytics ---
31109
31184
  "comment_category_spread",
31110
31185
  "comment_country",
@@ -31250,6 +31325,13 @@ var READ_ONLY_TOOLS2 = [
31250
31325
  "benchmark_gap",
31251
31326
  "content_calendar",
31252
31327
  "internal_links",
31328
+ // Hands back the procedure for one step of a full write, shaped by what the
31329
+ // caller measured in the step before. Reads nothing and calls no model.
31330
+ "write_pipeline_step",
31331
+ // 🔴 Writes the title in the panel, where a pick IS the confirmation — but the
31332
+ // pick lives in the consent step, and MCP never reaches it: only a
31333
+ // needs-confirm result continues there, and this one returns prompt material.
31334
+ "title_optimize",
31253
31335
  // --- Grouped with the writing tools, but a pure collector. ---
31254
31336
  // Calls no model itself — it only gathers public data. Admitted after
31255
31337
  // checking behaviour rather than trusting the category label.
@@ -31322,6 +31404,11 @@ var TOOL_TIERS2 = Object.freeze({
31322
31404
  // 🔴 Only whole-body replacement is destructive: the others append, so the
31323
31405
  // autosave point still holds, while replacing the body overwrites it.
31324
31406
  editor_insert_draft: "write",
31407
+ // 🔴 `write`, and it stays that way because it REFUSES an occupied body rather
31408
+ // than overwriting it. The day it gains a replace fallback, a destructive act
31409
+ // hides under a session toggle the user granted once — so that fallback is a
31410
+ // review-block, not a convenience.
31411
+ editor_place_draft: "write",
31325
31412
  // Opening a window edits no document, but it does create a tab in the user's
31326
31413
  // browser, and a session that has not enabled editing can do nothing with the
31327
31414
  // window anyway — one tier keeps it one thing for the user to allow.
@@ -31333,9 +31420,6 @@ var TOOL_TIERS2 = Object.freeze({
31333
31420
  // tier is the only barrier, and the user approves each image's source.
31334
31421
  editor_insert_image: "destructive",
31335
31422
  editor_set_title: "write",
31336
- // 🔴 Not an editor tool, but tiered like one: picking a title candidate IS
31337
- // the confirmation and overwrites the title, so it matches editor_set_title.
31338
- title_optimize: "write",
31339
31423
  editor_undo: "write",
31340
31424
  editor_replace: "destructive"
31341
31425
  });
@@ -31448,6 +31532,25 @@ ${BRIDGE_AGENT_HINT2.notConnected}`;
31448
31532
 
31449
31533
  // src/adapter.ts
31450
31534
  var NAME2 = "datalab-extension-mcp";
31535
+ var PACKAGE_NAME = "@modootoday/datalab-extension-mcp";
31536
+ var DEFAULT_WATCH_MS = 5 * 60 * 1e3;
31537
+ var CANONICAL_VERSION_URL = "https://app.datalab.tools/api/v1/mcp/version";
31538
+ var CANONICAL_TIMEOUT_MS = 4e3;
31539
+ var SEMVER = /^\d+\.\d+\.\d+$/;
31540
+ async function canonicalVersion(fetchImpl = fetch) {
31541
+ try {
31542
+ const ctl = new AbortController();
31543
+ const timer = setTimeout(() => ctl.abort(), CANONICAL_TIMEOUT_MS);
31544
+ const res = await fetchImpl(CANONICAL_VERSION_URL, {
31545
+ signal: ctl.signal
31546
+ }).finally(() => clearTimeout(timer));
31547
+ if (!res.ok) return null;
31548
+ const body = await res.json();
31549
+ return typeof body.version === "string" && SEMVER.test(body.version) ? body.version : null;
31550
+ } catch {
31551
+ return null;
31552
+ }
31553
+ }
31451
31554
  var PROBE_TIMEOUT_MS = 300;
31452
31555
  var READY_ATTEMPTS = 40;
31453
31556
  var READY_INTERVAL_MS = 50;
@@ -31588,6 +31691,7 @@ function createAdapterServer(deps = {}) {
31588
31691
  const fetchImpl = deps.fetchImpl ?? globalThis.fetch;
31589
31692
  const log = deps.log ?? defaultLog;
31590
31693
  const mcpUrl = `http://${host}:${port}/mcp`;
31694
+ const revive = deps.revive ?? ensureDaemonRunning;
31591
31695
  const server = new Server(
31592
31696
  { name: deps.name ?? NAME2, version: deps.version ?? "0.0.0" },
31593
31697
  // Declares that we send list-changed notifications: the daemon signals us
@@ -31596,6 +31700,7 @@ function createAdapterServer(deps = {}) {
31596
31700
  { capabilities: { tools: { listChanged: true } } }
31597
31701
  );
31598
31702
  server.setRequestHandler(ListToolsRequestSchema, async () => {
31703
+ deps.onActivity?.(1);
31599
31704
  try {
31600
31705
  const resp = await postMcp(fetchImpl, mcpUrl, "tools/list");
31601
31706
  if (resp.error) {
@@ -31615,9 +31720,19 @@ function createAdapterServer(deps = {}) {
31615
31720
  isTimeout(err) ? "connector_stuck" : "not_connected"
31616
31721
  )
31617
31722
  );
31723
+ } finally {
31724
+ deps.onActivity?.(-1);
31618
31725
  }
31619
31726
  });
31620
31727
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
31728
+ deps.onActivity?.(1);
31729
+ try {
31730
+ return await handleToolCall(request);
31731
+ } finally {
31732
+ deps.onActivity?.(-1);
31733
+ }
31734
+ });
31735
+ async function handleToolCall(request) {
31621
31736
  const { name, arguments: args } = request.params;
31622
31737
  let resp;
31623
31738
  try {
@@ -31625,20 +31740,45 @@ function createAdapterServer(deps = {}) {
31625
31740
  name,
31626
31741
  arguments: args ?? {}
31627
31742
  });
31628
- } catch (err) {
31629
- log(`tools/call transport failure: ${describeError(err)}`);
31630
- return {
31631
- content: [
31632
- {
31633
- type: "text",
31634
- text: agentActionableMessage2(
31635
- isTimeout(err) ? "connector_stuck" : "not_connected"
31636
- )
31637
- }
31638
- ],
31639
- isError: true
31640
- };
31743
+ } catch (first) {
31744
+ if (isTimeout(first)) return failedCall(first);
31745
+ try {
31746
+ resp = await reviveAndRetry(name, args);
31747
+ } catch (second) {
31748
+ return failedCall(second);
31749
+ }
31641
31750
  }
31751
+ return shapeCallResult(name, resp);
31752
+ }
31753
+ async function reviveAndRetry(name, args) {
31754
+ log(`tools/call found no connector; restarting it and retrying ${name}`);
31755
+ await revive({
31756
+ host: deps.host,
31757
+ port: deps.port,
31758
+ log,
31759
+ selfVersion: deps.version
31760
+ });
31761
+ return postMcp(fetchImpl, mcpUrl, "tools/call", {
31762
+ name,
31763
+ arguments: args ?? {}
31764
+ });
31765
+ }
31766
+ function failedCall(err) {
31767
+ log(`tools/call transport failure: ${describeError(err)}`);
31768
+ return {
31769
+ content: [
31770
+ {
31771
+ type: "text",
31772
+ text: agentActionableMessage2(
31773
+ isTimeout(err) ? "connector_stuck" : "not_connected"
31774
+ )
31775
+ }
31776
+ ],
31777
+ isError: true
31778
+ };
31779
+ }
31780
+ function shapeCallResult(name, resp) {
31781
+ void name;
31642
31782
  if (resp.error) {
31643
31783
  throw new McpError(
31644
31784
  ErrorCode.InternalError,
@@ -31646,7 +31786,7 @@ function createAdapterServer(deps = {}) {
31646
31786
  );
31647
31787
  }
31648
31788
  return resp.result ?? {};
31649
- });
31789
+ }
31650
31790
  return server;
31651
31791
  }
31652
31792
  var defaultSubscribe = async (url3, onEvent, signal) => {
@@ -31669,13 +31809,16 @@ var defaultSubscribe = async (url3, onEvent, signal) => {
31669
31809
  };
31670
31810
  var TOOLCHANGE_BACKOFF_BASE_MS = 250;
31671
31811
  var TOOLCHANGE_BACKOFF_CAP_MS = 5e3;
31812
+ var TOOLCHANGE_REVIVE_AFTER_FAILURES = 3;
31672
31813
  async function pushToolChanges(deps) {
31673
31814
  const subscribe = deps.subscribe ?? defaultSubscribe;
31674
31815
  const sleep = deps.sleep ?? realSleep;
31675
31816
  const rng = deps.rng ?? Math.random;
31676
31817
  const now = deps.now ?? Date.now;
31677
31818
  const url3 = `${deps.mcpUrl.replace(/\/mcp$/, "")}/mcp/notifications`;
31819
+ const revive = deps.revive ?? ensureDaemonRunning;
31678
31820
  let prev = TOOLCHANGE_BACKOFF_BASE_MS;
31821
+ let failures = 0;
31679
31822
  while (!deps.signal.aborted) {
31680
31823
  const startedAt = now();
31681
31824
  try {
@@ -31693,8 +31836,19 @@ async function pushToolChanges(deps) {
31693
31836
  } catch {
31694
31837
  }
31695
31838
  if (deps.signal.aborted) return;
31696
- if (now() - startedAt >= TOOLCHANGE_BACKOFF_BASE_MS) {
31839
+ const held = now() - startedAt >= TOOLCHANGE_BACKOFF_BASE_MS;
31840
+ if (held) {
31697
31841
  prev = TOOLCHANGE_BACKOFF_BASE_MS;
31842
+ failures = 0;
31843
+ } else {
31844
+ failures += 1;
31845
+ }
31846
+ if (failures >= TOOLCHANGE_REVIVE_AFTER_FAILURES) {
31847
+ failures = 0;
31848
+ try {
31849
+ await revive({ log: deps.log, selfVersion: deps.selfVersion });
31850
+ } catch {
31851
+ }
31698
31852
  }
31699
31853
  const upper = Math.min(TOOLCHANGE_BACKOFF_CAP_MS, prev * 3);
31700
31854
  const wait = Math.round(
@@ -31704,7 +31858,41 @@ async function pushToolChanges(deps) {
31704
31858
  prev = wait;
31705
31859
  }
31706
31860
  }
31861
+ async function selfUpdateAndRestart(selfVersion, log, deps = {}) {
31862
+ if (!selfVersion) return false;
31863
+ const target = await (deps.resolve ?? canonicalVersion)();
31864
+ if (!target || !isOlderVersion(selfVersion, target)) return false;
31865
+ const args = deps.argv ?? process.argv.slice(2);
31866
+ log(
31867
+ `updating to the canonical connector ${target} (this build is ${selfVersion})`
31868
+ );
31869
+ const run = deps.spawn ?? spawnSync;
31870
+ const res = run("npx", ["-y", `${PACKAGE_NAME}@${target}`, ...args], {
31871
+ stdio: "inherit",
31872
+ shell: process.platform === "win32"
31873
+ });
31874
+ if (res.error) {
31875
+ log(
31876
+ `could not start ${target} (${res.error.message}); continuing as ${selfVersion}`
31877
+ );
31878
+ return false;
31879
+ }
31880
+ process.exit(res.status ?? 0);
31881
+ }
31882
+ function countRequests(flight, passthrough) {
31883
+ return (delta) => {
31884
+ flight.count += delta;
31885
+ passthrough?.(delta);
31886
+ };
31887
+ }
31707
31888
  async function runAdapter(deps = {}) {
31889
+ if (deps.selfUpdate !== false) {
31890
+ await selfUpdateAndRestart(deps.version, deps.log ?? (() => {
31891
+ }), {
31892
+ resolve: deps.canonical,
31893
+ spawn: deps.spawnImpl
31894
+ });
31895
+ }
31708
31896
  const ensure = deps.ensure ?? ensureDaemonRunning;
31709
31897
  await ensure({
31710
31898
  host: deps.host,
@@ -31712,7 +31900,21 @@ async function runAdapter(deps = {}) {
31712
31900
  log: deps.log,
31713
31901
  selfVersion: deps.version
31714
31902
  });
31715
- const server = createAdapterServer(deps);
31903
+ const flight = { count: 0 };
31904
+ const watchMs = deps.watchIntervalMs ?? DEFAULT_WATCH_MS;
31905
+ const server = createAdapterServer({
31906
+ ...deps,
31907
+ onActivity: countRequests(flight, deps.onActivity)
31908
+ });
31909
+ const watch = deps.selfUpdate === false ? null : setInterval(() => {
31910
+ if (flight.count > 0) return;
31911
+ void selfUpdateAndRestart(deps.version, deps.log ?? (() => {
31912
+ }), {
31913
+ resolve: deps.canonical,
31914
+ spawn: deps.spawnImpl
31915
+ });
31916
+ }, watchMs);
31917
+ watch?.unref?.();
31716
31918
  let transport;
31717
31919
  if (deps.transport) {
31718
31920
  transport = deps.transport;
@@ -31725,6 +31927,7 @@ async function runAdapter(deps = {}) {
31725
31927
  const controller = new AbortController();
31726
31928
  const prevOnClose = transport.onclose;
31727
31929
  transport.onclose = () => {
31930
+ if (watch) clearInterval(watch);
31728
31931
  controller.abort();
31729
31932
  prevOnClose?.();
31730
31933
  };
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  dispatchCli,
4
4
  runAdapter,
5
5
  runDaemon
6
- } from "./chunk-CBHNMIS3.js";
6
+ } from "./chunk-XSYBSML4.js";
7
7
  import "./chunk-MLKGABMK.js";
8
8
 
9
9
  // src/cli.ts
@@ -47,7 +47,13 @@ async function main() {
47
47
  serve: () => {
48
48
  runDaemon(process.env);
49
49
  },
50
- adapter: () => runAdapter({ name: NAME, version: VERSION })
50
+ adapter: () => runAdapter({
51
+ name: NAME,
52
+ version: VERSION,
53
+ // The host config pins an exact version, so without this a paired user
54
+ // never receives a release. `--no-self-update` keeps this build.
55
+ selfUpdate: !process.argv.slice(2).includes("--no-self-update")
56
+ })
51
57
  });
52
58
  }
53
59
  main().catch((err) => {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { spawnSync } from 'node:child_process';
1
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
3
  import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
3
4
 
@@ -11,6 +12,15 @@ import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
11
12
  * what makes the proxy testable without a real socket or daemon.
12
13
  */
13
14
 
15
+ /**
16
+ * The version the operator currently publishes as canonical.
17
+ *
18
+ * 🔴 Asked of OUR gateway, never of npm directly. A floating `@latest` in the
19
+ * host config would let whoever controls the registry push code next to a
20
+ * logged-in browser session; this keeps the decision on a surface we own, with
21
+ * a rollback lever behind it. A failure returns null and changes nothing.
22
+ */
23
+ declare function canonicalVersion(fetchImpl?: typeof fetch): Promise<string | null>;
14
24
  /** The `fetch` shape this module needs — injectable so tests need no real HTTP. */
15
25
  type FetchImpl = (url: string, init: {
16
26
  method: string;
@@ -63,6 +73,12 @@ interface EnsureRunningDeps {
63
73
  */
64
74
  declare function ensureDaemonRunning(deps?: EnsureRunningDeps): Promise<void>;
65
75
  interface AdapterServerDeps {
76
+ /**
77
+ * Marks a request as started (+1) and finished (-1). The promotion watcher
78
+ * uses it to swap builds only while nothing is in flight — re-execing mid
79
+ * request would drop that call on the floor.
80
+ */
81
+ onActivity?: (delta: 1 | -1) => void;
66
82
  host?: string;
67
83
  port?: number;
68
84
  /** Injected in tests; defaults to the global `fetch`. */
@@ -71,6 +87,11 @@ interface AdapterServerDeps {
71
87
  /** MCP handshake identity reported to the host. */
72
88
  name?: string;
73
89
  version?: string;
90
+ /**
91
+ * Brings the daemon back when a call finds nothing listening. Injected in
92
+ * tests; defaults to `ensureDaemonRunning`.
93
+ */
94
+ revive?: (deps: EnsureRunningDeps) => Promise<void>;
74
95
  }
75
96
  /**
76
97
  * Build the low-level MCP Server whose two handlers proxy to the daemon. The
@@ -85,18 +106,21 @@ declare function createAdapterServer(deps?: AdapterServerDeps): Server;
85
106
  */
86
107
  type SubscribeImpl = (url: string, onEvent: (data: string) => void, signal: AbortSignal) => Promise<void>;
87
108
  interface RunAdapterDeps extends AdapterServerDeps {
109
+ /** Injected in tests; how often the canonical version is re-checked. */
110
+ watchIntervalMs?: number;
111
+ /** Injected in tests; defaults to asking the gateway. */
112
+ canonical?: typeof canonicalVersion;
113
+ /** Injected in tests; defaults to the real spawn. */
114
+ spawnImpl?: typeof spawnSync;
88
115
  /** Injected in tests; defaults to `ensureDaemonRunning`. */
89
116
  ensure?: (deps: EnsureRunningDeps) => Promise<void>;
90
117
  /** Injected in tests; defaults to a real stdio transport. */
91
118
  transport?: Transport;
92
119
  /** Injected in tests; defaults to the real SSE subscription. */
93
120
  subscribe?: SubscribeImpl;
121
+ /** false pins this build — `--no-self-update`. Default promotes to canonical. */
122
+ selfUpdate?: boolean;
94
123
  }
95
- /**
96
- * Run the adapter: make sure a daemon exists, then serve MCP over stdio. The
97
- * two legs stay separate so each is testable alone, and this is the only place
98
- * that touches the real stdio transport.
99
- */
100
124
  declare function runAdapter(deps?: RunAdapterDeps): Promise<void>;
101
125
  interface CliHandlers {
102
126
  install: (sub: "install" | "uninstall", argv: readonly string[]) => Promise<void>;
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  dispatchCli,
4
4
  ensureDaemonRunning,
5
5
  runAdapter
6
- } from "./chunk-CBHNMIS3.js";
6
+ } from "./chunk-XSYBSML4.js";
7
7
  import "./chunk-MLKGABMK.js";
8
8
  export {
9
9
  createAdapterServer,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modootoday/datalab-extension-mcp",
3
- "version": "1.4.3",
3
+ "version": "1.5.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",