@zenalexa/unicli 0.213.1 → 0.213.3

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 (350) hide show
  1. package/AGENTS.md +1 -1
  2. package/README.md +4 -4
  3. package/README.zh-CN.md +3 -3
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +11 -2
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/describe.d.ts +26 -0
  8. package/dist/commands/describe.d.ts.map +1 -0
  9. package/dist/commands/describe.js +230 -0
  10. package/dist/commands/describe.js.map +1 -0
  11. package/dist/commands/dev.js +1 -1
  12. package/dist/commands/dev.js.map +1 -1
  13. package/dist/commands/dispatch.d.ts +14 -3
  14. package/dist/commands/dispatch.d.ts.map +1 -1
  15. package/dist/commands/dispatch.js +141 -81
  16. package/dist/commands/dispatch.js.map +1 -1
  17. package/dist/commands/health.d.ts.map +1 -1
  18. package/dist/commands/health.js +1 -1
  19. package/dist/commands/health.js.map +1 -1
  20. package/dist/commands/skills.d.ts.map +1 -1
  21. package/dist/commands/skills.js +1 -1
  22. package/dist/commands/skills.js.map +1 -1
  23. package/dist/constants.d.ts +2 -0
  24. package/dist/constants.d.ts.map +1 -1
  25. package/dist/constants.js +2 -0
  26. package/dist/constants.js.map +1 -1
  27. package/dist/discovery/loader.d.ts +8 -0
  28. package/dist/discovery/loader.d.ts.map +1 -1
  29. package/dist/discovery/loader.js +22 -1
  30. package/dist/discovery/loader.js.map +1 -1
  31. package/dist/engine/args.d.ts +83 -0
  32. package/dist/engine/args.d.ts.map +1 -0
  33. package/dist/engine/args.js +260 -0
  34. package/dist/engine/args.js.map +1 -0
  35. package/dist/engine/executor.d.ts +20 -1
  36. package/dist/engine/executor.d.ts.map +1 -1
  37. package/dist/engine/executor.js +11 -2
  38. package/dist/engine/executor.js.map +1 -1
  39. package/dist/engine/harden.d.ts +48 -0
  40. package/dist/engine/harden.d.ts.map +1 -0
  41. package/dist/engine/harden.js +327 -0
  42. package/dist/engine/harden.js.map +1 -0
  43. package/dist/engine/invoke.d.ts +16 -0
  44. package/dist/engine/invoke.d.ts.map +1 -0
  45. package/dist/engine/invoke.js +15 -0
  46. package/dist/engine/invoke.js.map +1 -0
  47. package/dist/engine/kernel/compile.d.ts +30 -0
  48. package/dist/engine/kernel/compile.d.ts.map +1 -0
  49. package/dist/engine/kernel/compile.js +167 -0
  50. package/dist/engine/kernel/compile.js.map +1 -0
  51. package/dist/engine/kernel/execute.d.ts +35 -0
  52. package/dist/engine/kernel/execute.d.ts.map +1 -0
  53. package/dist/engine/kernel/execute.js +221 -0
  54. package/dist/engine/kernel/execute.js.map +1 -0
  55. package/dist/engine/kernel/types.d.ts +49 -0
  56. package/dist/engine/kernel/types.d.ts.map +1 -0
  57. package/dist/engine/kernel/types.js +9 -0
  58. package/dist/engine/kernel/types.js.map +1 -0
  59. package/dist/engine/kernel/ulid.d.ts +21 -0
  60. package/dist/engine/kernel/ulid.d.ts.map +1 -0
  61. package/dist/engine/kernel/ulid.js +76 -0
  62. package/dist/engine/kernel/ulid.js.map +1 -0
  63. package/dist/engine/template.d.ts.map +1 -1
  64. package/dist/engine/template.js +11 -0
  65. package/dist/engine/template.js.map +1 -1
  66. package/dist/manifest.json +1 -1
  67. package/dist/mcp/dispatch.d.ts +46 -0
  68. package/dist/mcp/dispatch.d.ts.map +1 -0
  69. package/dist/mcp/dispatch.js +109 -0
  70. package/dist/mcp/dispatch.js.map +1 -0
  71. package/dist/mcp/handler.d.ts +30 -0
  72. package/dist/mcp/handler.d.ts.map +1 -0
  73. package/dist/mcp/handler.js +293 -0
  74. package/dist/mcp/handler.js.map +1 -0
  75. package/dist/mcp/http-transport.d.ts +16 -0
  76. package/dist/mcp/http-transport.d.ts.map +1 -0
  77. package/dist/mcp/http-transport.js +124 -0
  78. package/dist/mcp/http-transport.js.map +1 -0
  79. package/dist/mcp/server.d.ts +17 -33
  80. package/dist/mcp/server.d.ts.map +1 -1
  81. package/dist/mcp/server.js +31 -742
  82. package/dist/mcp/server.js.map +1 -1
  83. package/dist/mcp/sse-transport.js.map +1 -1
  84. package/dist/mcp/streamable-http/handle-post.d.ts +23 -0
  85. package/dist/mcp/streamable-http/handle-post.d.ts.map +1 -0
  86. package/dist/mcp/streamable-http/handle-post.js +357 -0
  87. package/dist/mcp/streamable-http/handle-post.js.map +1 -0
  88. package/dist/mcp/streamable-http/index.d.ts +45 -0
  89. package/dist/mcp/streamable-http/index.d.ts.map +1 -0
  90. package/dist/mcp/streamable-http/index.js +150 -0
  91. package/dist/mcp/streamable-http/index.js.map +1 -0
  92. package/dist/mcp/streamable-http/session.d.ts +85 -0
  93. package/dist/mcp/streamable-http/session.d.ts.map +1 -0
  94. package/dist/mcp/streamable-http/session.js +104 -0
  95. package/dist/mcp/streamable-http/session.js.map +1 -0
  96. package/dist/mcp/streamable-http.d.ts +7 -85
  97. package/dist/mcp/streamable-http.d.ts.map +1 -1
  98. package/dist/mcp/streamable-http.js +6 -568
  99. package/dist/mcp/streamable-http.js.map +1 -1
  100. package/dist/mcp/tools.d.ts +57 -0
  101. package/dist/mcp/tools.d.ts.map +1 -0
  102. package/dist/mcp/tools.js +237 -0
  103. package/dist/mcp/tools.js.map +1 -0
  104. package/dist/output/envelope.d.ts +29 -0
  105. package/dist/output/envelope.d.ts.map +1 -1
  106. package/dist/output/envelope.js +6 -0
  107. package/dist/output/envelope.js.map +1 -1
  108. package/dist/output/next-actions.d.ts +18 -0
  109. package/dist/output/next-actions.d.ts.map +1 -0
  110. package/dist/output/next-actions.js +64 -0
  111. package/dist/output/next-actions.js.map +1 -0
  112. package/dist/output/projection.d.ts +93 -0
  113. package/dist/output/projection.d.ts.map +1 -0
  114. package/dist/output/projection.js +255 -0
  115. package/dist/output/projection.js.map +1 -0
  116. package/dist/protocol/acp-helpers.d.ts +32 -0
  117. package/dist/protocol/acp-helpers.d.ts.map +1 -0
  118. package/dist/protocol/acp-helpers.js +175 -0
  119. package/dist/protocol/acp-helpers.js.map +1 -0
  120. package/dist/protocol/acp.d.ts +2 -27
  121. package/dist/protocol/acp.d.ts.map +1 -1
  122. package/dist/protocol/acp.js +7 -170
  123. package/dist/protocol/acp.js.map +1 -1
  124. package/dist/types.d.ts +32 -0
  125. package/dist/types.d.ts.map +1 -1
  126. package/dist/types.js.map +1 -1
  127. package/package.json +13 -2
  128. package/src/adapters/1688/item.yaml +1 -0
  129. package/src/adapters/1688/store.yaml +1 -0
  130. package/src/adapters/36kr/article.yaml +1 -0
  131. package/src/adapters/amazon/bestsellers.yaml +2 -0
  132. package/src/adapters/amazon/movers-shakers.yaml +2 -0
  133. package/src/adapters/amazon/new-releases.yaml +2 -0
  134. package/src/adapters/amazon/rankings.yaml +1 -0
  135. package/src/adapters/apple-podcasts/episodes.yaml +1 -0
  136. package/src/adapters/arxiv/paper.yaml +1 -0
  137. package/src/adapters/audacity/convert.yaml +1 -0
  138. package/src/adapters/audacity/effects.yaml +1 -0
  139. package/src/adapters/audacity/info.yaml +1 -0
  140. package/src/adapters/audacity/mix.yaml +1 -0
  141. package/src/adapters/audacity/normalize.yaml +1 -0
  142. package/src/adapters/audacity/spectrogram.yaml +1 -0
  143. package/src/adapters/audacity/split-channels.yaml +1 -0
  144. package/src/adapters/audacity/trim.yaml +1 -0
  145. package/src/adapters/bilibili/favorites.yaml +1 -0
  146. package/src/adapters/blender/animation.yaml +2 -0
  147. package/src/adapters/blender/camera.yaml +1 -0
  148. package/src/adapters/blender/convert.yaml +1 -0
  149. package/src/adapters/blender/export.yaml +1 -0
  150. package/src/adapters/blender/import.yaml +1 -0
  151. package/src/adapters/blender/info.yaml +1 -0
  152. package/src/adapters/blender/lighting.yaml +1 -0
  153. package/src/adapters/blender/materials.yaml +1 -0
  154. package/src/adapters/blender/objects.yaml +1 -0
  155. package/src/adapters/blender/render.yaml +2 -0
  156. package/src/adapters/blender/scene.yaml +1 -0
  157. package/src/adapters/blender/screenshot.yaml +2 -0
  158. package/src/adapters/blender/script.yaml +1 -0
  159. package/src/adapters/boss/detail.yaml +2 -0
  160. package/src/adapters/cloudcompare/info.yaml +1 -0
  161. package/src/adapters/coupang/add-to-cart.yaml +1 -0
  162. package/src/adapters/ctrip/search.yaml +1 -0
  163. package/src/adapters/douban/download.yaml +1 -0
  164. package/src/adapters/douban/photos.yaml +1 -0
  165. package/src/adapters/douban/subject.yaml +2 -0
  166. package/src/adapters/doubao-web/detail.yaml +1 -0
  167. package/src/adapters/doubao-web/meeting-summary.yaml +1 -0
  168. package/src/adapters/doubao-web/meeting-transcript.yaml +1 -0
  169. package/src/adapters/drawio/export.yaml +2 -0
  170. package/src/adapters/facebook/join-group.yaml +2 -0
  171. package/src/adapters/ffmpeg/compress.yaml +2 -0
  172. package/src/adapters/ffmpeg/concat.yaml +1 -0
  173. package/src/adapters/ffmpeg/convert.yaml +1 -0
  174. package/src/adapters/ffmpeg/extract-audio.yaml +2 -0
  175. package/src/adapters/ffmpeg/gif.yaml +2 -0
  176. package/src/adapters/ffmpeg/normalize.yaml +2 -0
  177. package/src/adapters/ffmpeg/probe.yaml +1 -0
  178. package/src/adapters/ffmpeg/resize.yaml +2 -0
  179. package/src/adapters/ffmpeg/subtitles.yaml +2 -0
  180. package/src/adapters/ffmpeg/thumbnail.yaml +2 -0
  181. package/src/adapters/ffmpeg/trim.yaml +2 -0
  182. package/src/adapters/freecad/assembly.yaml +1 -0
  183. package/src/adapters/freecad/bom.yaml +1 -0
  184. package/src/adapters/freecad/boolean.yaml +1 -0
  185. package/src/adapters/freecad/check.yaml +1 -0
  186. package/src/adapters/freecad/convert.yaml +1 -0
  187. package/src/adapters/freecad/export-stl.yaml +1 -0
  188. package/src/adapters/freecad/import.yaml +1 -0
  189. package/src/adapters/freecad/info.yaml +1 -0
  190. package/src/adapters/freecad/macro.yaml +1 -0
  191. package/src/adapters/freecad/measure.yaml +1 -0
  192. package/src/adapters/freecad/mesh.yaml +1 -0
  193. package/src/adapters/freecad/properties.yaml +1 -0
  194. package/src/adapters/freecad/render.yaml +1 -0
  195. package/src/adapters/freecad/section.yaml +1 -0
  196. package/src/adapters/freecad/sketch.yaml +1 -0
  197. package/src/adapters/gimp/adjust.yaml +1 -0
  198. package/src/adapters/gimp/batch.yaml +1 -0
  199. package/src/adapters/gimp/convert.yaml +1 -0
  200. package/src/adapters/gimp/crop.yaml +1 -0
  201. package/src/adapters/gimp/filter.yaml +1 -0
  202. package/src/adapters/gimp/flip.yaml +1 -0
  203. package/src/adapters/gimp/info.yaml +1 -0
  204. package/src/adapters/gimp/layers.yaml +1 -0
  205. package/src/adapters/gimp/merge-layers.yaml +1 -0
  206. package/src/adapters/gimp/resize.yaml +1 -0
  207. package/src/adapters/gimp/rotate.yaml +1 -0
  208. package/src/adapters/gimp/text.yaml +1 -0
  209. package/src/adapters/godot/scene-export.yaml +1 -0
  210. package/src/adapters/hackernews/comments.yaml +1 -0
  211. package/src/adapters/hackernews/item.yaml +1 -0
  212. package/src/adapters/hf/top.yaml +1 -0
  213. package/src/adapters/imagemagick/composite.yaml +1 -0
  214. package/src/adapters/imagemagick/convert.yaml +1 -0
  215. package/src/adapters/imagemagick/identify.yaml +1 -0
  216. package/src/adapters/imagemagick/montage.yaml +1 -0
  217. package/src/adapters/imagemagick/resize.yaml +1 -0
  218. package/src/adapters/imdb/person.yaml +1 -0
  219. package/src/adapters/imdb/reviews.yaml +1 -0
  220. package/src/adapters/imdb/title.yaml +1 -0
  221. package/src/adapters/inkscape/convert.yaml +2 -0
  222. package/src/adapters/inkscape/export.yaml +2 -0
  223. package/src/adapters/inkscape/optimize.yaml +2 -0
  224. package/src/adapters/instagram/stories.yaml +1 -0
  225. package/src/adapters/jd/item.yaml +1 -0
  226. package/src/adapters/jike/post.yaml +2 -0
  227. package/src/adapters/jike/topic.yaml +2 -0
  228. package/src/adapters/kdenlive/info.yaml +1 -0
  229. package/src/adapters/kdenlive/render.yaml +1 -0
  230. package/src/adapters/krita/batch.yaml +1 -0
  231. package/src/adapters/krita/convert.yaml +1 -0
  232. package/src/adapters/krita/export.yaml +1 -0
  233. package/src/adapters/krita/info.yaml +1 -0
  234. package/src/adapters/libreoffice/convert.yaml +1 -0
  235. package/src/adapters/libreoffice/print.yaml +1 -0
  236. package/src/adapters/linear/issue-update.yaml +1 -0
  237. package/src/adapters/linux-do/category.yaml +1 -0
  238. package/src/adapters/linux-do/topic.yaml +1 -0
  239. package/src/adapters/macos/calendar-create.yaml +1 -0
  240. package/src/adapters/macos/finder-copy.yaml +1 -0
  241. package/src/adapters/macos/finder-move.yaml +1 -0
  242. package/src/adapters/macos/finder-new-folder.yaml +1 -0
  243. package/src/adapters/macos/finder-tags.yaml +1 -0
  244. package/src/adapters/macos/open.yaml +2 -0
  245. package/src/adapters/macos/screen-recording.yaml +1 -0
  246. package/src/adapters/macos/screenshot.yaml +1 -0
  247. package/src/adapters/macos/spotlight.yaml +1 -0
  248. package/src/adapters/macos/wallpaper.yaml +1 -0
  249. package/src/adapters/medium/article.yaml +1 -0
  250. package/src/adapters/mermaid/render.yaml +1 -0
  251. package/src/adapters/minimax/tts.yaml +1 -0
  252. package/src/adapters/motion-studio/component-get.yaml +1 -0
  253. package/src/adapters/musescore/convert.yaml +2 -0
  254. package/src/adapters/musescore/export.yaml +2 -0
  255. package/src/adapters/musescore/info.yaml +1 -0
  256. package/src/adapters/musescore/instruments.yaml +1 -0
  257. package/src/adapters/musescore/transpose.yaml +1 -0
  258. package/src/adapters/netease-music/playlist.yaml +1 -0
  259. package/src/adapters/netease-music/top.yaml +1 -0
  260. package/src/adapters/notebooklm/get.yaml +1 -0
  261. package/src/adapters/notebooklm/history.yaml +1 -0
  262. package/src/adapters/notebooklm/note-list.yaml +1 -0
  263. package/src/adapters/notebooklm/notes-get.yaml +1 -0
  264. package/src/adapters/notebooklm/open.yaml +1 -0
  265. package/src/adapters/notebooklm/source-fulltext.yaml +1 -0
  266. package/src/adapters/notebooklm/source-get.yaml +1 -0
  267. package/src/adapters/notebooklm/source-guide.yaml +1 -0
  268. package/src/adapters/notebooklm/source-list.yaml +1 -0
  269. package/src/adapters/notebooklm/summary.yaml +1 -0
  270. package/src/adapters/novita/status.yaml +1 -0
  271. package/src/adapters/obs/screenshot.yaml +1 -0
  272. package/src/adapters/obsidian/open.yaml +1 -0
  273. package/src/adapters/ones/login.yaml +1 -0
  274. package/src/adapters/ones/task.yaml +1 -0
  275. package/src/adapters/pandoc/convert.yaml +1 -0
  276. package/src/adapters/paperreview/feedback.yaml +2 -0
  277. package/src/adapters/paperreview/review.yaml +2 -0
  278. package/src/adapters/paperreview/submit.yaml +2 -0
  279. package/src/adapters/pixiv/detail.yaml +1 -0
  280. package/src/adapters/pixiv/download.yaml +2 -0
  281. package/src/adapters/quark/ls.yaml +1 -0
  282. package/src/adapters/reddit/comments.yaml +2 -0
  283. package/src/adapters/renderdoc/capture-list.yaml +1 -0
  284. package/src/adapters/renderdoc/frame-export.yaml +1 -0
  285. package/src/adapters/reuters/article.yaml +2 -0
  286. package/src/adapters/shotcut/info.yaml +1 -0
  287. package/src/adapters/shotcut/render.yaml +1 -0
  288. package/src/adapters/sinablog/article.yaml +1 -0
  289. package/src/adapters/sinablog/user.yaml +1 -0
  290. package/src/adapters/sketch/artboards.yaml +1 -0
  291. package/src/adapters/sketch/export.yaml +1 -0
  292. package/src/adapters/sketch/symbols.yaml +1 -0
  293. package/src/adapters/smzdm/article.yaml +1 -0
  294. package/src/adapters/stackoverflow/question.yaml +1 -0
  295. package/src/adapters/stagehand/wrap-observe.yaml +1 -0
  296. package/src/adapters/steam/wishlist.yaml +1 -0
  297. package/src/adapters/tieba/read.yaml +1 -0
  298. package/src/adapters/tiktok/comment.yaml +1 -0
  299. package/src/adapters/tiktok/like.yaml +1 -0
  300. package/src/adapters/tiktok/save.yaml +1 -0
  301. package/src/adapters/tiktok/unlike.yaml +1 -0
  302. package/src/adapters/tiktok/unsave.yaml +1 -0
  303. package/src/adapters/twitch/streams.yaml +1 -0
  304. package/src/adapters/twitter/quotes.yaml +2 -0
  305. package/src/adapters/twitter/retweets.yaml +1 -0
  306. package/src/adapters/v2ex/replies.yaml +1 -0
  307. package/src/adapters/v2ex/topic.yaml +1 -0
  308. package/src/adapters/vscode/install-ext.yaml +1 -0
  309. package/src/adapters/vscode/open.yaml +1 -0
  310. package/src/adapters/web/read.yaml +1 -0
  311. package/src/adapters/weibo/comments.yaml +1 -0
  312. package/src/adapters/weibo/post.yaml +1 -0
  313. package/src/adapters/weixin/download.yaml +1 -0
  314. package/src/adapters/wiremock/create-stub.yaml +2 -0
  315. package/src/adapters/wiremock/delete-stub.yaml +1 -0
  316. package/src/adapters/wiremock/verify.yaml +1 -0
  317. package/src/adapters/xianyu/item.yaml +1 -0
  318. package/src/adapters/xiaoe/catalog.yaml +1 -0
  319. package/src/adapters/xiaoe/content.yaml +1 -0
  320. package/src/adapters/xiaoe/detail.yaml +1 -0
  321. package/src/adapters/xiaoe/play-url.yaml +1 -0
  322. package/src/adapters/xiaoyuzhou/podcast.yaml +2 -0
  323. package/src/adapters/yollomi/edit.yaml +2 -0
  324. package/src/adapters/yollomi/remove-bg.yaml +2 -0
  325. package/src/adapters/yollomi/restore.yaml +2 -0
  326. package/src/adapters/yollomi/upload.yaml +2 -0
  327. package/src/adapters/yollomi/upscale.yaml +2 -0
  328. package/src/adapters/youtube/playlist.yaml +1 -0
  329. package/src/adapters/yt-dlp/download.yaml +2 -0
  330. package/src/adapters/yt-dlp/extract-audio.yaml +2 -0
  331. package/src/adapters/yt-dlp/info.yaml +1 -0
  332. package/src/adapters/zhihu/answer.yaml +1 -0
  333. package/src/adapters/zhihu/answers.yaml +2 -0
  334. package/src/adapters/zhihu/article.yaml +1 -0
  335. package/src/adapters/zhihu/articles.yaml +2 -0
  336. package/src/adapters/zhihu/collections.yaml +2 -0
  337. package/src/adapters/zhihu/columns.yaml +2 -0
  338. package/src/adapters/zhihu/comment.yaml +1 -0
  339. package/src/adapters/zhihu/download.yaml +1 -0
  340. package/src/adapters/zhihu/followers.yaml +2 -0
  341. package/src/adapters/zhihu/following.yaml +2 -0
  342. package/src/adapters/zhihu/pins.yaml +2 -0
  343. package/src/adapters/zhihu/question.yaml +1 -0
  344. package/src/adapters/zhihu/topic.yaml +1 -0
  345. package/src/adapters/zhihu/topics.yaml +1 -0
  346. package/src/adapters/zhihu/user.yaml +2 -0
  347. package/src/adapters/zoom/join.yaml +1 -0
  348. package/src/adapters/zsxq/search.yaml +1 -0
  349. package/src/adapters/zsxq/topic.yaml +1 -0
  350. package/src/adapters/zsxq/topics.yaml +1 -0
