@opentiny/webmcp-cli 0.0.1-alpha.6 → 0.0.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.
@@ -0,0 +1,94 @@
1
+ ;(function(){
2
+ "use strict";
3
+ (() => {
4
+ // webmcp-tools/my.oschina.net/index.ts
5
+ var mcp = navigator.modelContext;
6
+ if (!mcp || typeof mcp.registerTool !== "function") {
7
+ console.warn("[webmcp-tools] juejin.cn: navigator.modelContext.registerTool \u672A\u5C31\u7EEA\uFF0C\u8DF3\u8FC7\u6CE8\u5165");
8
+ } else if (!window.__webmcptools_juejincn) {
9
+ try {
10
+ mcp.registerTool({
11
+ name: "create_article",
12
+ title: "\u53D1\u5E03\u65B0\u6587\u7AE0",
13
+ description: "\u63A5\u6536\u6587\u7AE0\u7684\u6807\u9898\u548C\u6B63\u6587\uFF0C\u5C06\u5B83\u4EEC\u586B\u5199\u5230\u7F51\u9875\u4E2D\u3002",
14
+ inputSchema: {
15
+ type: "object",
16
+ properties: {
17
+ title: {
18
+ type: "string",
19
+ description: "\u6587\u7AE0\u6807\u9898\uFF0C\u5FC5\u586B\uFF0C \u5EFA\u8BAE\u4E0D\u8D85\u8FC715\u4E2A\u5B57\uFF0C\u4E0D\u5141\u8BB8\u6709\u7279\u6B8A\u7B26\u53F7\u5F71\u54CD\u547D\u4EE4\u884C\u53C2\u6570\u89E3\u6790"
20
+ },
21
+ content: {
22
+ type: "string",
23
+ description: "\u6587\u7AE0\u5185\u5BB9\u7684base64\u7F16\u7801\u540E\u7684\u5B57\u7B26\u4E32\uFF0C\u5FC5\u586B\u3002"
24
+ }
25
+ },
26
+ required: ["title", "content"]
27
+ },
28
+ execute: async ({
29
+ uid,
30
+ title,
31
+ content
32
+ }) => {
33
+ if (!title?.trim()) {
34
+ throw new Error("\u53C2\u6570 title \u4E0D\u80FD\u4E3A\u7A7A");
35
+ }
36
+ if (!content?.trim()) {
37
+ throw new Error("\u53C2\u6570 content \u4E0D\u80FD\u4E3A\u7A7A");
38
+ }
39
+ if (!(location.href.startsWith(`https://my.oschina.net/u`) && location.href.endsWith(`/blog/ai-write`))) {
40
+ throw new Error(`\u5F53\u524D\u9875\u9762\u4E0D\u662F\u53D1\u5E03\u9875\u9762\uFF0C\u8BF7\u5148\u6253\u5F00 https://my.oschina.net/u/<uid>}/blog/ai-write`);
41
+ }
42
+ let decodeContent;
43
+ try {
44
+ decodeContent = decodeURIComponent(escape(atob(content)));
45
+ } catch {
46
+ throw new Error("content \u4E0D\u662F\u6709\u6548\u7684 Base64 \u7F16\u7801\uFF0C\u8BF7\u68C0\u67E5\u53C2\u6570\u6216\u4F7F\u7528 @base64file: \u5F15\u7528\u6587\u4EF6");
47
+ }
48
+ let switchBtn = document.querySelector(".editor-switch-btn");
49
+ if (switchBtn) {
50
+ switchBtn.click();
51
+ }
52
+ await new Promise((resolve) => setTimeout(resolve, 300));
53
+ const titleInput = document.querySelector(".title-input-container .title-input");
54
+ if (!titleInput) {
55
+ throw new Error("\u672A\u627E\u5230\u6807\u9898\u8F93\u5165\u6846\uFF0C\u8BF7\u786E\u8BA4\u7F16\u8F91\u5668\u9875\u9762\u5DF2\u5B8C\u5168\u52A0\u8F7D");
56
+ }
57
+ titleInput.focus();
58
+ titleInput.value = title;
59
+ titleInput.dispatchEvent(
60
+ new InputEvent("input", {
61
+ bubbles: true,
62
+ cancelable: true,
63
+ data: title
64
+ })
65
+ );
66
+ titleInput.dispatchEvent(new Event("change", { bubbles: true }));
67
+ titleInput.blur();
68
+ if (titleInput.value.trim() !== title.trim()) {
69
+ throw new Error("\u6807\u9898\u586B\u5199\u5931\u8D25\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u540E\u91CD\u8BD5");
70
+ }
71
+ await new Promise((resolve) => setTimeout(resolve, 300));
72
+ let editor = document.querySelector(".v-md-textarea-editor textarea");
73
+ if (editor) {
74
+ editor.value = decodeContent;
75
+ editor.dispatchEvent(new Event("change", { bubbles: true }));
76
+ editor.blur();
77
+ } else {
78
+ throw new Error("\u672A\u627E\u5230\u6B63\u6587\u7F16\u8F91\u5668");
79
+ }
80
+ return {
81
+ success: true,
82
+ message: "\u6587\u7AE0\u6807\u9898\u548C\u6B63\u6587\u5DF2\u6210\u529F\u586B\u5199\u5230\u5F00\u6E90\u4E2D\u56FD\u7684\u7F16\u8F91\u5668\uFF0C\u8349\u7A3F\u5C06\u81EA\u52A8\u4FDD\u5B58",
83
+ title: title.trim()
84
+ };
85
+ }
86
+ });
87
+ window.__webmcptools_juejincn = true;
88
+ console.log("[webmcp-tools] juejin.cn \u5DE5\u5177\u6CE8\u518C\u6210\u529F");
89
+ } catch (e) {
90
+ console.error("[webmcp-tools] juejin.cn \u5DE5\u5177\u6CE8\u518C\u5931\u8D25:", e.message);
91
+ }
92
+ }
93
+ })();
94
+ })();
@@ -0,0 +1,278 @@
1
+ ;(function(){
2
+ "use strict";
3
+ (() => {
4
+ // webmcp-tools/xiaohongshu.com/index.ts
5
+ var attempts = 0;
6
+ var MAX_ATTEMPTS = 20;
7
+ async function initXhsTools() {
8
+ const mcp = navigator.modelContext;
9
+ if (!mcp || typeof mcp.registerTool !== "function") {
10
+ attempts++;
11
+ if (attempts < MAX_ATTEMPTS) {
12
+ console.log(`[webmcp-tools] xiaohongshu.com: registerTool \u672A\u5C31\u7EEA\uFF0C500ms \u540E\u8FDB\u884C\u7B2C ${attempts} \u6B21\u91CD\u8BD5...`);
13
+ setTimeout(initXhsTools, 500);
14
+ } else {
15
+ console.error("[webmcp-tools] xiaohongshu.com: \u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0CregisterTool \u4ECD\u672A\u5C31\u7EEA");
16
+ }
17
+ return;
18
+ }
19
+ const isRegistered = (() => {
20
+ try {
21
+ const list = window.__webmcpcli_tools || [];
22
+ return list.some((t) => t.name === "xhs_get_note_detail");
23
+ } catch {
24
+ return false;
25
+ }
26
+ })();
27
+ if (!isRegistered) {
28
+ try {
29
+ mcp.registerTool({
30
+ name: "xhs_get_note_detail",
31
+ title: "\u83B7\u53D6\u5C0F\u7EA2\u4E66\u7B14\u8BB0\u8BE6\u60C5",
32
+ description: "\u83B7\u53D6\u5F53\u524D\u67E5\u770B\u7684\u5C0F\u7EA2\u4E66\u7B14\u8BB0\u7684\u8BE6\u7EC6\u4FE1\u606F\uFF0C\u5305\u542B\u6807\u9898\u3001\u4F5C\u8005\u3001\u6B63\u6587\u3001\u70B9\u8D5E\u6570\u3001\u6536\u85CF\u6570\u3001\u8BC4\u8BBA\u6570\u53CA\u6807\u7B7E\u3002",
33
+ inputSchema: {
34
+ type: "object",
35
+ properties: {}
36
+ },
37
+ execute: async () => {
38
+ const bodyText = document.body?.innerText || "";
39
+ const loginWall = /登录后查看|请登录/.test(bodyText);
40
+ const notFound = /页面不见了|笔记不存在|无法浏览/.test(bodyText);
41
+ const securityBlock = /安全限制|访问链接异常/.test(bodyText) || /website-login\/error|error_code=300017|error_code=300031/.test(location.href);
42
+ const clean = (el) => (el?.textContent || "").replace(/\s+/g, " ").trim();
43
+ const title = clean(document.querySelector("#detail-title, .title"));
44
+ const desc = clean(document.querySelector("#detail-desc, .desc, .note-text"));
45
+ const author = clean(document.querySelector(".username, .author-wrapper .name"));
46
+ const likes = clean(document.querySelector(".interact-container .like-wrapper .count"));
47
+ const collects = clean(document.querySelector(".interact-container .collect-wrapper .count"));
48
+ const comments = clean(document.querySelector(".interact-container .chat-wrapper .count"));
49
+ const tags = [];
50
+ document.querySelectorAll('#detail-desc a.tag, #detail-desc a[href*="search_result"]').forEach((el) => {
51
+ const t = (el.textContent || "").trim();
52
+ if (t) tags.push(t);
53
+ });
54
+ if (securityBlock) {
55
+ throw new Error("\u5C0F\u7EA2\u4E66\u98CE\u63A7\u5B89\u5168\u9650\u5236\uFF0C\u8BF7\u7A0D\u540E\u518D\u8BD5\u6216\u66F4\u6362\u767B\u5F55\u8D26\u53F7");
56
+ }
57
+ if (loginWall) {
58
+ throw new Error("\u6B64\u7B14\u8BB0\u5185\u5BB9\u9700\u8981\u767B\u5F55\u540E\u67E5\u770B");
59
+ }
60
+ if (notFound) {
61
+ throw new Error("\u672A\u627E\u5230\u8BE5\u7B14\u8BB0\u6216\u8BE5\u7B14\u8BB0\u5DF2\u88AB\u9650\u5236/\u5220\u9664");
62
+ }
63
+ const numOrZero = (v) => /^\d+/.test(v) ? v : "0";
64
+ return {
65
+ title,
66
+ author,
67
+ content: desc,
68
+ likes: numOrZero(likes),
69
+ collects: numOrZero(collects),
70
+ comments: numOrZero(comments),
71
+ tags: tags.join(", ")
72
+ };
73
+ }
74
+ });
75
+ mcp.registerTool({
76
+ name: "xhs_get_feed",
77
+ title: "\u83B7\u53D6\u5C0F\u7EA2\u4E66\u9996\u9875\u63A8\u8350Feed",
78
+ description: "\u4ECE\u5C0F\u7EA2\u4E66\u9996\u9875\u83B7\u53D6\u63A8\u8350\u7684\u7B14\u8BB0\u5217\u8868\u3002\u5FC5\u987B\u5728 https://www.xiaohongshu.com/explore \u9875\u9762\u6267\u884C\u3002",
79
+ inputSchema: {
80
+ type: "object",
81
+ properties: {
82
+ limit: {
83
+ type: "number",
84
+ description: "\u9650\u5236\u83B7\u53D6\u7684\u63A8\u8350\u7B14\u8BB0\u6570\u91CF\uFF0C\u9ED8\u8BA4 20 \u6761\uFF0C\u6700\u5927\u4E0D\u8D85\u8FC7 50"
85
+ }
86
+ }
87
+ },
88
+ execute: async ({ limit = 20 }) => {
89
+ if (!location.href.includes("xiaohongshu.com/explore")) {
90
+ throw new Error("\u8BF7\u5148\u5207\u6362\u5230\u5C0F\u7EA2\u4E66\u9996\u9875\u5217\u8868\u9875 https://www.xiaohongshu.com/explore");
91
+ }
92
+ let pinia = null;
93
+ const probe = (el) => el?.__vue_app__?.config?.globalProperties?.$pinia ?? null;
94
+ pinia = probe(document.querySelector("#app"));
95
+ if (!pinia) {
96
+ for (const el of Array.from(document.querySelectorAll("*"))) {
97
+ pinia = probe(el);
98
+ if (pinia) break;
99
+ }
100
+ }
101
+ if (!pinia || !pinia._s) {
102
+ throw new Error("\u9875\u9762\u5C1A\u672A\u5B8C\u6210\u52A0\u8F7D\u6216\u672A\u627E\u5230 Pinia \u5B9E\u4F8B\uFF0C\u8BF7\u7B49\u5F85\u4E00\u4E24\u79D2\u540E\u91CD\u8BD5");
103
+ }
104
+ const store = pinia._s.get("feed");
105
+ if (!store) {
106
+ throw new Error("\u672A\u627E\u5230 Pinia \u4E2D\u7684 feed \u72B6\u6001");
107
+ }
108
+ const feeds = store.feeds;
109
+ if (!Array.isArray(feeds)) {
110
+ throw new Error("\u83B7\u53D6\u63A8\u8350 Feed \u6570\u636E\u5931\u8D25\uFF0C\u6570\u636E\u683C\u5F0F\u4E0D\u6B63\u786E");
111
+ }
112
+ const toCleanString = (value) => typeof value === "string" ? value.trim() : value == null ? "" : String(value).trim();
113
+ const buildFeedNoteUrl = (id, xsecToken) => {
114
+ const cleanId = toCleanString(id);
115
+ const url = new URL(`https://www.xiaohongshu.com/explore/${encodeURIComponent(cleanId)}`);
116
+ const cleanToken = toCleanString(xsecToken);
117
+ if (!cleanToken) return url.toString();
118
+ url.searchParams.set("xsec_token", cleanToken);
119
+ url.searchParams.set("xsec_source", "");
120
+ return url.toString();
121
+ };
122
+ const rows = [];
123
+ for (const entry of feeds) {
124
+ if (rows.length >= limit) break;
125
+ const card = entry?.noteCard ?? {};
126
+ const id = toCleanString(entry?.id);
127
+ if (!id) continue;
128
+ const xsecToken = toCleanString(entry?.xsecToken);
129
+ rows.push({
130
+ id,
131
+ title: toCleanString(card.displayTitle),
132
+ type: toCleanString(card.type),
133
+ author: toCleanString(card.user?.nickname ?? card.user?.nickName),
134
+ likes: toCleanString(card.interactInfo?.likedCount),
135
+ url: buildFeedNoteUrl(id, xsecToken)
136
+ });
137
+ }
138
+ return rows;
139
+ }
140
+ });
141
+ mcp.registerTool({
142
+ name: "xhs_search_notes",
143
+ title: "\u641C\u7D22\u5C0F\u7EA2\u4E66\u7B14\u8BB0",
144
+ description: "\u5728\u5C0F\u7EA2\u4E66\u641C\u7D22\u9875\u9762\u83B7\u53D6\u641C\u7D22\u7ED3\u679C\u5217\u8868\u3002\u82E5\u63D0\u4F9B keyword \u4E14\u5F53\u524D\u4E0D\u662F\u5BF9\u5E94\u641C\u7D22\u9875\uFF0C\u4F1A\u89E6\u53D1\u9875\u9762\u8DF3\u8F6C\u3002",
145
+ inputSchema: {
146
+ type: "object",
147
+ properties: {
148
+ keyword: {
149
+ type: "string",
150
+ description: "\u641C\u7D22\u5173\u952E\u5B57\uFF0C\u82E5\u4E0D\u63D0\u4F9B\u5219\u76F4\u63A5\u4ECE\u5F53\u524D\u641C\u7D22\u9875\u9762\u63D0\u53D6\u7ED3\u679C"
151
+ },
152
+ limit: {
153
+ type: "number",
154
+ description: "\u671F\u671B\u83B7\u53D6\u7684\u641C\u7D22\u7ED3\u679C\u6761\u6570\uFF0C\u9ED8\u8BA4 20\uFF0C\u6700\u5927 50"
155
+ }
156
+ }
157
+ },
158
+ execute: async ({ keyword, limit = 20 }) => {
159
+ if (keyword) {
160
+ const targetUrl = `https://www.xiaohongshu.com/search_result?keyword=${encodeURIComponent(keyword)}&source=web_search_result_notes`;
161
+ if (location.href !== targetUrl) {
162
+ location.href = targetUrl;
163
+ return {
164
+ status: "redirecting",
165
+ message: `\u6B63\u5728\u8DF3\u8F6C\u5230\u5C0F\u7EA2\u4E66\u641C\u7D22\u9875\u9762 "${keyword}"\uFF0C\u8BF7\u5728\u9875\u9762\u8DF3\u8F6C\u5E76\u52A0\u8F7D\u5B8C\u6BD5\u540E\u91CD\u65B0\u8FD0\u884C\u6B64\u5DE5\u5177\u3002`
166
+ };
167
+ }
168
+ }
169
+ if (!location.href.includes("xiaohongshu.com/search_result")) {
170
+ throw new Error("\u5F53\u524D\u4E0D\u5728\u5C0F\u7EA2\u4E66\u641C\u7D22\u7ED3\u679C\u9875\uFF0C\u8BF7\u6307\u5B9A keyword \u53C2\u6570\uFF0C\u6216\u5148\u8BBF\u95EE https://www.xiaohongshu.com/search_result");
171
+ }
172
+ const isVisibleNote = (el) => {
173
+ if (el.classList.contains("query-note-item")) return false;
174
+ const rect = el.getBoundingClientRect();
175
+ if (rect.width <= 0 || rect.height <= 0) return false;
176
+ const style = getComputedStyle(el);
177
+ return style.display !== "none" && style.visibility !== "hidden";
178
+ };
179
+ const collectNoteCards = () => {
180
+ const classMatches = document.querySelectorAll("section.note-item");
181
+ if (classMatches.length > 0) return Array.from(classMatches);
182
+ const sections = /* @__PURE__ */ new Set();
183
+ for (const a of Array.from(document.querySelectorAll('a[href*="/search_result/"], a[href*="/explore/"]'))) {
184
+ const section = a.closest("section");
185
+ if (section) sections.add(section);
186
+ }
187
+ return Array.from(sections);
188
+ };
189
+ const cleanText = (value) => (value || "").replace(/\s+/g, " ").trim();
190
+ const stripSuffix = (value) => {
191
+ const text = (value || "").replace(/\s+/g, " ").trim();
192
+ return text.replace(/\s*(?:\d{1,2}天前|\d+小时前|\d+分钟前|\d+秒前|刚刚|昨天|前天|\d+周前|\d+个月前|\d{1,2}-\d{1,2}|\d{4}-\d{1,2}-\d{1,2})$/u, "").trim() || text;
193
+ };
194
+ const extractData = () => {
195
+ const results = [];
196
+ const seen = /* @__PURE__ */ new Set();
197
+ const cards = collectNoteCards();
198
+ for (const el of cards) {
199
+ const htmlEl = el;
200
+ if (htmlEl.classList?.contains("query-note-item")) continue;
201
+ if (!isVisibleNote(htmlEl)) continue;
202
+ const titleEl = el.querySelector(".title, .note-title, a.title, .footer .title span");
203
+ const nameEl = el.querySelector("a.author .name, .author-name, .nick-name, .name");
204
+ const authorWrapEl = el.querySelector("a.author");
205
+ let author = cleanText(nameEl?.textContent || "");
206
+ if (!author && authorWrapEl) {
207
+ const nameChild = authorWrapEl.querySelector(".name");
208
+ author = nameChild ? cleanText(nameChild.textContent || "") : stripSuffix(authorWrapEl.textContent || "");
209
+ }
210
+ const likesEl = el.querySelector(".count, .like-count, .like-wrapper .count");
211
+ const detailLinkEl = el.querySelector("a.cover.mask") || el.querySelector('a[href*="/search_result/"]') || el.querySelector('a[href*="/explore/"]') || el.querySelector('a[href*="/note/"]');
212
+ const authorLinkEl = el.querySelector('a.author, a[href*="/user/profile/"]');
213
+ let url = detailLinkEl?.getAttribute("href") || "";
214
+ if (url && !url.startsWith("http")) {
215
+ url = "https://www.xiaohongshu.com" + url;
216
+ }
217
+ if (!url) continue;
218
+ if (seen.has(url)) continue;
219
+ seen.add(url);
220
+ let title = cleanText(titleEl?.textContent || "");
221
+ if (!title && detailLinkEl) {
222
+ const captionSpan = detailLinkEl.querySelector("span");
223
+ title = cleanText(captionSpan?.textContent || "");
224
+ }
225
+ const noteIdToDate = (urlStr) => {
226
+ const match = urlStr.match(/\/(?:search_result|explore|note)\/([0-9a-f]{24})(?=[?#/]|$)/i);
227
+ if (!match) return "";
228
+ const hex = match[1].substring(0, 8);
229
+ const ts = parseInt(hex, 16);
230
+ if (!ts || ts < 1e9 || ts > 4e9) return "";
231
+ return new Date((ts + 8 * 3600) * 1e3).toISOString().slice(0, 10);
232
+ };
233
+ results.push({
234
+ title,
235
+ author,
236
+ likes: cleanText(likesEl?.textContent || "0"),
237
+ url,
238
+ published_at: noteIdToDate(url),
239
+ author_url: authorLinkEl?.getAttribute("href") ? "https://www.xiaohongshu.com" + authorLinkEl.getAttribute("href") : ""
240
+ });
241
+ }
242
+ return results;
243
+ };
244
+ let currentData = extractData();
245
+ let lastCount = currentData.length;
246
+ let plateauRounds = 0;
247
+ for (let i = 0; i < 5; i++) {
248
+ if (currentData.length >= limit) break;
249
+ const lastHeight = document.body.scrollHeight;
250
+ window.scrollTo(0, lastHeight);
251
+ await new Promise((resolve) => setTimeout(resolve, 1500));
252
+ currentData = extractData();
253
+ if (currentData.length === lastCount) {
254
+ plateauRounds++;
255
+ if (plateauRounds >= 2) break;
256
+ } else {
257
+ plateauRounds = 0;
258
+ lastCount = currentData.length;
259
+ }
260
+ }
261
+ return currentData.slice(0, limit);
262
+ }
263
+ });
264
+ if (typeof mcp.listTools === "function") {
265
+ mcp.listTools().then((list) => {
266
+ window.__webmcpcli_tools = list;
267
+ }).catch(() => {
268
+ });
269
+ }
270
+ console.log("[webmcp-tools] xiaohongshu.com \u4E13\u5C5E\u5DE5\u5177\u6CE8\u518C\u6210\u529F");
271
+ } catch (e) {
272
+ console.error("[webmcp-tools] xiaohongshu.com \u6CE8\u518C\u5931\u8D25:", e.message);
273
+ }
274
+ }
275
+ }
276
+ initXhsTools();
277
+ })();
278
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opentiny/webmcp-cli",
3
- "version": "0.0.1-alpha.6",
3
+ "version": "0.0.2",
4
4
  "type": "module",
5
5
  "description": "WebMCP CLI for AI Agents to interact with browser via Puppeteer",
6
6
  "main": "dist/index.js",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@mcp-b/webmcp-polyfill": "^2.2.0",
30
- "@page-agent/page-controller": "^1.8.2",
30
+ "@page-agent/page-controller": "^1.9.0",
31
31
  "clipboardy": "^5.3.1",
32
32
  "commander": "^11.1.0",
33
33
  "json5": "^2.2.3",