@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.
- package/mcp-client.js +32 -12
- package/package.json +1 -1
- 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: "
|
|
114
|
-
description: "在 TikTok
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
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: `❌
|
|
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: `❌ 异常:
|
|
203
|
+
return { content: [{ type: "text", text: `❌ 异常: 插件未返回数组格式的结果` }], isError: true };
|
|
185
204
|
}
|
|
186
205
|
|
|
187
206
|
const savePath = serviceResponse.savePath || "";
|
|
188
207
|
const saveLine = savePath
|
|
189
|
-
? `📂
|
|
190
|
-
:
|
|
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: `✅
|
|
216
|
+
text: `✅ [${toolName}] 执行成功,共 ${result.length} 条数据。\n` +
|
|
197
217
|
saveLine +
|
|
198
|
-
|
|
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: `❌
|
|
225
|
+
content: [{ type: "text", text: `❌ 链路故障: ${e.message}.` }],
|
|
206
226
|
isError: true
|
|
207
227
|
};
|
|
208
228
|
}
|
package/package.json
CHANGED
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
|
|
164
|
-
|
|
163
|
+
const action = payload.action;
|
|
164
|
+
|
|
165
|
+
if (!action) {
|
|
165
166
|
res.statusCode = 400;
|
|
166
|
-
return res.end(JSON.stringify({ error: "Missing
|
|
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(
|
|
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:
|
|
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: {
|
|
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
|
-
|
|
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}
|
|
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;
|