@gecho-ai/gecho-bridge 1.1.2 → 1.1.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 (3) hide show
  1. package/mcp-client.js +32 -12
  2. package/package.json +1 -1
  3. package/server.js +22 -11
package/mcp-client.js CHANGED
@@ -110,8 +110,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
110
110
  return {
111
111
  tools: [
112
112
  {
113
- name: "tiktok_search_top_200",
114
- description: "在 TikTok 上搜索关键词,自动滚动加载至少 200 条结果,并全部返回。",
113
+ name: "tiktok_search",
114
+ description: "在 TikTok 上搜索关键词,自动滚动加载结果并返回。",
115
115
  inputSchema: {
116
116
  type: "object",
117
117
  properties: {
@@ -120,6 +120,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
120
120
  },
121
121
  required: ["query"]
122
122
  }
123
+ },
124
+ {
125
+ name: "tiktok_insight",
126
+ description: "在 TikTok 搜索的基础上进行商机洞察和趋势分析。",
127
+ inputSchema: {
128
+ type: "object",
129
+ properties: {
130
+ query: { type: "string", description: "搜索关键词 (例如: '户外野餐垫')" },
131
+ save_dir: { type: "string", description: "可选的保存目录绝对路径 (例如: '/Users/xxx/data')" }
132
+ },
133
+ required: ["query"]
134
+ }
123
135
  }
124
136
  ]
125
137
  };
@@ -127,9 +139,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
127
139
 
128
140
  // 2. 转发工具请求到 Service 层
