@zenalexa/unicli 0.209.0 → 0.211.2
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/AGENTS.md +75 -20
- package/README.md +832 -285
- package/dist/adapters/cursor/cursor.d.ts +2 -1
- package/dist/adapters/cursor/cursor.d.ts.map +1 -1
- package/dist/adapters/cursor/cursor.js +87 -1
- package/dist/adapters/cursor/cursor.js.map +1 -1
- package/dist/adapters/discord-app/discord-app.d.ts +1 -1
- package/dist/adapters/discord-app/discord-app.js +94 -1
- package/dist/adapters/discord-app/discord-app.js.map +1 -1
- package/dist/browser/bridge.d.ts +45 -0
- package/dist/browser/bridge.d.ts.map +1 -1
- package/dist/browser/bridge.js +94 -1
- package/dist/browser/bridge.js.map +1 -1
- package/dist/browser/cdp-client.d.ts +24 -1
- package/dist/browser/cdp-client.d.ts.map +1 -1
- package/dist/browser/cdp-client.js +56 -2
- package/dist/browser/cdp-client.js.map +1 -1
- package/dist/browser/launcher.d.ts +5 -0
- package/dist/browser/launcher.d.ts.map +1 -1
- package/dist/browser/launcher.js +7 -0
- package/dist/browser/launcher.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +72 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/agents.d.ts +14 -3
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +369 -140
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/browser.d.ts.map +1 -1
- package/dist/commands/browser.js +79 -1
- package/dist/commands/browser.js.map +1 -1
- package/dist/commands/ext.d.ts +11 -0
- package/dist/commands/ext.d.ts.map +1 -0
- package/dist/commands/ext.js +122 -0
- package/dist/commands/ext.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -4
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +47 -62
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/schema.d.ts +12 -0
- package/dist/commands/schema.d.ts.map +1 -0
- package/dist/commands/schema.js +72 -0
- package/dist/commands/schema.js.map +1 -0
- package/dist/commands/search.d.ts +12 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +47 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/skills.d.ts +2 -3
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +2 -3
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/status.d.ts +12 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +137 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/discovery/aliases.d.ts +31 -0
- package/dist/discovery/aliases.d.ts.map +1 -0
- package/dist/discovery/aliases.js +477 -0
- package/dist/discovery/aliases.js.map +1 -0
- package/dist/discovery/loader.d.ts.map +1 -1
- package/dist/discovery/loader.js +25 -0
- package/dist/discovery/loader.js.map +1 -1
- package/dist/discovery/search.d.ts +73 -0
- package/dist/discovery/search.d.ts.map +1 -0
- package/dist/discovery/search.js +355 -0
- package/dist/discovery/search.js.map +1 -0
- package/dist/engine/yaml-runner.d.ts +10 -0
- package/dist/engine/yaml-runner.d.ts.map +1 -1
- package/dist/engine/yaml-runner.js +51 -3
- package/dist/engine/yaml-runner.js.map +1 -1
- package/dist/hub/index.d.ts +37 -0
- package/dist/hub/index.d.ts.map +1 -0
- package/dist/hub/index.js +98 -0
- package/dist/hub/index.js.map +1 -0
- package/dist/hub/passthrough.d.ts +21 -0
- package/dist/hub/passthrough.d.ts.map +1 -0
- package/dist/hub/passthrough.js +71 -0
- package/dist/hub/passthrough.js.map +1 -0
- package/dist/manifest-compact.txt +15 -0
- package/dist/manifest-search.json +1 -0
- package/dist/manifest.json +2163 -268
- package/dist/mcp/oauth.d.ts +33 -0
- package/dist/mcp/oauth.d.ts.map +1 -0
- package/dist/mcp/oauth.js +220 -0
- package/dist/mcp/oauth.js.map +1 -0
- package/dist/mcp/schema.d.ts +65 -0
- package/dist/mcp/schema.d.ts.map +1 -0
- package/dist/mcp/schema.js +136 -0
- package/dist/mcp/schema.js.map +1 -0
- package/dist/mcp/server.d.ts +23 -10
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +350 -182
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/sse-transport.d.ts +34 -0
- package/dist/mcp/sse-transport.d.ts.map +1 -0
- package/dist/mcp/sse-transport.js +182 -0
- package/dist/mcp/sse-transport.js.map +1 -0
- package/dist/mcp/streamable-http.d.ts +64 -0
- package/dist/mcp/streamable-http.d.ts.map +1 -0
- package/dist/mcp/streamable-http.js +312 -0
- package/dist/mcp/streamable-http.js.map +1 -0
- package/dist/permissions/sensitive-paths.js +2 -2
- package/dist/permissions/sensitive-paths.js.map +1 -1
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +8 -7
- package/src/adapters/1688/_site.json +9 -0
- package/src/adapters/36kr/latest.yaml +36 -0
- package/src/adapters/arxiv/trending.yaml +37 -0
- package/src/adapters/baidu/hot.yaml +37 -0
- package/src/adapters/baidu/search.yaml +41 -0
- package/src/adapters/barchart/_site.json +10 -0
- package/src/adapters/bbc/technology.yaml +26 -0
- package/src/adapters/bbc/top.yaml +26 -0
- package/src/adapters/bbc/world.yaml +26 -0
- package/src/adapters/bilibili/coin.yaml +33 -0
- package/src/adapters/bilibili/later.yaml +31 -0
- package/src/adapters/bilibili/live.yaml +41 -0
- package/src/adapters/bilibili/trending.yaml +29 -0
- package/src/adapters/binance/hot.yaml +39 -0
- package/src/adapters/binance/kline.yaml +35 -0
- package/src/adapters/binance/ticker.yaml +33 -0
- package/src/adapters/bluesky/likes.yaml +35 -0
- package/src/adapters/bluesky/notifications.yaml +30 -0
- package/src/adapters/bluesky/post.yaml +25 -0
- package/src/adapters/chrome/bookmarks.yaml +26 -0
- package/src/adapters/chrome/tabs.yaml +25 -0
- package/src/adapters/cnki/search.yaml +40 -0
- package/src/adapters/cnn/technology.yaml +26 -0
- package/src/adapters/cocoapods/info.yaml +33 -0
- package/src/adapters/coinbase/prices.yaml +27 -0
- package/src/adapters/coinbase/rates.yaml +24 -0
- package/src/adapters/coupang/hot.yaml +29 -0
- package/src/adapters/crates-io/info.yaml +29 -0
- package/src/adapters/crates-io/versions.yaml +33 -0
- package/src/adapters/ctrip/hot.yaml +28 -0
- package/src/adapters/cursor/cursor.ts +91 -1
- package/src/adapters/dangdang/hot.yaml +38 -0
- package/src/adapters/dangdang/search.yaml +43 -0
- package/src/adapters/deepseek/chat.yaml +33 -0
- package/src/adapters/deepseek/models.yaml +18 -0
- package/src/adapters/devto/latest.yaml +29 -0
- package/src/adapters/devto/search.yaml +34 -0
- package/src/adapters/dianping/hot.yaml +43 -0
- package/src/adapters/dianping/search.yaml +48 -0
- package/src/adapters/discord-app/discord-app.ts +99 -1
- package/src/adapters/docker/networks.yaml +20 -0
- package/src/adapters/docker/volumes.yaml +19 -0
- package/src/adapters/docker-hub/info.yaml +26 -0
- package/src/adapters/docker-hub/tags.yaml +34 -0
- package/src/adapters/douban/group-hot.yaml +29 -0
- package/src/adapters/douban/new-movies.yaml +32 -0
- package/src/adapters/douban/tv-hot.yaml +32 -0
- package/src/adapters/doubao/ask.yaml +64 -0
- package/src/adapters/doubao/new.yaml +18 -0
- package/src/adapters/doubao/status.yaml +21 -0
- package/src/adapters/douyu/hot.yaml +34 -0
- package/src/adapters/douyu/search.yaml +42 -0
- package/src/adapters/eastmoney/fund.yaml +36 -0
- package/src/adapters/eastmoney/market.yaml +26 -0
- package/src/adapters/ele/hot.yaml +40 -0
- package/src/adapters/ele/search.yaml +47 -0
- package/src/adapters/exchangerate/list.yaml +25 -0
- package/src/adapters/facebook/marketplace.yaml +43 -0
- package/src/adapters/facebook/post.yaml +29 -0
- package/src/adapters/futu/hot.yaml +49 -0
- package/src/adapters/futu/quote.yaml +46 -0
- package/src/adapters/gitee/repos.yaml +34 -0
- package/src/adapters/github-trending/developers.yaml +33 -0
- package/src/adapters/github-trending/weekly.yaml +34 -0
- package/src/adapters/gitlab/projects.yaml +34 -0
- package/src/adapters/godot/scene-export.yaml +1 -1
- package/src/adapters/hackernews/comments.yaml +36 -0
- package/src/adapters/hackernews/item.yaml +28 -0
- package/src/adapters/hf/datasets.yaml +34 -0
- package/src/adapters/hf/spaces.yaml +34 -0
- package/src/adapters/homebrew/search.yaml +27 -0
- package/src/adapters/huggingface-papers/search.yaml +33 -0
- package/src/adapters/imdb/box-office.yaml +28 -0
- package/src/adapters/infoq/latest.yaml +26 -0
- package/src/adapters/instagram/activity.yaml +45 -0
- package/src/adapters/instagram/highlights.yaml +48 -0
- package/src/adapters/instagram/reels-trending.yaml +51 -0
- package/src/adapters/instagram/reels.yaml +34 -0
- package/src/adapters/instagram/stories.yaml +29 -0
- package/src/adapters/instagram/suggested.yaml +40 -0
- package/src/adapters/instagram/tags.yaml +52 -0
- package/src/adapters/itch-io/search.yaml +35 -0
- package/src/adapters/itch-io/top.yaml +31 -0
- package/src/adapters/ithome/hot.yaml +27 -0
- package/src/adapters/ithome/latest.yaml +26 -0
- package/src/adapters/jd/_site.json +9 -0
- package/src/adapters/jianyu/search.yaml +42 -0
- package/src/adapters/ke/ershoufang.yaml +75 -0
- package/src/adapters/ke/xiaoqu.yaml +73 -0
- package/src/adapters/kuaishou/hot.yaml +39 -0
- package/src/adapters/kuaishou/search.yaml +44 -0
- package/src/adapters/linkedin/_site.json +10 -0
- package/src/adapters/linkedin/jobs.yaml +47 -0
- package/src/adapters/lobsters/search.yaml +35 -0
- package/src/adapters/macos/active-app.yaml +16 -0
- package/src/adapters/macos/apps-list.yaml +14 -0
- package/src/adapters/macos/apps.yaml +31 -0
- package/src/adapters/macos/battery.yaml +26 -0
- package/src/adapters/macos/bluetooth.yaml +14 -0
- package/src/adapters/macos/brightness.yaml +22 -0
- package/src/adapters/macos/caffeinate.yaml +25 -0
- package/src/adapters/macos/calendar-create.yaml +81 -0
- package/src/adapters/macos/calendar-list.yaml +53 -0
- package/src/adapters/macos/calendar-today.yaml +30 -0
- package/src/adapters/macos/clipboard.yaml +23 -0
- package/src/adapters/macos/contacts-search.yaml +59 -0
- package/src/adapters/macos/dark-mode.yaml +19 -0
- package/src/adapters/macos/disk-info.yaml +31 -0
- package/src/adapters/macos/disk-usage.yaml +14 -0
- package/src/adapters/macos/do-not-disturb.yaml +21 -0
- package/src/adapters/macos/empty-trash.yaml +16 -0
- package/src/adapters/macos/finder-copy.yaml +40 -0
- package/src/adapters/macos/finder-move.yaml +40 -0
- package/src/adapters/macos/finder-new-folder.yaml +36 -0
- package/src/adapters/macos/finder-recent.yaml +24 -0
- package/src/adapters/macos/finder-selection.yaml +22 -0
- package/src/adapters/macos/finder-tags.yaml +40 -0
- package/src/adapters/macos/lock-screen.yaml +16 -0
- package/src/adapters/macos/mail-send.yaml +54 -0
- package/src/adapters/macos/mail-status.yaml +38 -0
- package/src/adapters/macos/messages-send.yaml +42 -0
- package/src/adapters/macos/music-control.yaml +43 -0
- package/src/adapters/macos/music-now.yaml +34 -0
- package/src/adapters/macos/notes-list.yaml +30 -0
- package/src/adapters/macos/notes-search.yaml +45 -0
- package/src/adapters/macos/notification.yaml +27 -0
- package/src/adapters/macos/notify.yaml +46 -0
- package/src/adapters/macos/open-app.yaml +21 -0
- package/src/adapters/macos/open.yaml +24 -0
- package/src/adapters/macos/photos-search.yaml +44 -0
- package/src/adapters/macos/processes.yaml +14 -0
- package/src/adapters/macos/reminder-create.yaml +75 -0
- package/src/adapters/macos/reminders-complete.yaml +33 -0
- package/src/adapters/macos/reminders-list.yaml +28 -0
- package/src/adapters/macos/safari-history.yaml +23 -0
- package/src/adapters/macos/safari-tabs.yaml +26 -0
- package/src/adapters/macos/safari-url.yaml +22 -0
- package/src/adapters/macos/say.yaml +36 -0
- package/src/adapters/macos/screen-lock.yaml +16 -0
- package/src/adapters/macos/screen-recording.yaml +32 -0
- package/src/adapters/macos/screenshot.yaml +34 -0
- package/src/adapters/macos/shortcuts-list.yaml +14 -0
- package/src/adapters/macos/shortcuts-run.yaml +28 -0
- package/src/adapters/macos/sleep.yaml +14 -0
- package/src/adapters/macos/spotlight.yaml +43 -0
- package/src/adapters/macos/system-info.yaml +32 -0
- package/src/adapters/macos/trash.yaml +32 -0
- package/src/adapters/macos/uptime.yaml +14 -0
- package/src/adapters/macos/volume.yaml +19 -0
- package/src/adapters/macos/wallpaper.yaml +33 -0
- package/src/adapters/macos/wifi-info.yaml +14 -0
- package/src/adapters/macos/wifi.yaml +63 -0
- package/src/adapters/maimai/search.yaml +62 -0
- package/src/adapters/maoyan/hot.yaml +33 -0
- package/src/adapters/maoyan/search.yaml +40 -0
- package/src/adapters/mastodon/timeline.yaml +33 -0
- package/src/adapters/mastodon/user.yaml +32 -0
- package/src/adapters/medium/article.yaml +22 -0
- package/src/adapters/medium/trending.yaml +30 -0
- package/src/adapters/meituan/hot.yaml +26 -0
- package/src/adapters/mubu/list.yaml +36 -0
- package/src/adapters/mubu/search.yaml +54 -0
- package/src/adapters/netease-music/playlist.yaml +34 -0
- package/src/adapters/netease-music/top.yaml +33 -0
- package/src/adapters/notion/databases.yaml +41 -0
- package/src/adapters/notion/pages.yaml +44 -0
- package/src/adapters/notion/search.yaml +41 -0
- package/src/adapters/npm/downloads.yaml +28 -0
- package/src/adapters/npm/info.yaml +25 -0
- package/src/adapters/npm/search.yaml +35 -0
- package/src/adapters/npm/versions.yaml +33 -0
- package/src/adapters/npm-trends/trending.yaml +17 -0
- package/src/adapters/nytimes/search.yaml +35 -0
- package/src/adapters/obsidian/daily.yaml +23 -0
- package/src/adapters/obsidian/open.yaml +26 -0
- package/src/adapters/obsidian/search.yaml +28 -0
- package/src/adapters/ollama/generate.yaml +33 -0
- package/src/adapters/ollama/models.yaml +29 -0
- package/src/adapters/ollama/ps.yaml +20 -0
- package/src/adapters/openrouter/search.yaml +31 -0
- package/src/adapters/perplexity/ask.yaml +33 -0
- package/src/adapters/pexels/curated.yaml +31 -0
- package/src/adapters/pinduoduo/search.yaml +36 -0
- package/src/adapters/producthunt/search.yaml +41 -0
- package/src/adapters/pypi/search.yaml +37 -0
- package/src/adapters/pypi/versions.yaml +31 -0
- package/src/adapters/quark/ls.yaml +39 -0
- package/src/adapters/quark/search.yaml +39 -0
- package/src/adapters/qweather/forecast.yaml +37 -0
- package/src/adapters/reddit/new.yaml +34 -0
- package/src/adapters/reddit/rising.yaml +33 -0
- package/src/adapters/reddit/top.yaml +40 -0
- package/src/adapters/reddit/trending.yaml +25 -0
- package/src/adapters/replicate/run.yaml +39 -0
- package/src/adapters/reuters/_site.json +9 -0
- package/src/adapters/reuters/latest.yaml +26 -0
- package/src/adapters/sinablog/_site.json +9 -0
- package/src/adapters/sinafinance/market.yaml +21 -0
- package/src/adapters/slack/messages.yaml +41 -0
- package/src/adapters/slack/post.yaml +32 -0
- package/src/adapters/slack/search.yaml +28 -0
- package/src/adapters/slack/send.yaml +35 -0
- package/src/adapters/slack/status.yaml +19 -0
- package/src/adapters/slack/users.yaml +37 -0
- package/src/adapters/slock/servers.yaml +32 -0
- package/src/adapters/smzdm/_site.json +9 -0
- package/src/adapters/spotify/now-playing.yaml +28 -0
- package/src/adapters/spotify/playlists.yaml +37 -0
- package/src/adapters/spotify/top-tracks.yaml +42 -0
- package/src/adapters/stackoverflow/question.yaml +34 -0
- package/src/adapters/stackoverflow/tags.yaml +30 -0
- package/src/adapters/steam/app-details.yaml +32 -0
- package/src/adapters/steam/new-releases.yaml +28 -0
- package/src/adapters/steam/search.yaml +36 -0
- package/src/adapters/steam/specials.yaml +29 -0
- package/src/adapters/steam/wishlist.yaml +36 -0
- package/src/adapters/substack/trending.yaml +28 -0
- package/src/adapters/taobao/hot.yaml +31 -0
- package/src/adapters/taobao/search.yaml +44 -0
- package/src/adapters/techcrunch/search.yaml +33 -0
- package/src/adapters/theverge/search.yaml +33 -0
- package/src/adapters/threads/hot.yaml +44 -0
- package/src/adapters/threads/search.yaml +47 -0
- package/src/adapters/tiktok/trending.yaml +28 -0
- package/src/adapters/toutiao/hot.yaml +31 -0
- package/src/adapters/toutiao/search.yaml +47 -0
- package/src/adapters/twitch/games.yaml +31 -0
- package/src/adapters/twitch/search.yaml +39 -0
- package/src/adapters/twitch/streams.yaml +38 -0
- package/src/adapters/twitter/lists.yaml +49 -0
- package/src/adapters/twitter/media.yaml +48 -0
- package/src/adapters/twitter/mentions.yaml +38 -0
- package/src/adapters/twitter/mute.yaml +42 -0
- package/src/adapters/twitter/pin.yaml +43 -0
- package/src/adapters/twitter/quotes.yaml +43 -0
- package/src/adapters/twitter/retweets.yaml +44 -0
- package/src/adapters/twitter/spaces.yaml +43 -0
- package/src/adapters/twitter/unmute.yaml +42 -0
- package/src/adapters/unsplash/random.yaml +27 -0
- package/src/adapters/v2ex/search.yaml +36 -0
- package/src/adapters/vscode/extensions.yaml +23 -0
- package/src/adapters/vscode/install-ext.yaml +26 -0
- package/src/adapters/vscode/open.yaml +25 -0
- package/src/adapters/wechat-channels/hot.yaml +39 -0
- package/src/adapters/wechat-channels/search.yaml +57 -0
- package/src/adapters/weibo/trending.yaml +28 -0
- package/src/adapters/weixin/_site.json +9 -0
- package/src/adapters/weixin/hot.yaml +22 -0
- package/src/adapters/wikipedia/today.yaml +25 -0
- package/src/adapters/xiaohongshu/follow.yaml +35 -0
- package/src/adapters/xiaohongshu/hashtag.yaml +44 -0
- package/src/adapters/xiaohongshu/hot.yaml +34 -0
- package/src/adapters/xiaohongshu/like.yaml +27 -0
- package/src/adapters/xiaohongshu/profile.yaml +39 -0
- package/src/adapters/xiaohongshu/save.yaml +27 -0
- package/src/adapters/xiaohongshu/suggest.yaml +34 -0
- package/src/adapters/xiaohongshu/trending.yaml +35 -0
- package/src/adapters/xiaohongshu/unfollow.yaml +40 -0
- package/src/adapters/xueqiu/market.yaml +25 -0
- package/src/adapters/yahoo-finance/quote.yaml +35 -0
- package/src/adapters/yahoo-finance/search.yaml +42 -0
- package/src/adapters/yahoo-finance/trending.yaml +30 -0
- package/src/adapters/youtube/playlist.yaml +43 -0
- package/src/adapters/youtube/shorts.yaml +42 -0
- package/src/adapters/youtube/trending.yaml +41 -0
- package/src/adapters/zhihu/answer.yaml +29 -0
- package/src/adapters/zhihu/answers.yaml +38 -0
- package/src/adapters/zhihu/article.yaml +27 -0
- package/src/adapters/zhihu/articles.yaml +38 -0
- package/src/adapters/zhihu/collections.yaml +37 -0
- package/src/adapters/zhihu/columns.yaml +38 -0
- package/src/adapters/zhihu/comment.yaml +42 -0
- package/src/adapters/zhihu/followers.yaml +37 -0
- package/src/adapters/zhihu/following.yaml +37 -0
- package/src/adapters/zhihu/pins.yaml +37 -0
- package/src/adapters/zhihu/topic.yaml +35 -0
- package/src/adapters/zhihu/topics.yaml +37 -0
- package/src/adapters/zhihu/trending.yaml +36 -0
- package/src/adapters/zhihu/user.yaml +30 -0
- package/src/adapters/zoom/join.yaml +24 -0
- package/src/adapters/zoom/start.yaml +17 -0
- package/src/hub/external-clis.yaml +343 -0
- package/src/hub/index.ts +128 -0
- package/src/hub/passthrough.ts +85 -0
- package/src/adapters/1688/manifest.yaml +0 -7
- package/src/adapters/barchart/manifest.yaml +0 -8
- package/src/adapters/jd/manifest.yaml +0 -7
- package/src/adapters/linkedin/manifest.yaml +0 -8
- package/src/adapters/reuters/manifest.yaml +0 -7
- package/src/adapters/sinablog/manifest.yaml +0 -7
- package/src/adapters/smzdm/manifest.yaml +0 -7
- package/src/adapters/weixin/manifest.yaml +0 -7
package/dist/mcp/server.js
CHANGED
|
@@ -3,20 +3,19 @@
|
|
|
3
3
|
* MCP (Model Context Protocol) server for Uni-CLI.
|
|
4
4
|
*
|
|
5
5
|
* Two registration modes:
|
|
6
|
-
* 1. **
|
|
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
|
|
7
9
|
* (`unicli_<site>_<command>`) with JSON Schema derived from `args` +
|
|
8
|
-
* `columns`.
|
|
9
|
-
* MCP clients see the full Uni-CLI surface area without an extra
|
|
10
|
-
* list_adapters → run_command roundtrip.
|
|
11
|
-
* 2. **Lazy (`--lazy`)** — only `list_adapters` + `run_command` are
|
|
12
|
-
* registered. Useful when an MCP client has a hard tool-count limit
|
|
13
|
-
* or wants the smallest possible handshake.
|
|
10
|
+
* `columns`. MCP clients see the full Uni-CLI surface area.
|
|
14
11
|
*
|
|
15
|
-
*
|
|
12
|
+
* Three transports:
|
|
16
13
|
* - **stdio (default)** — newline-delimited JSON over stdin/stdout
|
|
17
14
|
* - **http (`--transport http [--port 19826]`)** — POST /mcp accepts a
|
|
18
|
-
* single JSON-RPC envelope and returns a single JSON response.
|
|
19
|
-
*
|
|
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.
|
|
20
19
|
*
|
|
21
20
|
* Auth pass-through is automatic: every adapter the CLI loads (including
|
|
22
21
|
* cookie-based ones) is exposed by name; the runtime resolves cookies on
|
|
@@ -28,174 +27,143 @@ import { loadAllAdapters, loadTsAdapters } from "../discovery/loader.js";
|
|
|
28
27
|
import { getAllAdapters, listCommands, resolveCommand } from "../registry.js";
|
|
29
28
|
import { runPipeline } from "../engine/yaml-runner.js";
|
|
30
29
|
import { VERSION } from "../constants.js";
|
|
31
|
-
//
|
|
32
|
-
|
|
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
|
+
// ── Smart default tools (4 meta-tools — the default mode) ─────────────────
|
|
36
|
+
const MAX_RESULT_SIZE_CHARS = 10_000;
|
|
37
|
+
function buildDefaultTools() {
|
|
33
38
|
return [
|
|
34
39
|
{
|
|
35
|
-
name: "
|
|
36
|
-
description: "
|
|
37
|
-
"Use this to discover what sites and commands are available before calling run_command.",
|
|
40
|
+
name: "unicli_run",
|
|
41
|
+
description: "Execute any Uni-CLI command. Returns JSON results.",
|
|
38
42
|
inputSchema: {
|
|
39
43
|
type: "object",
|
|
40
44
|
properties: {
|
|
41
45
|
site: {
|
|
42
46
|
type: "string",
|
|
43
|
-
description: "
|
|
47
|
+
description: "Site name (e.g. hackernews, github, bilibili)",
|
|
44
48
|
},
|
|
45
|
-
|
|
49
|
+
command: {
|
|
46
50
|
type: "string",
|
|
47
|
-
description: "
|
|
48
|
-
|
|
51
|
+
description: "Command to run (e.g. top, search, hot)",
|
|
52
|
+
},
|
|
53
|
+
args: {
|
|
54
|
+
type: "object",
|
|
55
|
+
description: 'Key-value arguments (e.g. {"query": "ai", "limit": 10})',
|
|
56
|
+
additionalProperties: true,
|
|
49
57
|
},
|
|
50
58
|
},
|
|
59
|
+
required: ["site", "command"],
|
|
60
|
+
},
|
|
61
|
+
_meta: {
|
|
62
|
+
"anthropic/searchHint": "Execute CLI commands on 200+ websites and desktop apps. Run adapters by site and command name.",
|
|
63
|
+
},
|
|
64
|
+
annotations: {
|
|
65
|
+
readOnlyHint: false,
|
|
66
|
+
destructiveHint: false,
|
|
67
|
+
idempotentHint: false,
|
|
68
|
+
openWorldHint: true,
|
|
51
69
|
},
|
|
52
70
|
},
|
|
53
71
|
{
|
|
54
|
-
name: "
|
|
55
|
-
description: "
|
|
56
|
-
"Returns JSON results. Use list_adapters first to discover available commands.",
|
|
72
|
+
name: "unicli_list",
|
|
73
|
+
description: "List available commands. Filter by site or adapter type.",
|
|
57
74
|
inputSchema: {
|
|
58
75
|
type: "object",
|
|
59
76
|
properties: {
|
|
60
77
|
site: {
|
|
61
78
|
type: "string",
|
|
62
|
-
description: "
|
|
79
|
+
description: "Filter by site name (partial match)",
|
|
63
80
|
},
|
|
64
|
-
|
|
81
|
+
type: {
|
|
65
82
|
type: "string",
|
|
66
|
-
description: "
|
|
67
|
-
|
|
68
|
-
args: {
|
|
69
|
-
type: "object",
|
|
70
|
-
description: 'Command arguments as key-value pairs (e.g. {"query": "ai", "limit": 10})',
|
|
71
|
-
additionalProperties: true,
|
|
83
|
+
description: "Filter by adapter type",
|
|
84
|
+
enum: ["web-api", "desktop", "browser", "bridge", "service"],
|
|
72
85
|
},
|
|
73
86
|
},
|
|
74
|
-
|
|
87
|
+
},
|
|
88
|
+
_meta: {
|
|
89
|
+
"anthropic/searchHint": "Browse available Uni-CLI sites and commands. Filter by site name or adapter type.",
|
|
90
|
+
"anthropic/alwaysLoad": true,
|
|
91
|
+
},
|
|
92
|
+
annotations: {
|
|
93
|
+
readOnlyHint: true,
|
|
94
|
+
idempotentHint: true,
|
|
95
|
+
openWorldHint: false,
|
|
75
96
|
},
|
|
76
97
|
},
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
description: "Cap result count (default 20)",
|
|
105
|
-
default: 20,
|
|
98
|
+
{
|
|
99
|
+
name: "unicli_search",
|
|
100
|
+
description: "Search 200+ sites and 956 commands by intent. Bilingual (EN/ZH). Returns top matches with usage examples.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {
|
|
104
|
+
query: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Natural language intent (e.g. 'download video', '推特热门', 'stock price')",
|
|
107
|
+
},
|
|
108
|
+
limit: {
|
|
109
|
+
type: "integer",
|
|
110
|
+
description: "Max results (default 5)",
|
|
111
|
+
default: 5,
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
required: ["query"],
|
|
115
|
+
},
|
|
116
|
+
_meta: {
|
|
117
|
+
"anthropic/searchHint": "Find CLI commands by intent. Semantic search across websites, desktop apps, macOS. Bilingual Chinese/English.",
|
|
118
|
+
"anthropic/alwaysLoad": true,
|
|
119
|
+
},
|
|
120
|
+
annotations: {
|
|
121
|
+
readOnlyHint: true,
|
|
122
|
+
idempotentHint: true,
|
|
123
|
+
openWorldHint: false,
|
|
124
|
+
},
|
|
106
125
|
},
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (a.choices)
|
|
119
|
-
prop.enum = a.choices;
|
|
120
|
-
props[a.name] = prop;
|
|
121
|
-
if (a.required)
|
|
122
|
-
required.push(a.name);
|
|
123
|
-
}
|
|
124
|
-
const schema = {
|
|
125
|
-
type: "object",
|
|
126
|
-
properties: props,
|
|
127
|
-
additionalProperties: false,
|
|
128
|
-
};
|
|
129
|
-
if (required.length > 0)
|
|
130
|
-
schema.required = required;
|
|
131
|
-
return schema;
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Build the output JSON Schema. We model results as `{ count, results }`
|
|
135
|
-
* mirroring run_command, where each item in `results` follows the
|
|
136
|
-
* `columns` shape (string-typed properties — Uni-CLI columns are
|
|
137
|
-
* format-agnostic and the runtime emits whatever the pipeline produced).
|
|
138
|
-
*/
|
|
139
|
-
/**
|
|
140
|
-
* Build an output JSON Schema. We model results as `{count, results}` where
|
|
141
|
-
* `results` is an array of items. `columns` becomes the item's property set.
|
|
142
|
-
*
|
|
143
|
-
* Note: we return a simple nested schema rather than a full JSON Schema
|
|
144
|
-
* (which would need a deeper `items` type for `array`). Most MCP clients
|
|
145
|
-
* only inspect the top-level type; Anthropic's client is permissive. If a
|
|
146
|
-
* strict validator rejects this, it will still fall back to the lazy tool
|
|
147
|
-
* path via `run_command`.
|
|
148
|
-
*/
|
|
149
|
-
function buildOutputSchema(cmd) {
|
|
150
|
-
const itemProps = {};
|
|
151
|
-
for (const col of cmd.columns ?? []) {
|
|
152
|
-
itemProps[col] = { type: "string", description: `Column: ${col}` };
|
|
153
|
-
}
|
|
154
|
-
return {
|
|
155
|
-
type: "object",
|
|
156
|
-
properties: {
|
|
157
|
-
count: { type: "integer", description: "Number of results returned" },
|
|
158
|
-
results: {
|
|
159
|
-
type: "array",
|
|
160
|
-
description: "Result rows",
|
|
161
|
-
items: {
|
|
162
|
-
type: "object",
|
|
163
|
-
...(Object.keys(itemProps).length > 0
|
|
164
|
-
? { properties: itemProps }
|
|
165
|
-
: {}),
|
|
126
|
+
{
|
|
127
|
+
name: "unicli_explore",
|
|
128
|
+
description: "Auto-discover API endpoints for any URL. Navigates the page, captures network requests, generates YAML adapters.",
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: "object",
|
|
131
|
+
properties: {
|
|
132
|
+
url: { type: "string", description: "Website URL to explore" },
|
|
133
|
+
goal: {
|
|
134
|
+
type: "string",
|
|
135
|
+
description: "Capability to find (e.g. 'search', 'hot', 'feed')",
|
|
136
|
+
},
|
|
166
137
|
},
|
|
138
|
+
required: ["url"],
|
|
139
|
+
},
|
|
140
|
+
_meta: {
|
|
141
|
+
"anthropic/searchHint": "Auto-discover API endpoints for any website URL. Generate YAML adapters for new sites.",
|
|
142
|
+
},
|
|
143
|
+
annotations: {
|
|
144
|
+
readOnlyHint: false,
|
|
145
|
+
destructiveHint: false,
|
|
146
|
+
idempotentHint: false,
|
|
147
|
+
openWorldHint: true,
|
|
167
148
|
},
|
|
168
149
|
},
|
|
169
|
-
|
|
150
|
+
];
|
|
170
151
|
}
|
|
152
|
+
const expandedRegistry = new Map();
|
|
171
153
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
* older clients reject hyphens, so we normalize defensively.
|
|
154
|
+
* Build the expanded tool set: 4 default meta-tools + one full tool per
|
|
155
|
+
* adapter command. Clients see the complete Uni-CLI surface area.
|
|
175
156
|
*
|
|
176
|
-
*
|
|
177
|
-
* and `claude_code-version` would yield the same normalized name). The
|
|
178
|
-
* expanded-mode dispatcher uses a name → {adapter, cmdName} lookup table
|
|
179
|
-
* built at the same time as the tool list, so callers never need to reverse
|
|
180
|
-
* the normalization. See `expandedRegistry` and `buildExpandedTools` below.
|
|
157
|
+
* Token cost: ~160K for 956 commands. Use only when the client can handle it.
|
|
181
158
|
*/
|
|
182
|
-
function buildToolName(site, command) {
|
|
183
|
-
return `unicli_${site}_${command}`.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
184
|
-
}
|
|
185
|
-
const expandedRegistry = new Map();
|
|
186
159
|
function buildExpandedTools() {
|
|
187
160
|
const tools = [];
|
|
188
|
-
|
|
189
|
-
tools.push(buildLazyTools()[0]);
|
|
161
|
+
tools.push(...buildDefaultTools());
|
|
190
162
|
expandedRegistry.clear();
|
|
191
|
-
|
|
192
|
-
// tool name, the first one wins and the second is silently shadowed. We
|
|
193
|
-
// don't expect this in practice (most adapters use lowercase alphanumeric
|
|
194
|
-
// + hyphen names), but flag it on stderr so it gets noticed.
|
|
195
|
-
const seen = new Set();
|
|
163
|
+
const seen = new Set(DEFAULT_TOOL_NAMES);
|
|
196
164
|
for (const adapter of getAllAdapters()) {
|
|
197
165
|
for (const [cmdName, cmd] of Object.entries(adapter.commands)) {
|
|
198
|
-
const
|
|
166
|
+
const rawDesc = cmd.description?.trim() ||
|
|
199
167
|
adapter.description?.trim() ||
|
|
200
168
|
`${cmdName} for ${adapter.name}`;
|
|
201
169
|
const toolName = buildToolName(adapter.name, cmdName);
|
|
@@ -207,30 +175,82 @@ function buildExpandedTools() {
|
|
|
207
175
|
expandedRegistry.set(toolName, { adapter, cmdName, cmd });
|
|
208
176
|
tools.push({
|
|
209
177
|
name: toolName,
|
|
210
|
-
description: `[${adapter.name}] ${
|
|
178
|
+
description: truncateDescription(`[${adapter.name}] ${rawDesc}`),
|
|
211
179
|
inputSchema: buildInputSchema(cmd),
|
|
212
180
|
outputSchema: buildOutputSchema(cmd),
|
|
181
|
+
_meta: {
|
|
182
|
+
"anthropic/searchHint": `${adapter.name}: ${rawDesc}`,
|
|
183
|
+
},
|
|
184
|
+
annotations: {
|
|
185
|
+
readOnlyHint: true,
|
|
186
|
+
idempotentHint: true,
|
|
187
|
+
openWorldHint: true,
|
|
188
|
+
},
|
|
213
189
|
});
|
|
214
190
|
}
|
|
215
191
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
192
|
+
return tools;
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Build deferred tool set: 4 default meta-tools with full schemas, plus
|
|
196
|
+
* lightweight stubs for all adapter commands (name + searchHint only,
|
|
197
|
+
* minimal inputSchema). Clients like Claude Code's ToolSearch can discover
|
|
198
|
+
* tools by searchHint and then call them — the handler resolves the full
|
|
199
|
+
* command at call time via the expandedRegistry.
|
|
200
|
+
*
|
|
201
|
+
* Token cost: ~8K (vs ~160K for expanded). 95% reduction.
|
|
202
|
+
*/
|
|
203
|
+
function buildDeferredTools() {
|
|
204
|
+
const tools = [];
|
|
205
|
+
tools.push(...buildDefaultTools());
|
|
206
|
+
expandedRegistry.clear();
|
|
207
|
+
const seen = new Set(DEFAULT_TOOL_NAMES);
|
|
208
|
+
for (const adapter of getAllAdapters()) {
|
|
209
|
+
for (const [cmdName, cmd] of Object.entries(adapter.commands)) {
|
|
210
|
+
const rawDesc = cmd.description?.trim() ||
|
|
211
|
+
adapter.description?.trim() ||
|
|
212
|
+
`${cmdName} for ${adapter.name}`;
|
|
213
|
+
const toolName = buildToolName(adapter.name, cmdName);
|
|
214
|
+
if (seen.has(toolName))
|
|
215
|
+
continue;
|
|
216
|
+
seen.add(toolName);
|
|
217
|
+
// Register in the lookup table for runtime dispatch
|
|
218
|
+
expandedRegistry.set(toolName, { adapter, cmdName, cmd });
|
|
219
|
+
// Lightweight stub: name + searchHint + minimal schema.
|
|
220
|
+
// Full inputSchema is resolved at call time via expandedRegistry.
|
|
221
|
+
tools.push({
|
|
222
|
+
name: toolName,
|
|
223
|
+
description: truncateDescription(`[${adapter.name}] ${rawDesc}`),
|
|
224
|
+
inputSchema: {
|
|
225
|
+
type: "object",
|
|
226
|
+
properties: {
|
|
227
|
+
_args: {
|
|
228
|
+
type: "object",
|
|
229
|
+
description: "Command arguments (pass key-value pairs)",
|
|
230
|
+
additionalProperties: true,
|
|
231
|
+
},
|
|
232
|
+
},
|
|
227
233
|
},
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
234
|
+
_meta: {
|
|
235
|
+
"anthropic/searchHint": `${adapter.name}: ${rawDesc}`,
|
|
236
|
+
},
|
|
237
|
+
annotations: {
|
|
238
|
+
readOnlyHint: true,
|
|
239
|
+
idempotentHint: true,
|
|
240
|
+
openWorldHint: true,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
232
245
|
return tools;
|
|
233
246
|
}
|
|
247
|
+
const DEFAULT_TOOL_NAMES = new Set([
|
|
248
|
+
"unicli_run",
|
|
249
|
+
"unicli_list",
|
|
250
|
+
"unicli_search",
|
|
251
|
+
"unicli_explore",
|
|
252
|
+
"unicli_discover",
|
|
253
|
+
]);
|
|
234
254
|
// ── Tool Handlers ───────────────────────────────────────────────────────────
|
|
235
255
|
function handleListAdapters(params) {
|
|
236
256
|
let commands = listCommands();
|
|
@@ -331,6 +351,21 @@ async function runResolvedCommand(adapter, cmd, cmdName, args) {
|
|
|
331
351
|
};
|
|
332
352
|
}
|
|
333
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Annotate a tool result with `_meta.anthropic/maxResultSizeChars` when the
|
|
356
|
+
* serialized payload exceeds MAX_RESULT_SIZE_CHARS (10 KB). This tells
|
|
357
|
+
* Claude Code to accept large payloads without truncation.
|
|
358
|
+
*/
|
|
359
|
+
export function annotateIfLarge(result) {
|
|
360
|
+
const totalChars = result.content.reduce((sum, c) => sum + c.text.length, 0);
|
|
361
|
+
if (totalChars > MAX_RESULT_SIZE_CHARS) {
|
|
362
|
+
return {
|
|
363
|
+
...result,
|
|
364
|
+
_meta: { "anthropic/maxResultSizeChars": 100_000 },
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
334
369
|
async function handleRunCommand(params) {
|
|
335
370
|
const site = params.site;
|
|
336
371
|
const command = params.command;
|
|
@@ -375,7 +410,7 @@ async function handleRunCommand(params) {
|
|
|
375
410
|
/**
|
|
376
411
|
* Expanded-tool dispatcher — parse `unicli_<site>_<command>` back to its
|
|
377
412
|
* components and call the resolver. Returns `undefined` when the tool name
|
|
378
|
-
* is not in expanded form, so the caller can fall through to
|
|
413
|
+
* is not in expanded form, so the caller can fall through to default-tool
|
|
379
414
|
* handling (list_adapters / run_command).
|
|
380
415
|
*/
|
|
381
416
|
async function handleExpandedTool(toolName, args) {
|
|
@@ -394,21 +429,29 @@ async function handleExpandedTool(toolName, args) {
|
|
|
394
429
|
return runResolvedCommand(entry.adapter, entry.cmd, entry.cmdName, args);
|
|
395
430
|
}
|
|
396
431
|
// ── MCP Protocol Handler ────────────────────────────────────────────────────
|
|
397
|
-
const PROTOCOL_VERSION = "
|
|
432
|
+
const PROTOCOL_VERSION = "2025-03-26";
|
|
398
433
|
function parseArgs(argv) {
|
|
399
434
|
const opts = {
|
|
400
|
-
|
|
435
|
+
expanded: false,
|
|
401
436
|
transport: "stdio",
|
|
402
437
|
port: 19826,
|
|
438
|
+
auth: false,
|
|
403
439
|
};
|
|
404
440
|
for (let i = 0; i < argv.length; i++) {
|
|
405
441
|
const a = argv[i];
|
|
406
|
-
if (a === "--
|
|
407
|
-
opts.
|
|
442
|
+
if (a === "--expanded")
|
|
443
|
+
opts.expanded = true;
|
|
444
|
+
else if (a === "--auth")
|
|
445
|
+
opts.auth = true;
|
|
408
446
|
else if (a === "--transport") {
|
|
409
447
|
const v = argv[++i];
|
|
410
|
-
if (v === "stdio" || v === "http")
|
|
448
|
+
if (v === "stdio" || v === "http" || v === "streamable") {
|
|
411
449
|
opts.transport = v;
|
|
450
|
+
}
|
|
451
|
+
else if (v === "sse") {
|
|
452
|
+
// Deprecated alias — SSE replaced by Streamable HTTP in spec 2025-03-26
|
|
453
|
+
opts.transport = "streamable";
|
|
454
|
+
}
|
|
412
455
|
}
|
|
413
456
|
else if (a === "--port") {
|
|
414
457
|
const v = parseInt(argv[++i], 10);
|
|
@@ -429,7 +472,7 @@ function buildHandler(tools) {
|
|
|
429
472
|
result: {
|
|
430
473
|
protocolVersion: PROTOCOL_VERSION,
|
|
431
474
|
capabilities: {
|
|
432
|
-
tools: {},
|
|
475
|
+
tools: { listChanged: false },
|
|
433
476
|
},
|
|
434
477
|
serverInfo: {
|
|
435
478
|
name: "unicli",
|
|
@@ -438,6 +481,8 @@ function buildHandler(tools) {
|
|
|
438
481
|
},
|
|
439
482
|
};
|
|
440
483
|
case "notifications/initialized":
|
|
484
|
+
// JSON-RPC notifications must not receive responses.
|
|
485
|
+
// Returning a sentinel that transports check before serializing.
|
|
441
486
|
return null;
|
|
442
487
|
case "tools/list":
|
|
443
488
|
return {
|
|
@@ -456,17 +501,65 @@ function buildHandler(tools) {
|
|
|
456
501
|
}
|
|
457
502
|
const toolArgs = params.arguments ?? {};
|
|
458
503
|
switch (params.name) {
|
|
504
|
+
// Support both old names (list_adapters, run_command) and new
|
|
505
|
+
// names (unicli_list, unicli_run) for backwards compatibility.
|
|
506
|
+
case "unicli_list":
|
|
459
507
|
case "list_adapters": {
|
|
460
508
|
const result = handleListAdapters(toolArgs);
|
|
461
|
-
return { jsonrpc: "2.0", id, result };
|
|
509
|
+
return { jsonrpc: "2.0", id, result: annotateIfLarge(result) };
|
|
462
510
|
}
|
|
511
|
+
case "unicli_run":
|
|
463
512
|
case "run_command":
|
|
464
513
|
return handleRunCommand(toolArgs).then((result) => ({
|
|
465
514
|
jsonrpc: "2.0",
|
|
466
515
|
id,
|
|
467
|
-
result,
|
|
516
|
+
result: annotateIfLarge(result),
|
|
468
517
|
}));
|
|
518
|
+
case "unicli_search": {
|
|
519
|
+
const searchQuery = toolArgs.query;
|
|
520
|
+
const searchLimit = toolArgs.limit || 5;
|
|
521
|
+
if (!searchQuery) {
|
|
522
|
+
return {
|
|
523
|
+
jsonrpc: "2.0",
|
|
524
|
+
id,
|
|
525
|
+
error: {
|
|
526
|
+
code: -32602,
|
|
527
|
+
message: "Missing required parameter: query",
|
|
528
|
+
},
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
return import("../discovery/search.js").then(({ search: searchFn }) => {
|
|
532
|
+
const results = searchFn(searchQuery, searchLimit);
|
|
533
|
+
return {
|
|
534
|
+
jsonrpc: "2.0",
|
|
535
|
+
id,
|
|
536
|
+
result: annotateIfLarge({
|
|
537
|
+
content: [
|
|
538
|
+
{
|
|
539
|
+
type: "text",
|
|
540
|
+
text: JSON.stringify({
|
|
541
|
+
query: searchQuery,
|
|
542
|
+
count: results.length,
|
|
543
|
+
results: results.map((r) => ({
|
|
544
|
+
command: `unicli ${r.site} ${r.command}`,
|
|
545
|
+
site: r.site,
|
|
546
|
+
name: r.command,
|
|
547
|
+
description: r.description,
|
|
548
|
+
score: r.score,
|
|
549
|
+
category: r.category,
|
|
550
|
+
usage: r.usage,
|
|
551
|
+
})),
|
|
552
|
+
}, null, 2),
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
}),
|
|
556
|
+
};
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
case "unicli_explore":
|
|
469
560
|
case "unicli_discover": {
|
|
561
|
+
// unicli_explore is the canonical name (v0.211.1+).
|
|
562
|
+
// unicli_discover kept as alias for backwards compatibility.
|
|
470
563
|
const discoverUrl = toolArgs.url;
|
|
471
564
|
const discoverGoal = toolArgs.goal;
|
|
472
565
|
if (!discoverUrl) {
|
|
@@ -479,6 +572,17 @@ function buildHandler(tools) {
|
|
|
479
572
|
},
|
|
480
573
|
};
|
|
481
574
|
}
|
|
575
|
+
if (!discoverUrl.startsWith("http://") &&
|
|
576
|
+
!discoverUrl.startsWith("https://")) {
|
|
577
|
+
return {
|
|
578
|
+
jsonrpc: "2.0",
|
|
579
|
+
id,
|
|
580
|
+
error: {
|
|
581
|
+
code: -32602,
|
|
582
|
+
message: "URL must start with http:// or https://",
|
|
583
|
+
},
|
|
584
|
+
};
|
|
585
|
+
}
|
|
482
586
|
return import("node:child_process").then(({ execFile: ef }) => import("node:util").then(({ promisify: prom }) => {
|
|
483
587
|
const execFileP = prom(ef);
|
|
484
588
|
const discoverArgs = ["generate", discoverUrl, "--json"];
|
|
@@ -490,11 +594,13 @@ function buildHandler(tools) {
|
|
|
490
594
|
}).then(({ stdout }) => ({
|
|
491
595
|
jsonrpc: "2.0",
|
|
492
596
|
id,
|
|
493
|
-
result: {
|
|
597
|
+
result: annotateIfLarge({
|
|
598
|
+
content: [{ type: "text", text: stdout }],
|
|
599
|
+
}),
|
|
494
600
|
}), (err) => ({
|
|
495
601
|
jsonrpc: "2.0",
|
|
496
602
|
id,
|
|
497
|
-
result: {
|
|
603
|
+
result: annotateIfLarge({
|
|
498
604
|
content: [
|
|
499
605
|
{
|
|
500
606
|
type: "text",
|
|
@@ -504,20 +610,20 @@ function buildHandler(tools) {
|
|
|
504
610
|
},
|
|
505
611
|
],
|
|
506
612
|
isError: true,
|
|
507
|
-
},
|
|
613
|
+
}),
|
|
508
614
|
}));
|
|
509
615
|
}));
|
|
510
616
|
}
|
|
511
617
|
default:
|
|
512
618
|
return handleExpandedTool(params.name, toolArgs).then((result) => {
|
|
513
619
|
if (result)
|
|
514
|
-
return { jsonrpc: "2.0", id, result };
|
|
620
|
+
return { jsonrpc: "2.0", id, result: annotateIfLarge(result) };
|
|
515
621
|
return {
|
|
516
622
|
jsonrpc: "2.0",
|
|
517
623
|
id,
|
|
518
624
|
error: {
|
|
519
|
-
code: -
|
|
520
|
-
message: `Unknown tool: ${params.name}. Use
|
|
625
|
+
code: -32602,
|
|
626
|
+
message: `Unknown tool: ${params.name}. Use unicli_list to see available commands.`,
|
|
521
627
|
},
|
|
522
628
|
};
|
|
523
629
|
});
|
|
@@ -596,15 +702,29 @@ async function startStdio(handler) {
|
|
|
596
702
|
* to MCP only need request/response, and starting with the simpler shape
|
|
597
703
|
* means zero new dependencies and a tiny attack surface.
|
|
598
704
|
*/
|
|
599
|
-
async function startHttp(handler, port,
|
|
705
|
+
async function startHttp(handler, port, authEnabled = false) {
|
|
706
|
+
const oauthMiddleware = authEnabled ? createOAuthMiddleware() : null;
|
|
600
707
|
const server = createServer((req, res) => {
|
|
601
|
-
|
|
708
|
+
// OAuth routes (authorize + token) — always public
|
|
709
|
+
if (authEnabled && handleOAuthRoute(req, res))
|
|
710
|
+
return;
|
|
711
|
+
// Health endpoint — always public
|
|
712
|
+
if (req.method === "GET" &&
|
|
713
|
+
(req.url === "/" || req.url === "/mcp" || req.url === "/health")) {
|
|
602
714
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
715
|
+
const adapterCount = getAllAdapters().length;
|
|
716
|
+
const commandCount = listCommands().length;
|
|
717
|
+
// Compute actual expanded tool count: 3 default tools + all adapter commands.
|
|
718
|
+
let expandedCount = 3; // default tools
|
|
719
|
+
for (const adapter of getAllAdapters()) {
|
|
720
|
+
expandedCount += Object.keys(adapter.commands).length;
|
|
721
|
+
}
|
|
603
722
|
res.end(JSON.stringify({
|
|
604
|
-
|
|
723
|
+
status: "ok",
|
|
724
|
+
adapters: adapterCount,
|
|
725
|
+
commands: commandCount,
|
|
726
|
+
tools: { default: 3, expanded: expandedCount },
|
|
605
727
|
version: VERSION,
|
|
606
|
-
tools: toolCount,
|
|
607
|
-
protocol: PROTOCOL_VERSION,
|
|
608
728
|
}));
|
|
609
729
|
return;
|
|
610
730
|
}
|
|
@@ -613,9 +733,31 @@ async function startHttp(handler, port, toolCount) {
|
|
|
613
733
|
res.end(JSON.stringify({ error: "POST /mcp" }));
|
|
614
734
|
return;
|
|
615
735
|
}
|
|
736
|
+
// OAuth middleware — block unauthenticated requests when --auth is set
|
|
737
|
+
if (oauthMiddleware?.(req, res))
|
|
738
|
+
return;
|
|
739
|
+
const MAX_BODY = 1_048_576; // 1 MB
|
|
616
740
|
const chunks = [];
|
|
617
|
-
|
|
741
|
+
let bodySize = 0;
|
|
742
|
+
let aborted = false;
|
|
743
|
+
req.on("data", (chunk) => {
|
|
744
|
+
bodySize += chunk.length;
|
|
745
|
+
if (bodySize > MAX_BODY) {
|
|
746
|
+
aborted = true;
|
|
747
|
+
res.writeHead(413, { "Content-Type": "application/json" });
|
|
748
|
+
res.end(JSON.stringify({
|
|
749
|
+
jsonrpc: "2.0",
|
|
750
|
+
id: null,
|
|
751
|
+
error: { code: -32600, message: "Request too large" },
|
|
752
|
+
}));
|
|
753
|
+
req.destroy();
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
chunks.push(chunk);
|
|
757
|
+
});
|
|
618
758
|
req.on("end", async () => {
|
|
759
|
+
if (aborted)
|
|
760
|
+
return;
|
|
619
761
|
const body = Buffer.concat(chunks).toString("utf-8");
|
|
620
762
|
let parsed;
|
|
621
763
|
try {
|
|
@@ -632,8 +774,14 @@ async function startHttp(handler, port, toolCount) {
|
|
|
632
774
|
}
|
|
633
775
|
try {
|
|
634
776
|
const response = await handler(parsed);
|
|
777
|
+
if (!response) {
|
|
778
|
+
// JSON-RPC notification — no response expected
|
|
779
|
+
res.writeHead(204);
|
|
780
|
+
res.end();
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
635
783
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
636
|
-
res.end(JSON.stringify(response
|
|
784
|
+
res.end(JSON.stringify(response));
|
|
637
785
|
}
|
|
638
786
|
catch (err) {
|
|
639
787
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -661,18 +809,38 @@ async function main() {
|
|
|
661
809
|
// Load adapters (same as CLI)
|
|
662
810
|
loadAllAdapters();
|
|
663
811
|
await loadTsAdapters();
|
|
664
|
-
|
|
812
|
+
// Three modes:
|
|
813
|
+
// default → 4 meta-tools (~200 tokens)
|
|
814
|
+
// expanded → 4 meta-tools + 956 full tool schemas (~160K tokens)
|
|
815
|
+
// deferred → 4 meta-tools + 956 lightweight stubs (~8K tokens)
|
|
816
|
+
const mode = opts.expanded ? "expanded" : "default";
|
|
817
|
+
const tools = opts.expanded ? buildExpandedTools() : buildDefaultTools();
|
|
818
|
+
// Deferred mode is auto-activated for Streamable HTTP transport (remote
|
|
819
|
+
// clients benefit most from searchHint-based discovery).
|
|
820
|
+
// For explicit control, the expanded flag takes precedence.
|
|
821
|
+
if (opts.transport === "streamable" && !opts.expanded) {
|
|
822
|
+
const deferredTools = buildDeferredTools();
|
|
823
|
+
tools.length = 0;
|
|
824
|
+
tools.push(...deferredTools);
|
|
825
|
+
}
|
|
665
826
|
const handler = buildHandler(tools);
|
|
666
827
|
const adapterCount = getAllAdapters().length;
|
|
667
828
|
const commandCount = listCommands().length;
|
|
668
829
|
if (opts.transport === "http") {
|
|
669
|
-
await startHttp(handler, opts.port,
|
|
670
|
-
|
|
830
|
+
await startHttp(handler, opts.port, opts.auth);
|
|
831
|
+
const authLabel = opts.auth ? ", OAuth enabled" : "";
|
|
832
|
+
process.stderr.write(`unicli MCP server v${VERSION} — ${adapterCount} sites, ${commandCount} commands (${tools.length} tools registered, mode=${mode}${authLabel})\n`);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (opts.transport === "streamable") {
|
|
836
|
+
await startStreamableHttp(opts.port, handler, { auth: opts.auth });
|
|
837
|
+
const authLabel = opts.auth ? ", OAuth enabled" : "";
|
|
838
|
+
process.stderr.write(`unicli MCP server v${VERSION} — ${adapterCount} sites, ${commandCount} commands (${tools.length} tools, mode=${mode}, transport=streamable${authLabel})\n`);
|
|
671
839
|
return;
|
|
672
840
|
}
|
|
673
841
|
// stdio (default)
|
|
674
842
|
await startStdio(handler);
|
|
675
|
-
process.stderr.write(`unicli MCP server v${VERSION} — ${adapterCount} sites, ${commandCount} commands (${tools.length} tools registered, mode=${
|
|
843
|
+
process.stderr.write(`unicli MCP server v${VERSION} — ${adapterCount} sites, ${commandCount} commands (${tools.length} tools registered, mode=${mode})\n`);
|
|
676
844
|
}
|
|
677
845
|
main().catch((err) => {
|
|
678
846
|
process.stderr.write(`Fatal: ${err instanceof Error ? err.message : String(err)}\n`);
|