@@ -1,419 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * MCP (Model Context Protocol) server for Uni-CLI.
3
+ * MCP (Model Context Protocol) server entry point for Uni-CLI.
4
4
  *
5
- * Two registration modes:
6
- * 1. **Smart default** 3 tools: `unicli_run`, `unicli_list`,
7
- * `unicli_discover`. Keeps the MCP handshake under 200 tokens.
8
- * 2. **Expanded (`--expanded`)** one tool per adapter command
9
- * (`unicli_<site>_<command>`) with JSON Schema derived from `args` +
10
- * `columns`. MCP clients see the full Uni-CLI surface area.
5
+ * Thin bootstrap: load adapters → build tool list → wire the transport →
6
+ * start serving. The meat lives in sibling modules:
7
+ * - `./tools.ts` tool-definition builders (default/expanded/deferred)
8
+ * - `./dispatch.ts` kernel-backed tool-call dispatcher
9
+ * - `./handler.ts` JSON-RPC method dispatch
10
+ * - `./http-transport.ts` POST /mcp transport
11
+ * - `./streamable-http/` — Streamable HTTP transport (MCP spec 2025-11-25)
12
+ *
13
+ * Three modes:
14
+ * - default (~200 tokens) — 4 meta-tools only
15
+ * - expanded (~160K tokens) — one tool per adapter command
16
+ * - deferred (~8K tokens) — stubs for ToolSearch-aware clients
11
17
  *