129
141
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
130
- if (request.params.name === "tiktok_search_top_200") {
131
- const { query, save_dir } = request.params.arguments;
142
+ const toolName = request.params.name;
143
+ const args = request.params.arguments;
132
144
 
145
+ // 只要是 tiktok_ 开头的工具,都走通用转发逻辑
146
+ if (toolName.startsWith("tiktok_") || toolName.startsWith("x_") || toolName.startsWith("ins_")) {
133
147
  try {
134
148
  const requestService = () => new Promise((resolve, reject) => {
135
149
  const req = http.request(HTTP_SERVICE_URL, {
@@ -154,7 +168,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
154
168
  });
155
169
 
156
170
  req.on("error", () => reject(new Error("Service Layer communication error")));
157
- req.write(JSON.stringify({ query, save_dir }));
171
+
172
+ // 【核心改动】:直接将工具名作为 action,所有参数作为 payload 透传
173
+ req.write(JSON.stringify({
174
+ action: toolName,
175
+ ...args
176
+ }));
158
177
  req.end();
159
178
  });
160
179
 
@@ -177,32 +196,33 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
177
196
 
178
197
  const result = serviceResponse.data;
179
198
  if (typeof result === 'object' && result !== null && result.error) {
180
- return { content: [{ type: "text", text: `❌ 抓取错误: ${result.error}` }], isError: true };
199
+ return { content: [{ type: "text", text: `❌ 业务错误: ${result.error}` }], isError: true };
181
200
  }
182
201
 
183
202
  if (!Array.isArray(result)) {
184
- return { content: [{ type: "text", text: `❌ 异常: 服务端未返回数组格式的数据` }], isError: true };
203
+ return { content: [{ type: "text", text: `❌ 异常: 插件未返回数组格式的结果` }], isError: true };
185
204
  }
186
205
 
187
206
  const savePath = serviceResponse.savePath || "";
188
207
  const saveLine = savePath
189
- ? `📂 完整结果已保存到: ${savePath}\n\n`
190
- : `📂 未返回保存路径,请检查服务端日志。\n\n`;
208
+ ? `📂 数据已存: ${savePath}\n\n`
209
+ : "";
210
+
191
211
  const top20 = result.slice(0, 20);
192
212
  return {
193
213
  content: [
194
214
  {
195
215
  type: "text",
196
- text: `✅ 抓取完成!共获取 ${result.length} 条数据。\n` +
216
+ text: `✅ [${toolName}] 执行成功,共 ${result.length} 条数据。\n` +
197
217
  saveLine +
198
- `以下是点赞最高的前 20 条结果:\n` +
218
+ `以下是部分结果展示:\n` +
199
219
  JSON.stringify(top20, null, 2)
200
220
  }
201
221
  ]
202
222
  };
203
223
  } catch (e) {
204
224
  return {
205
- content: [{ type: "text", text: `❌ 转发失败: ${e.message}.` }],
225
+ content: [{ type: "text", text: `❌ 链路故障: ${e.message}.` }],
206
226
  isError: true
207
227
  };
208
228
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gecho-ai/gecho-bridge",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "MCP Bridge for TikTok Extension Relay - Connecting LLMs to TikTok via Chrome Extension",
5
5
  "main": "mcp-client.js",
6
6
  "bin": {
package/server.js CHANGED
@@ -149,7 +149,7 @@ const server = http.createServer(async (req, res) => {
149
149
  return;
150
150
  }
151
151
 
152
- if (req.method === "POST" && req.url === "/search") {
152
+ if (req.method === "POST" && (req.url === "/search" || req.url === "/action")) {
153
153
  if (shuttingDown) {
154
154
  res.statusCode = 503;
155
155
  return res.end(JSON.stringify({ error: "Service is shutting down" }));
@@ -160,10 +160,11 @@ const server = http.createServer(async (req, res) => {
160
160
  req.on("end", async () => {
161
161
  try {
162
162
  const payload = JSON.parse(body);
163
- const query = normalizeQuery(payload.query, payload.query_b64);
164
- if (!query) {
163
+ const action = payload.action;
164
+
165
+ if (!action) {
165
166
  res.statusCode = 400;
166
- return res.end(JSON.stringify({ error: "Missing query" }));
167
+ return res.end(JSON.stringify({ error: "Missing action" }));
167
168
  }
168
169
 
169
170
  if (!extensionSocket || extensionSocket.readyState !== 1) {
@@ -171,20 +172,31 @@ const server = http.createServer(async (req, res) => {
171
172
  return res.end(JSON.stringify({ error: "Extension not connected" }));
172
173
  }
173
174
 
174
- console.log(`🔍 Processing search request for: [${query}]`);
175
+ console.log(`🚀 Dispatching action: [${action}]`);
175
176
  const requestId = `svc-${Date.now()}-${requestIdCounter++}`;
176
177
 
177
178
  const result = await new Promise((resolve) => {
178
179
  const timeoutId = setTimeout(() => {
179
180
  pendingRequests.delete(requestId);
180
- resolve({ error: "Scraping timeout (300s)" });
181
+ resolve({ error: `Scraping timeout (300s) for action: ${action}` });
181
182
  }, 300000);
182
183
 
183
184
  pendingRequests.set(requestId, { resolve, timeoutId });
184
185
 
186
+ // 通用透传逻辑:将 payload 中的所有参数(除去 action)作为 params 传给插件
187
+ const { action: _a, ...params } = payload;
188
+
189
+ // 🐷 兼容层:如果插件版本较旧,识别不了 tiktok_search,则映射回 search
190
+ let finalAction = action;
191
+ if (action === "tiktok_search") finalAction = "search";
192
+ if (action === "tiktok_insight") finalAction = "search";
193
+
185
194
  extensionSocket.send(JSON.stringify({
186
195
  method: "execute_action",
187
- params: { action: "search", params: { query } },
196
+ params: {
197
+ action: finalAction,
198
+ params: params
199
+ },
188
200
  requestId: requestId
189
201
  }));
190
202
  });
@@ -193,11 +205,10 @@ const server = http.createServer(async (req, res) => {
193
205
  let savePath = "";
194
206
  let saveWarning = "";
195
207
  if (Array.isArray(result) && result.length > 0) {
196
- // 优先级: 1. 请求中传来的 save_dir 2. 环境变量 GECHO_DATA_DIR 3. 默认 data 目录
197
- const dataDir = payload.save_dir || process.env.GECHO_DATA_DIR || path.join(__dirname, "..", "data");
208
+ const dataDir = payload.save_dir || process.env.GECHO_DATA_DIR || path.join(__dirname, "data");
198
209
  if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
199
- const safeName = toSafeFileName(query);
200
- const fixedPath = path.join(dataDir, `${safeName}_search_results.json`);
210
+ const safeName = toSafeFileName(params.query || action);
211
+ const fixedPath = path.join(dataDir, `${safeName}_results.json`);
201
212
  try {
202
213
  fs.writeFileSync(fixedPath, JSON.stringify(result, null, 2), "utf8");
203
214
  savePath = fixedPath;