12
18
  * Three transports:
13
- * - **stdio (default)** — newline-delimited JSON over stdin/stdout
14
- * - **http (`--transport http [--port 19826]`)** — POST /mcp accepts a
15
- * single JSON-RPC envelope and returns a single JSON response.
16
- * - **sse (`--transport sse [--port 19826]`)** — Streamable HTTP with
17
- * Server-Sent Events. GET /mcp/sse opens the event stream, POST
18
- * /mcp/message?sessionId=xxx delivers JSON-RPC requests.
19
+ * - stdio (default) — newline-delimited JSON over stdin/stdout
20
+ * - http — POST /mcp single request/response
21
+ * - streamable — Streamable HTTP with SSE session resume
19
22
  *
20
23
  * Auth pass-through is automatic: every adapter the CLI loads (including
21
24
  * cookie-based ones) is exposed by name; the runtime resolves cookies on
22
25
  * each call via the same code path as the CLI.
23
26
  */
24
27
  import { createInterface } from "node:readline";
25
- import { createServer, } from "node:http";
26
28
  import { loadAllAdapters, loadTsAdapters } from "../discovery/loader.js";
27
- import { getAllAdapters, listCommands, resolveCommand } from "../registry.js";
28
- import { runPipeline } from "../engine/executor.js";
29
- import { VERSION, MCP_PROTOCOL_VERSION } from "../constants.js";
30
- // sse-transport.ts is deprecated (spec 2025-03-26). Kept for backwards compatibility.
31
- // import { startSseServer } from "./sse-transport.js";
32
- import { startStreamableHttp } from "./streamable-http.js";
33
- import { handleOAuthRoute, createOAuthMiddleware } from "./oauth.js";
34
- import { buildInputSchema, buildOutputSchema, buildToolName, truncateDescription, } from "./schema.js";
35
- import { resolveElicitation } from "./elicitation.js";
36
- // ── Smart default tools (4 meta-tools — the default mode) ─────────────────
37
- const MAX_RESULT_SIZE_CHARS = 10_000;
38
- function buildDefaultTools() {
39
- return [
40
- {
41
- name: "unicli_run",
42
- description: "Execute any Uni-CLI command. Returns JSON results.",
43
- inputSchema: {
44
- type: "object",
45
- properties: {
46
- site: {
47
- type: "string",
48
- description: "Site name (e.g. hackernews, github, bilibili)",
49
- },
50
- command: {
51
- type: "string",
52
- description: "Command to run (e.g. top, search, hot)",
53
- },
54
- args: {
55
- type: "object",
56
- description: 'Key-value arguments (e.g. {"query": "ai", "limit": 10})',
57
- additionalProperties: true,
58
- },
59
- },
60
- required: ["site", "command"],
61
- },
62
- _meta: {
63
- "anthropic/searchHint": "Execute CLI commands on 200+ websites and desktop apps. Run adapters by site and command name.",
64
- },
65
- annotations: {
66
- readOnlyHint: false,
67
- destructiveHint: false,
68
- idempotentHint: false,
69
- openWorldHint: true,
70
- },
71
- },
72
- {
73
- name: "unicli_list",
74
- description: "List available commands. Filter by site or adapter type.",
75
- inputSchema: {
76
- type: "object",
77
- properties: {
78
- site: {
79
- type: "string",
80
- description: "Filter by site name (partial match)",
81
- },
82
- type: {
83
- type: "string",
84
- description: "Filter by adapter type",
85
- enum: ["web-api", "desktop", "browser", "bridge", "service"],
86
- },
87
- },
88
- },
89
- _meta: {
90
- "anthropic/searchHint": "Browse available Uni-CLI sites and commands. Filter by site name or adapter type.",
91
- "anthropic/alwaysLoad": true,
92
- },
93
- annotations: {
94
- readOnlyHint: true,
95
- idempotentHint: true,
96
- openWorldHint: false,
97
- },
98
- },
99
- {
100
- name: "unicli_search",
101
- description: "Search 200+ sites and 956 commands by intent. Bilingual (EN/ZH). Returns top matches with usage examples.",
102
- inputSchema: {
103
- type: "object",
104
- properties: {
105
- query: {
106
- type: "string",
107
- description: "Natural language intent (e.g. 'download video', '推特热门', 'stock price')",
108
- },
109
- limit: {
110
- type: "integer",
111
- description: "Max results (default 5)",
112
- default: 5,
113
- },
114
- },
115
- required: ["query"],
116
- },
117
- _meta: {
118
- "anthropic/searchHint": "Find CLI commands by intent. Semantic search across websites, desktop apps, macOS. Bilingual Chinese/English.",
119
- "anthropic/alwaysLoad": true,
120
- },
121
- annotations: {
122
- readOnlyHint: true,
123
- idempotentHint: true,
124
- openWorldHint: false,
125
- },
126
- },
127
- {
128
- name: "unicli_explore",
129
- description: "Auto-discover API endpoints for any URL. Navigates the page, captures network requests, generates YAML adapters.",
130
- inputSchema: {
131
- type: "object",
132
- properties: {
133
- url: { type: "string", description: "Website URL to explore" },
134
- goal: {
135
- type: "string",
136
- description: "Capability to find (e.g. 'search', 'hot', 'feed')",
137
- },
138
- },
139
- required: ["url"],
140
- },
141
- _meta: {
142
- "anthropic/searchHint": "Auto-discover API endpoints for any website URL. Generate YAML adapters for new sites.",
143
- },
144
- annotations: {
145
- readOnlyHint: false,
146
- destructiveHint: false,
147
- idempotentHint: false,
148
- openWorldHint: true,
149
- },
150
- },
151
- ];
152
- }
153
- const expandedRegistry = new Map();
154
- /**
155
- * Build the expanded tool set: 4 default meta-tools + one full tool per
156
- * adapter command. Clients see the complete Uni-CLI surface area.
157
- *
158
- * Token cost: ~160K for 956 commands. Use only when the client can handle it.
159
- */
160
- function buildExpandedTools() {
161
- const tools = [];
162
- tools.push(...buildDefaultTools());
163
- expandedRegistry.clear();
164
- const seen = new Set(DEFAULT_TOOL_NAMES);
165
- for (const adapter of getAllAdapters()) {
166
- for (const [cmdName, cmd] of Object.entries(adapter.commands)) {
167
- const rawDesc = cmd.description?.trim() ||
168
- adapter.description?.trim() ||
169
- `${cmdName} for ${adapter.name}`;
170
- const toolName = buildToolName(adapter.name, cmdName);
171
- if (seen.has(toolName)) {
172
- process.stderr.write(`unicli MCP: tool name collision: ${toolName} — shadowing ${adapter.name}/${cmdName}\n`);
173
- continue;
174
- }
175
- seen.add(toolName);
176
- expandedRegistry.set(toolName, { adapter, cmdName, cmd });
177
- tools.push({
178
- name: toolName,
179
- description: truncateDescription(`[${adapter.name}] ${rawDesc}`),
180
- inputSchema: buildInputSchema(cmd),
181
- outputSchema: buildOutputSchema(cmd),
182
- _meta: {
183
- "anthropic/searchHint": `${adapter.name}: ${rawDesc}`,
184
- },
185
- annotations: {
186
- readOnlyHint: true,
187
- idempotentHint: true,
188
- openWorldHint: true,
189
- },
190
- });
191
- }
192
- }
193
- return tools;
194
- }
195
- /**
196
- * Build deferred tool set: 4 default meta-tools with full schemas, plus
197
- * lightweight stubs for all adapter commands (name + searchHint only,
198
- * minimal inputSchema). Clients like Claude Code's ToolSearch can discover
199
- * tools by searchHint and then call them — the handler resolves the full
200
- * command at call time via the expandedRegistry.
201
- *
202
- * Token cost: ~8K (vs ~160K for expanded). 95% reduction.
203
- */
204
- function buildDeferredTools() {
205
- const tools = [];
206
- tools.push(...buildDefaultTools());
207
- expandedRegistry.clear();
208
- const seen = new Set(DEFAULT_TOOL_NAMES);
209
- for (const adapter of getAllAdapters()) {
210
- for (const [cmdName, cmd] of Object.entries(adapter.commands)) {
211
- const rawDesc = cmd.description?.trim() ||
212
- adapter.description?.trim() ||
213
- `${cmdName} for ${adapter.name}`;
214
- const toolName = buildToolName(adapter.name, cmdName);
215
- if (seen.has(toolName))
216
- continue;
217
- seen.add(toolName);
218
- // Register in the lookup table for runtime dispatch
219
- expandedRegistry.set(toolName, { adapter, cmdName, cmd });
220
- // Lightweight stub: name + searchHint + minimal schema.
221
- // Full inputSchema is resolved at call time via expandedRegistry.
222
- tools.push({
223
- name: toolName,
224
- description: truncateDescription(`[${adapter.name}] ${rawDesc}`),
225
- inputSchema: {
226
- type: "object",
227
- properties: {
228
- _args: {
229
- type: "object",
230
- description: "Command arguments (pass key-value pairs)",
231
- additionalProperties: true,
232
- },
233
- },
234
- },
235
- _meta: {
236
- "anthropic/searchHint": `${adapter.name}: ${rawDesc}`,
237
- },
238
- annotations: {
239
- readOnlyHint: true,
240
- idempotentHint: true,
241
- openWorldHint: true,
242
- },
243
- });
244
- }
245
- }
246
- return tools;
247
- }
248
- const DEFAULT_TOOL_NAMES = new Set([
249
- "unicli_run",
250
- "unicli_list",
251
- "unicli_search",
252
- "unicli_explore",
253
- "unicli_discover",
254
- ]);
255
- // ── Tool Handlers ───────────────────────────────────────────────────────────
256
- function handleListAdapters(params) {
257
- let commands = listCommands();
258
- const site = params.site;
259
- const type = params.type;
260
- if (site) {
261
- commands = commands.filter((c) => c.site.includes(site));
262
- }
263
- if (type) {
264
- commands = commands.filter((c) => c.type === type);
265
- }
266
- const adapters = getAllAdapters();
267
- const siteMap = new Map();
268
- for (const cmd of commands) {
269
- let entry = siteMap.get(cmd.site);
270
- if (!entry) {
271
- const adapter = adapters.find((a) => a.name === cmd.site);
272
- entry = { type: adapter?.type ?? cmd.type, commands: [] };
273
- siteMap.set(cmd.site, entry);
274
- }
275
- entry.commands.push({ name: cmd.command, description: cmd.description });
276
- }
277
- const result = Array.from(siteMap.entries()).map(([name, info]) => ({
278
- site: name,
279
- type: info.type,
280
- commands: info.commands,
281
- }));
282
- const data = {
283
- total_sites: result.length,
284
- total_commands: commands.length,
285
- adapters: result,
286
- };
287
- return {
288
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
289
- structuredContent: { type: "json", data },
290
- };
291
- }
292
- async function runResolvedCommand(adapter, cmd, cmdName, args) {
293
- // Merge default args
294
- const mergedArgs = { limit: 20, ...args };
295
- if (args.limit !== undefined) {
296
- mergedArgs.limit =
297
- typeof args.limit === "number"
298
- ? args.limit
299
- : parseInt(String(args.limit), 10) || 20;
300
- }
301
- try {
302
- let results;
303
- if (cmd.pipeline) {
304
- results = await runPipeline(cmd.pipeline, mergedArgs, adapter.base, {
305
- site: adapter.name,
306
- strategy: adapter.strategy,
307
- });
308
- }
309
- else if (cmd.func) {
310
- const raw = await cmd.func(null, mergedArgs);
311
- results = Array.isArray(raw) ? raw : [raw];
312
- }
313
- else {
314
- const errorData = {
315
- error: "No pipeline or function defined for this command",
316
- };
317
- return {
318
- content: [{ type: "text", text: JSON.stringify(errorData) }],
319
- structuredContent: { type: "json", data: errorData },
320
- isError: true,
321
- };
322
- }
323
- const data = { count: results.length, results };
324
- return {
325
- content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
326
- structuredContent: { type: "json", data },
327
- };
328
- }
329
- catch (err) {
330
- const message = err instanceof Error ? err.message : String(err);
331
- const errorData = {
332
- error: message,
333
- adapter_path: `src/adapters/${adapter.name}/${cmdName}.yaml`,
334
- suggestion: "The adapter may need updating. Check the YAML file.",
335
- };
336
- return {
337
- content: [{ type: "text", text: JSON.stringify(errorData, null, 2) }],
338
- structuredContent: { type: "json", data: errorData },
339
- isError: true,
340
- };
341
- }
342
- }
343
- /**
344
- * Annotate a tool result with `_meta.anthropic/maxResultSizeChars` when the
345
- * serialized payload exceeds MAX_RESULT_SIZE_CHARS (10 KB). This tells
346
- * Claude Code to accept large payloads without truncation.
347
- */
348
- export function annotateIfLarge(result) {
349
- const totalChars = result.content.reduce((sum, c) => sum + c.text.length, 0);
350
- if (totalChars > MAX_RESULT_SIZE_CHARS) {
351
- return {
352
- ...result,
353
- _meta: { "anthropic/maxResultSizeChars": 100_000 },
354
- };
355
- }
356
- return result;
357
- }
358
- async function handleRunCommand(params) {
359
- const site = params.site;
360
- const command = params.command;
361
- const args = params.args ?? {};
362
- if (!site || !command) {
363
- const errorData = { error: "site and command are required" };
364
- return {
365
- content: [{ type: "text", text: JSON.stringify(errorData) }],
366
- structuredContent: { type: "json", data: errorData },
367
- isError: true,
368
- };
369
- }
370
- const resolved = resolveCommand(site, command);
371
- if (!resolved) {
372
- const adapters = getAllAdapters();
373
- const matchingSites = adapters
374
- .filter((a) => a.name.includes(site))
375
- .map((a) => ({
376
- site: a.name,
377
- commands: Object.keys(a.commands),
378
- }));
379
- const errorData = {
380
- error: `Unknown command: ${site} ${command}`,
381
- suggestion: matchingSites.length > 0
382
- ? `Did you mean one of these? ${JSON.stringify(matchingSites)}`
383
- : "Use list_adapters to see all available commands.",
384
- };
385
- return {
386
- content: [{ type: "text", text: JSON.stringify(errorData, null, 2) }],
387
- structuredContent: { type: "json", data: errorData },
388
- isError: true,
389
- };
390
- }
391
- return runResolvedCommand(resolved.adapter, resolved.command, command, args);
392
- }
393
- /**
394
- * Expanded-tool dispatcher — parse `unicli_<site>_<command>` back to its
395
- * components and call the resolver. Returns `undefined` when the tool name
396
- * is not in expanded form, so the caller can fall through to default-tool
397
- * handling (list_adapters / run_command).
398
- */
399
- async function handleExpandedTool(toolName, args) {
400
- if (!toolName.startsWith("unicli_"))
401
- return undefined;
402
- // Dictionary lookup into the expansion registry built by
403
- // `buildExpandedTools`. This is the ONLY correct way to map a normalized
404
- // tool name back to its adapter + command because the normalization
405
- // (`s/[^a-zA-Z0-9_]/_/g`) is not reversible — a command file named
406
- // `capture-list.yaml` and another named `capture_list.yaml` would map
407
- // to the same tool name. The registry resolves the ambiguity deterministically
408
- // (first-write-wins, collisions logged to stderr in `buildExpandedTools`).
409
- const entry = expandedRegistry.get(toolName);
410
- if (!entry)
411
- return undefined;
412
- return runResolvedCommand(entry.adapter, entry.cmd, entry.cmdName, args);
413
- }
414
- // ── MCP Protocol Handler ────────────────────────────────────────────────────
415
- // Protocol version imported from src/constants.ts (single source of truth)
416
- const PROTOCOL_VERSION = MCP_PROTOCOL_VERSION;
29
+ import { getAllAdapters, listCommands } from "../registry.js";
30
+ import { VERSION } from "../constants.js";
31
+ import { buildDefaultTools, buildExpandedTools, buildDeferredTools, } from "./tools.js";
32
+ import { buildHandler, } from "./handler.js";
33
+ import { startHttp } from "./http-transport.js";
34
+ import { startStreamableHttp } from "./streamable-http/index.js";
35
+ export { annotateIfLarge } from "./dispatch.js";
417
36
  function parseArgs(argv) {
418
37
  const opts = {
419
38
  expanded: false,
@@ -445,223 +64,11 @@ function parseArgs(argv) {
445
64
  }
446
65
  return opts;
447
66
  }
448
- function buildHandler(tools) {
449
- return function handleRequest(req) {
450
- const id = req.id ?? null;
451
- switch (req.method) {
452
- case "initialize":
453
- return {
454
- jsonrpc: "2.0",
455
- id,
456
- result: {
457
- protocolVersion: PROTOCOL_VERSION,
458
- capabilities: {
459
- tools: { listChanged: false },
460
- elicitation: { supported: true },
461
- },
462
- serverInfo: {
463
- name: "unicli",
464
- version: VERSION,
465
- },
466
- },
467
- };
468
- case "notifications/initialized":
469
- // JSON-RPC notifications must not receive responses.
470
- // Returning a sentinel that transports check before serializing.
471
- return null;
472
- case "tools/list":
473
- return {
474
- jsonrpc: "2.0",
475
- id,
476
- result: { tools },
477
- };
478
- case "tools/call": {
479
- const params = req.params;
480
- if (!params?.name) {
481
- return {
482
- jsonrpc: "2.0",
483
- id,
484
- error: { code: -32602, message: "Missing tool name" },
485
- };
486
- }
487
- const toolArgs = params.arguments ?? {};
488
- switch (params.name) {
489
- // Support both old names (list_adapters, run_command) and new
490
- // names (unicli_list, unicli_run) for backwards compatibility.
491
- case "unicli_list":
492
- case "list_adapters": {
493
- const result = handleListAdapters(toolArgs);
494
- return { jsonrpc: "2.0", id, result: annotateIfLarge(result) };
495
- }
496
- case "unicli_run":
497
- case "run_command":
498
- return handleRunCommand(toolArgs).then((result) => ({
499
- jsonrpc: "2.0",
500
- id,
501
- result: annotateIfLarge(result),
502
- }));
503
- case "unicli_search": {
504
- const searchQuery = toolArgs.query;
505
- const searchLimit = toolArgs.limit || 5;
506
- if (!searchQuery) {
507
- return {
508
- jsonrpc: "2.0",
509
- id,
510
- error: {
511
- code: -32602,
512
- message: "Missing required parameter: query",
513
- },
514
- };
515
- }
516
- return import("../discovery/search.js").then(({ search: searchFn }) => {
517
- const results = searchFn(searchQuery, searchLimit);
518
- const data = {
519
- query: searchQuery,
520
- count: results.length,
521
- results: results.map((r) => ({
522
- command: `unicli ${r.site} ${r.command}`,
523
- site: r.site,
524
- name: r.command,
525
- description: r.description,
526
- score: r.score,
527
- category: r.category,
528
- usage: r.usage,
529
- })),
530
- };
531
- return {
532
- jsonrpc: "2.0",
533
- id,
534
- result: annotateIfLarge({
535
- content: [
536
- {
537
- type: "text",
538
- text: JSON.stringify(data, null, 2),
539
- },
540
- ],
541
- structuredContent: { type: "json", data },
542
- }),
543
- };
544
- });
545
- }
546
- case "unicli_explore":
547
- case "unicli_discover": {
548
- // unicli_explore is the canonical name (v0.211.1+).
549
- // unicli_discover kept as alias for backwards compatibility.
550
- const discoverUrl = toolArgs.url;
551
- const discoverGoal = toolArgs.goal;
552
- if (!discoverUrl) {
553
- return {
554
- jsonrpc: "2.0",
555
- id,
556
- error: {
557
- code: -32602,
558
- message: "Missing required parameter: url",
559
- },
560
- };
561
- }
562
- if (!discoverUrl.startsWith("http://") &&
563
- !discoverUrl.startsWith("https://")) {
564
- return {
565
- jsonrpc: "2.0",
566
- id,
567
- error: {
568
- code: -32602,
569
- message: "URL must start with http:// or https://",
570
- },
571
- };
572
- }
573
- return import("node:child_process").then(({ execFile: ef }) => import("node:util").then(({ promisify: prom }) => {
574
- const execFileP = prom(ef);
575
- const discoverArgs = ["generate", discoverUrl, "--json"];
576
- if (discoverGoal)
577
- discoverArgs.push("--goal", discoverGoal);
578
- return execFileP("unicli", discoverArgs, {
579
- timeout: 120_000,
580
- encoding: "utf-8",
581
- }).then(({ stdout }) => ({
582
- jsonrpc: "2.0",
583
- id,
584
- result: annotateIfLarge({
585
- content: [{ type: "text", text: stdout }],
586
- }),
587
- }), (err) => ({
588
- jsonrpc: "2.0",
589
- id,
590
- result: annotateIfLarge({
591
- content: [
592
- {
593
- type: "text",
594
- text: JSON.stringify({
595
- error: err instanceof Error ? err.message : String(err),
596
- }),
597
- },
598
- ],
599
- isError: true,
600
- }),
601
- }));
602
- }));
603
- }
604
- default:
605
- return handleExpandedTool(params.name, toolArgs).then((result) => {
606
- if (result)
607
- return { jsonrpc: "2.0", id, result: annotateIfLarge(result) };
608
- return {
609
- jsonrpc: "2.0",
610
- id,
611
- error: {
612
- code: -32602,
613
- message: `Unknown tool: ${params.name}. Use unicli_list to see available commands.`,
614
- },
615
- };
616
- });
617
- }
618
- }
619
- case "elicitation/response": {
620
- const elicitParams = req.params;
621
- if (elicitParams?.id == null || !elicitParams?.response) {
622
- return {
623
- jsonrpc: "2.0",
624
- id,
625
- error: {
626
- code: -32602,
627
- message: "Missing id or response in elicitation/response",
628
- },
629
- };
630
- }
631
- const resolved = resolveElicitation(elicitParams.id, elicitParams.response);
632
- return {
633
- jsonrpc: "2.0",
634
- id,
635
- result: { resolved },
636
- };
637
- }
638
- case "ping":
639
- return { jsonrpc: "2.0", id, result: {} };
640
- default:
641
- if (id !== null && id !== undefined) {
642
- return {
643
- jsonrpc: "2.0",
644
- id,
645
- error: {
646
- code: -32601,
647
- message: `Method not found: ${req.method}`,
648
- },
649
- };
650
- }
651
- return null;
652
- }
653
- };
654
- }
655
- // ── Stdio Transport ─────────────────────────────────────────────────────────
656
67
  function send(response) {
657
- const json = JSON.stringify(response);
658
- process.stdout.write(json + "\n");
68
+ process.stdout.write(JSON.stringify(response) + "\n");
659
69
  }
660
70
  async function startStdio(handler) {
661
- const rl = createInterface({
662
- input: process.stdin,
663
- terminal: false,
664
- });
71
+ const rl = createInterface({ input: process.stdin, terminal: false });
665
72
  rl.on("line", async (line) => {
666
73
  const trimmed = line.trim();
667
74
  if (!trimmed)
@@ -680,9 +87,8 @@ async function startStdio(handler) {
680
87
  }
681
88
  try {
682
89
  const response = await handler(req);
683
- if (response) {
90
+ if (response)
684
91
  send(response);
685
- }
686
92
  }
687
93
  catch (err) {
688
94
  const message = err instanceof Error ? err.message : String(err);
@@ -697,133 +103,14 @@ async function startStdio(handler) {
697
103
  process.exit(0);
698
104
  });
699
105
  }
700
- // ── HTTP Transport ──────────────────────────────────────────────────────────
701
- /**
702
- * Simple JSON-RPC over HTTP. POST /mcp accepts a single JSON-RPC envelope and
703
- * returns a single JSON response. GET /mcp returns server info — handy for
704
- * a health check from a browser.
705
- *
706
- * Note: this is intentionally NOT a full MCP Streamable HTTP transport —
707
- * no SSE event stream, no session resume. Most clients that "speak HTTP"
708
- * to MCP only need request/response, and starting with the simpler shape
709
- * means zero new dependencies and a tiny attack surface.
710
- */
711
- async function startHttp(handler, port, authEnabled = false) {
712
- const oauthMiddleware = authEnabled ? createOAuthMiddleware() : null;
713
- const server = createServer((req, res) => {
714
- // OAuth routes (authorize + token) — always public
715
- if (authEnabled && handleOAuthRoute(req, res))
716
- return;
717
- // Health endpoint — always public
718
- if (req.method === "GET" &&
719
- (req.url === "/" || req.url === "/mcp" || req.url === "/health")) {
720
- res.writeHead(200, { "Content-Type": "application/json" });
721
- const adapterCount = getAllAdapters().length;
722
- const commandCount = listCommands().length;
723
- // Compute actual expanded tool count: 3 default tools + all adapter commands.
724
- let expandedCount = 3; // default tools
725
- for (const adapter of getAllAdapters()) {
726
- expandedCount += Object.keys(adapter.commands).length;
727
- }
728
- res.end(JSON.stringify({
729
- status: "ok",
730
- adapters: adapterCount,
731
- commands: commandCount,
732
- tools: { default: 3, expanded: expandedCount },
733
- version: VERSION,
734
- }));
735
- return;
736
- }
737
- if (req.method !== "POST" || req.url !== "/mcp") {
738
- res.writeHead(404, { "Content-Type": "application/json" });
739
- res.end(JSON.stringify({ error: "POST /mcp" }));
740
- return;
741
- }
742
- // OAuth middleware — block unauthenticated requests when --auth is set
743
- if (oauthMiddleware?.(req, res))
744
- return;
745
- const MAX_BODY = 1_048_576; // 1 MB
746
- const chunks = [];
747
- let bodySize = 0;
748
- let aborted = false;
749
- req.on("data", (chunk) => {
750
- bodySize += chunk.length;
751
- if (bodySize > MAX_BODY) {
752
- aborted = true;
753
- res.writeHead(413, { "Content-Type": "application/json" });
754
- res.end(JSON.stringify({
755
- jsonrpc: "2.0",
756
- id: null,
757
- error: { code: -32600, message: "Request too large" },
758
- }));
759
- req.destroy();
760
- return;
761
- }
762
- chunks.push(chunk);
763
- });
764
- req.on("end", async () => {
765
- if (aborted)
766
- return;
767
- const body = Buffer.concat(chunks).toString("utf-8");
768
- let parsed;
769
- try {
770
- parsed = JSON.parse(body);
771
- }
772
- catch {
773
- res.writeHead(400, { "Content-Type": "application/json" });
774
- res.end(JSON.stringify({
775
- jsonrpc: "2.0",
776
- id: null,
777
- error: { code: -32700, message: "Parse error" },
778
- }));
779
- return;
780
- }
781
- try {
782
- const response = await handler(parsed);
783
- if (!response) {
784
- // JSON-RPC notification — no response expected
785
- res.writeHead(204);
786
- res.end();
787
- return;
788
- }
789
- res.writeHead(200, { "Content-Type": "application/json" });
790
- res.end(JSON.stringify(response));
791
- }
792
- catch (err) {
793
- const message = err instanceof Error ? err.message : String(err);
794
- res.writeHead(500, { "Content-Type": "application/json" });
795
- res.end(JSON.stringify({
796
- jsonrpc: "2.0",
797
- id: parsed.id ?? null,
798
- error: { code: -32603, message: `Internal error: ${message}` },
799
- }));
800
- }
801
- });
802
- });
803
- await new Promise((resolve, reject) => {
804
- server.once("error", reject);
805
- server.listen(port, "127.0.0.1", () => {
806
- server.off("error", reject);
807
- resolve();
808
- });
809
- });
810
- process.stderr.write(`unicli MCP server v${VERSION} — HTTP transport on http://127.0.0.1:${port}/mcp\n`);
811
- }
812
- // ── main ────────────────────────────────────────────────────────────────────
813
106
  async function main() {
814
107
  const opts = parseArgs(process.argv.slice(2));
815
- // Load adapters (same as CLI)
816
108
  loadAllAdapters();
817
109
  await loadTsAdapters();
818
- // Three modes:
819
- // default → 4 meta-tools (~200 tokens)
820
- // expanded → 4 meta-tools + 956 full tool schemas (~160K tokens)
821
- // deferred → 4 meta-tools + 956 lightweight stubs (~8K tokens)
822
110
  const mode = opts.expanded ? "expanded" : "default";
823
111
  const tools = opts.expanded ? buildExpandedTools() : buildDefaultTools();
824
- // Deferred mode is auto-activated for Streamable HTTP transport (remote
825
- // clients benefit most from searchHint-based discovery).
826
- // For explicit control, the expanded flag takes precedence.
112
+ // Streamable HTTP auto-activates deferred mode remote clients benefit
113
+ // most from searchHint-based discovery.
827
114
  if (opts.transport === "streamable" && !opts.expanded) {
828
115
  const deferredTools = buildDeferredTools();
829
116
  tools.length = 0;
@@ -839,12 +126,14 @@ async function main() {
839
126
  return;
840
127
  }
841
128
  if (opts.transport === "streamable") {
129
+ // v0.213.3 P3: streamable-http.Handler now returns
130
+ // `Promise<JsonRpcResponse | undefined>`, so the pre-P3 cast-adapt
131
+ // is gone — the types match the `undefined`-for-notification contract.
842
132
  await startStreamableHttp(opts.port, handler, { auth: opts.auth });
843
133
  const authLabel = opts.auth ? ", OAuth enabled" : "";
844
134
  process.stderr.write(`unicli MCP server v${VERSION} — ${adapterCount} sites, ${commandCount} commands (${tools.length} tools, mode=${mode}, transport=streamable${authLabel})\n`);
845
135
  return;
846
136
  }
847
- // stdio (default)
848
137
  await startStdio(handler);
849
138
  process.stderr.write(`unicli MCP server v${VERSION} — ${adapterCount} sites, ${commandCount} commands (${tools.length} tools registered, mode=${mode})\n`);
850
139
  }