weixin-devtools-mcp 0.0.1
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/README.md +375 -0
- package/build/index.js +542 -0
- package/build/server.js +200 -0
- package/build/tools/ToolDefinition.js +52 -0
- package/build/tools/assert.js +245 -0
- package/build/tools/connection.js +670 -0
- package/build/tools/console.js +192 -0
- package/build/tools/diagnose.js +348 -0
- package/build/tools/index.js +80 -0
- package/build/tools/input.js +255 -0
- package/build/tools/navigate.js +310 -0
- package/build/tools/network.js +1111 -0
- package/build/tools/page.js +150 -0
- package/build/tools/screenshot.js +47 -0
- package/build/tools/snapshot.js +45 -0
- package/build/tools.js +2131 -0
- package/package.json +58 -0
|
@@ -0,0 +1,1111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 网络请求监听工具
|
|
3
|
+
* 通过拦截 wx.request, wx.uploadFile, wx.downloadFile 实现网络监控
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { defineTool } from './ToolDefinition.js';
|
|
7
|
+
/**
|
|
8
|
+
* 创建请求拦截器函数
|
|
9
|
+
* 注意: 这个函数会被序列化后在小程序环境执行,不能使用闭包变量
|
|
10
|
+
* 保持函数简单,只记录信息然后调用原始方法
|
|
11
|
+
*/
|
|
12
|
+
function createRequestInterceptor() {
|
|
13
|
+
return function (options) {
|
|
14
|
+
// 初始化全局存储
|
|
15
|
+
// 关键修复: 在小程序环境中直接访问 wx 对象,不通过 globalThis
|
|
16
|
+
// wx 是小程序提供的全局对象,直接可用
|
|
17
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
18
|
+
const wxObj = (typeof wx !== 'undefined' ? wx : null);
|
|
19
|
+
if (!wxObj) {
|
|
20
|
+
// wx 对象不存在,无法记录,直接调用原始方法
|
|
21
|
+
return this.origin(options);
|
|
22
|
+
}
|
|
23
|
+
if (!wxObj.__networkLogs) {
|
|
24
|
+
wxObj.__networkLogs = [];
|
|
25
|
+
}
|
|
26
|
+
const requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
|
|
27
|
+
const startTime = Date.now();
|
|
28
|
+
// 包装 success 回调
|
|
29
|
+
const originalSuccess = options.success;
|
|
30
|
+
options.success = function (res) {
|
|
31
|
+
wxObj.__networkLogs.push({
|
|
32
|
+
id: requestId,
|
|
33
|
+
type: 'request',
|
|
34
|
+
url: options.url,
|
|
35
|
+
method: options.method || 'GET',
|
|
36
|
+
headers: options.header,
|
|
37
|
+
data: options.data,
|
|
38
|
+
statusCode: res.statusCode,
|
|
39
|
+
response: res.data,
|
|
40
|
+
duration: Date.now() - startTime,
|
|
41
|
+
timestamp: new Date().toISOString(),
|
|
42
|
+
success: true
|
|
43
|
+
});
|
|
44
|
+
if (originalSuccess)
|
|
45
|
+
originalSuccess(res);
|
|
46
|
+
};
|
|
47
|
+
// 包装 fail 回调
|
|
48
|
+
const originalFail = options.fail;
|
|
49
|
+
options.fail = function (err) {
|
|
50
|
+
wxObj.__networkLogs.push({
|
|
51
|
+
id: requestId,
|
|
52
|
+
type: 'request',
|
|
53
|
+
url: options.url,
|
|
54
|
+
method: options.method || 'GET',
|
|
55
|
+
headers: options.header,
|
|
56
|
+
data: options.data,
|
|
57
|
+
error: err.errMsg || String(err),
|
|
58
|
+
duration: Date.now() - startTime,
|
|
59
|
+
timestamp: new Date().toISOString(),
|
|
60
|
+
success: false
|
|
61
|
+
});
|
|
62
|
+
if (originalFail)
|
|
63
|
+
originalFail(err);
|
|
64
|
+
};
|
|
65
|
+
// 调用原始方法
|
|
66
|
+
return this.origin(options);
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 创建 uploadFile 拦截器函数
|
|
71
|
+
*/
|
|
72
|
+
function createUploadFileInterceptor() {
|
|
73
|
+
return function (options) {
|
|
74
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
75
|
+
const wxObj = (typeof wx !== 'undefined' ? wx : null);
|
|
76
|
+
if (!wxObj) {
|
|
77
|
+
return this.origin(options);
|
|
78
|
+
}
|
|
79
|
+
if (!wxObj.__networkLogs) {
|
|
80
|
+
wxObj.__networkLogs = [];
|
|
81
|
+
}
|
|
82
|
+
const requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
|
|
83
|
+
const startTime = Date.now();
|
|
84
|
+
const originalSuccess = options.success;
|
|
85
|
+
options.success = function (res) {
|
|
86
|
+
wxObj.__networkLogs.push({
|
|
87
|
+
id: requestId,
|
|
88
|
+
type: 'uploadFile',
|
|
89
|
+
url: options.url,
|
|
90
|
+
headers: options.header,
|
|
91
|
+
data: {
|
|
92
|
+
filePath: options.filePath,
|
|
93
|
+
name: options.name,
|
|
94
|
+
formData: options.formData
|
|
95
|
+
},
|
|
96
|
+
statusCode: res.statusCode,
|
|
97
|
+
response: res.data,
|
|
98
|
+
duration: Date.now() - startTime,
|
|
99
|
+
timestamp: new Date().toISOString(),
|
|
100
|
+
success: true
|
|
101
|
+
});
|
|
102
|
+
if (originalSuccess)
|
|
103
|
+
originalSuccess(res);
|
|
104
|
+
};
|
|
105
|
+
const originalFail = options.fail;
|
|
106
|
+
options.fail = function (err) {
|
|
107
|
+
wxObj.__networkLogs.push({
|
|
108
|
+
id: requestId,
|
|
109
|
+
type: 'uploadFile',
|
|
110
|
+
url: options.url,
|
|
111
|
+
headers: options.header,
|
|
112
|
+
data: {
|
|
113
|
+
filePath: options.filePath,
|
|
114
|
+
name: options.name,
|
|
115
|
+
formData: options.formData
|
|
116
|
+
},
|
|
117
|
+
error: err.errMsg || String(err),
|
|
118
|
+
duration: Date.now() - startTime,
|
|
119
|
+
timestamp: new Date().toISOString(),
|
|
120
|
+
success: false
|
|
121
|
+
});
|
|
122
|
+
if (originalFail)
|
|
123
|
+
originalFail(err);
|
|
124
|
+
};
|
|
125
|
+
return this.origin(options);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 创建 downloadFile 拦截器函数
|
|
130
|
+
*/
|
|
131
|
+
function createDownloadFileInterceptor() {
|
|
132
|
+
return function (options) {
|
|
133
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
134
|
+
const wxObj = (typeof wx !== 'undefined' ? wx : null);
|
|
135
|
+
if (!wxObj) {
|
|
136
|
+
return this.origin(options);
|
|
137
|
+
}
|
|
138
|
+
if (!wxObj.__networkLogs) {
|
|
139
|
+
wxObj.__networkLogs = [];
|
|
140
|
+
}
|
|
141
|
+
const requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
|
|
142
|
+
const startTime = Date.now();
|
|
143
|
+
const originalSuccess = options.success;
|
|
144
|
+
options.success = function (res) {
|
|
145
|
+
wxObj.__networkLogs.push({
|
|
146
|
+
id: requestId,
|
|
147
|
+
type: 'downloadFile',
|
|
148
|
+
url: options.url,
|
|
149
|
+
headers: options.header,
|
|
150
|
+
statusCode: res.statusCode,
|
|
151
|
+
response: {
|
|
152
|
+
tempFilePath: res.tempFilePath,
|
|
153
|
+
filePath: res.filePath
|
|
154
|
+
},
|
|
155
|
+
duration: Date.now() - startTime,
|
|
156
|
+
timestamp: new Date().toISOString(),
|
|
157
|
+
success: true
|
|
158
|
+
});
|
|
159
|
+
if (originalSuccess)
|
|
160
|
+
originalSuccess(res);
|
|
161
|
+
};
|
|
162
|
+
const originalFail = options.fail;
|
|
163
|
+
options.fail = function (err) {
|
|
164
|
+
wxObj.__networkLogs.push({
|
|
165
|
+
id: requestId,
|
|
166
|
+
type: 'downloadFile',
|
|
167
|
+
url: options.url,
|
|
168
|
+
headers: options.header,
|
|
169
|
+
error: err.errMsg || String(err),
|
|
170
|
+
duration: Date.now() - startTime,
|
|
171
|
+
timestamp: new Date().toISOString(),
|
|
172
|
+
success: false
|
|
173
|
+
});
|
|
174
|
+
if (originalFail)
|
|
175
|
+
originalFail(err);
|
|
176
|
+
};
|
|
177
|
+
return this.origin(options);
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* 启动网络监听工具
|
|
182
|
+
*
|
|
183
|
+
* 使用evaluate()直接在小程序环境注入拦截代码
|
|
184
|
+
* 这种方式可以绕过Mpx等框架的API缓存问题
|
|
185
|
+
*/
|
|
186
|
+
export const startNetworkMonitoringTool = defineTool({
|
|
187
|
+
name: 'start_network_monitoring',
|
|
188
|
+
description: '启动对微信小程序网络请求的监听,拦截 wx.request、wx.uploadFile、wx.downloadFile',
|
|
189
|
+
schema: z.object({
|
|
190
|
+
clearExisting: z.boolean().optional().default(false).describe('是否清除已有的网络请求记录'),
|
|
191
|
+
}),
|
|
192
|
+
annotations: {
|
|
193
|
+
audience: ['developers'],
|
|
194
|
+
},
|
|
195
|
+
handler: async (request, response, context) => {
|
|
196
|
+
const { clearExisting } = request.params;
|
|
197
|
+
if (!context.miniProgram) {
|
|
198
|
+
throw new Error('请先连接到微信开发者工具');
|
|
199
|
+
}
|
|
200
|
+
if (context.networkStorage.isMonitoring) {
|
|
201
|
+
response.appendResponseLine('网络监听已在运行中');
|
|
202
|
+
response.appendResponseLine(`当前已记录 ${context.networkStorage.requests.length} 个网络请求`);
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
// 清除现有记录
|
|
206
|
+
if (clearExisting) {
|
|
207
|
+
context.networkStorage.requests = [];
|
|
208
|
+
}
|
|
209
|
+
try {
|
|
210
|
+
// 使用evaluate()方式在小程序环境中直接注入拦截代码
|
|
211
|
+
// 支持双模式:Mpx框架拦截器 + wx.request回退方案
|
|
212
|
+
await context.miniProgram.evaluate(function (shouldClear) {
|
|
213
|
+
// @ts-ignore - wx在小程序环境中可用
|
|
214
|
+
if (typeof wx === 'undefined') {
|
|
215
|
+
throw new Error('wx对象不可用');
|
|
216
|
+
}
|
|
217
|
+
// 初始化或清除存储
|
|
218
|
+
// @ts-ignore
|
|
219
|
+
if (!wx.__networkLogs || shouldClear) {
|
|
220
|
+
// @ts-ignore
|
|
221
|
+
wx.__networkLogs = [];
|
|
222
|
+
}
|
|
223
|
+
// 检查是否已经注入过拦截器
|
|
224
|
+
// @ts-ignore
|
|
225
|
+
if (wx.__networkInterceptorsInstalled && !shouldClear) {
|
|
226
|
+
console.log('[MCP-DEBUG] 拦截器已安装,跳过重复安装');
|
|
227
|
+
return; // 已安装,跳过
|
|
228
|
+
}
|
|
229
|
+
// 如果需要清除,先删除旧的标记
|
|
230
|
+
if (shouldClear) {
|
|
231
|
+
console.log('[MCP-DEBUG] 强制重装:清除旧的安装标记');
|
|
232
|
+
// @ts-ignore
|
|
233
|
+
delete wx.__networkInterceptorsInstalled;
|
|
234
|
+
// 同时清空pending队列和config缓存
|
|
235
|
+
// @ts-ignore
|
|
236
|
+
wx.__pendingQueue = [];
|
|
237
|
+
// @ts-ignore
|
|
238
|
+
wx.__requestConfigMap = {};
|
|
239
|
+
}
|
|
240
|
+
// ===== 模式1:检测并使用Mpx框架拦截器 =====
|
|
241
|
+
console.log('[MCP-DEBUG] 开始检测Mpx框架...');
|
|
242
|
+
// @ts-ignore - getApp is available in WeChat miniprogram environment
|
|
243
|
+
const app = getApp();
|
|
244
|
+
console.log('[MCP-DEBUG] getApp() 结果:', {
|
|
245
|
+
hasApp: !!app,
|
|
246
|
+
appType: typeof app,
|
|
247
|
+
hasXfetch: !!(app && app.$xfetch),
|
|
248
|
+
xfetchType: app && app.$xfetch ? typeof app.$xfetch : 'undefined'
|
|
249
|
+
});
|
|
250
|
+
const hasMpxFetch = app &&
|
|
251
|
+
app.$xfetch &&
|
|
252
|
+
app.$xfetch.interceptors &&
|
|
253
|
+
typeof app.$xfetch.interceptors.request.use === 'function';
|
|
254
|
+
console.log('[MCP-DEBUG] Mpx检测结果:', {
|
|
255
|
+
hasMpxFetch: hasMpxFetch,
|
|
256
|
+
hasInterceptors: !!(app && app.$xfetch && app.$xfetch.interceptors),
|
|
257
|
+
hasRequestUse: !!(app && app.$xfetch && app.$xfetch.interceptors && app.$xfetch.interceptors.request),
|
|
258
|
+
hasResponseUse: !!(app && app.$xfetch && app.$xfetch.interceptors && app.$xfetch.interceptors.response)
|
|
259
|
+
});
|
|
260
|
+
if (hasMpxFetch) {
|
|
261
|
+
console.log('[MCP] ✅ 检测到Mpx框架,使用getApp().$xfetch拦截器模式');
|
|
262
|
+
console.log('[MCP] 📝 使用Pending队列方案解决业务拦截器改变响应结构的问题');
|
|
263
|
+
// 初始化pending队列和config缓存
|
|
264
|
+
// @ts-ignore
|
|
265
|
+
if (!wx.__pendingQueue) {
|
|
266
|
+
// @ts-ignore
|
|
267
|
+
wx.__pendingQueue = [];
|
|
268
|
+
}
|
|
269
|
+
// @ts-ignore
|
|
270
|
+
if (!wx.__requestConfigMap) {
|
|
271
|
+
// @ts-ignore
|
|
272
|
+
wx.__requestConfigMap = {};
|
|
273
|
+
}
|
|
274
|
+
// 如果需要重装,清空旧的Mpx拦截器handlers(防止累加)
|
|
275
|
+
if (shouldClear) {
|
|
276
|
+
console.log('[MCP-DEBUG] 准备清空handlers, shouldClear=', shouldClear);
|
|
277
|
+
console.log('[MCP-DEBUG] request拦截器结构:', {
|
|
278
|
+
hasInterceptors: !!app.$xfetch.interceptors.request,
|
|
279
|
+
hasHandlers: !!app.$xfetch.interceptors.request.handlers,
|
|
280
|
+
handlersType: typeof app.$xfetch.interceptors.request.handlers,
|
|
281
|
+
handlersIsArray: Array.isArray(app.$xfetch.interceptors.request.handlers)
|
|
282
|
+
});
|
|
283
|
+
// @ts-ignore
|
|
284
|
+
if (app.$xfetch.interceptors.request && app.$xfetch.interceptors.request.handlers) {
|
|
285
|
+
// @ts-ignore
|
|
286
|
+
app.$xfetch.interceptors.request.handlers = [];
|
|
287
|
+
console.log('[MCP-DEBUG] ✅ 已清空旧的request拦截器handlers');
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
console.log('[MCP-DEBUG] ⚠️ request.handlers不存在或不是数组');
|
|
291
|
+
}
|
|
292
|
+
// @ts-ignore
|
|
293
|
+
if (app.$xfetch.interceptors.response && app.$xfetch.interceptors.response.handlers) {
|
|
294
|
+
// @ts-ignore
|
|
295
|
+
app.$xfetch.interceptors.response.handlers = [];
|
|
296
|
+
console.log('[MCP-DEBUG] ✅ 已清空旧的response拦截器handlers');
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
console.log('[MCP-DEBUG] ⚠️ response.handlers不存在或不是数组');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// 请求拦截器 - 记录请求开始并缓存config
|
|
303
|
+
// @ts-ignore
|
|
304
|
+
getApp().$xfetch.interceptors.request.use(function (config) {
|
|
305
|
+
const requestId = 'mpx_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
|
|
306
|
+
const startTime = Date.now();
|
|
307
|
+
console.log('[MCP-DEBUG] 🔵 请求拦截器被触发:', {
|
|
308
|
+
requestId: requestId,
|
|
309
|
+
method: config.method,
|
|
310
|
+
url: config.url,
|
|
311
|
+
hasData: !!config.data,
|
|
312
|
+
hasParams: !!config.params,
|
|
313
|
+
timestamp: new Date().toISOString()
|
|
314
|
+
});
|
|
315
|
+
// 保存完整的config到缓存(因为响应拦截器可能拿不到requestConfig)
|
|
316
|
+
// @ts-ignore
|
|
317
|
+
wx.__requestConfigMap[requestId] = {
|
|
318
|
+
url: config.url,
|
|
319
|
+
method: config.method || 'GET',
|
|
320
|
+
header: config.header || config.headers,
|
|
321
|
+
data: config.data,
|
|
322
|
+
params: config.params,
|
|
323
|
+
timeout: config.timeout || 30000
|
|
324
|
+
};
|
|
325
|
+
// 添加到pending队列(FIFO)
|
|
326
|
+
// @ts-ignore
|
|
327
|
+
wx.__pendingQueue.push({
|
|
328
|
+
id: requestId,
|
|
329
|
+
url: config.url,
|
|
330
|
+
method: config.method || 'GET',
|
|
331
|
+
startTime: startTime
|
|
332
|
+
});
|
|
333
|
+
// 清理超时的pending请求(避免队列堆积)
|
|
334
|
+
const timeout = config.timeout || 30000;
|
|
335
|
+
// @ts-ignore
|
|
336
|
+
wx.__pendingQueue = wx.__pendingQueue.filter((item) => Date.now() - item.startTime < timeout + 5000 // 额外5秒容错
|
|
337
|
+
);
|
|
338
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
339
|
+
wx.__networkLogs.push({
|
|
340
|
+
id: requestId,
|
|
341
|
+
type: 'request',
|
|
342
|
+
method: config.method || 'GET',
|
|
343
|
+
url: config.url,
|
|
344
|
+
headers: config.header || config.headers,
|
|
345
|
+
data: config.data,
|
|
346
|
+
params: config.params,
|
|
347
|
+
timestamp: new Date(startTime).toISOString(),
|
|
348
|
+
source: 'getApp().$xfetch',
|
|
349
|
+
pending: true, // 标记为待完成状态
|
|
350
|
+
success: undefined // 初始化success字段,避免状态判断问题
|
|
351
|
+
});
|
|
352
|
+
// @ts-ignore - wx在小程序环境可用
|
|
353
|
+
console.log('[MCP-DEBUG] ✅ 请求已记录, pending队列:', wx.__pendingQueue.length, ', 日志数:', wx.__networkLogs.length);
|
|
354
|
+
return config; // 必须返回config继续请求链
|
|
355
|
+
});
|
|
356
|
+
// 响应拦截器 - 使用Pending队列匹配请求/响应
|
|
357
|
+
// @ts-ignore
|
|
358
|
+
getApp().$xfetch.interceptors.response.use(function onSuccess(data) {
|
|
359
|
+
try {
|
|
360
|
+
// 注意: data可能只是业务数据(如{goodsList, tripId}),而不是完整的response对象
|
|
361
|
+
// 因为业务拦截器(commonResInterceptor)改变了响应结构
|
|
362
|
+
console.log('[MCP-DEBUG] 🟢 响应拦截器被触发(成功)');
|
|
363
|
+
console.log('[MCP-DEBUG] 🔍 响应数据类型:', typeof data, ', 键:', Object.keys(data || {}));
|
|
364
|
+
// 从Pending队列获取最早的请求(FIFO匹配)
|
|
365
|
+
// @ts-ignore
|
|
366
|
+
const requestInfo = wx.__pendingQueue.shift();
|
|
367
|
+
if (!requestInfo) {
|
|
368
|
+
console.log('[MCP-DEBUG] ⚠️ Pending队列为空,无法匹配请求');
|
|
369
|
+
return data;
|
|
370
|
+
}
|
|
371
|
+
const duration = Date.now() - requestInfo.startTime;
|
|
372
|
+
console.log('[MCP-DEBUG] 📦 从队列取出请求:', {
|
|
373
|
+
requestId: requestInfo.id,
|
|
374
|
+
url: requestInfo.url,
|
|
375
|
+
method: requestInfo.method,
|
|
376
|
+
duration: duration + 'ms'
|
|
377
|
+
});
|
|
378
|
+
// 从缓存获取完整的请求配置
|
|
379
|
+
// @ts-ignore
|
|
380
|
+
const savedConfig = wx.__requestConfigMap[requestInfo.id];
|
|
381
|
+
if (!savedConfig) {
|
|
382
|
+
console.log('[MCP-DEBUG] ⚠️ 未找到缓存的config');
|
|
383
|
+
}
|
|
384
|
+
// @ts-ignore
|
|
385
|
+
// 找到对应的日志记录并更新
|
|
386
|
+
let logIndex = wx.__networkLogs.findIndex((log) => log.id === requestInfo.id);
|
|
387
|
+
// 增强:如果按ID找不到,尝试按URL和时间窗口匹配(fallback策略)
|
|
388
|
+
if (logIndex === -1) {
|
|
389
|
+
console.log('[MCP-DEBUG] ⚠️ 按ID未找到日志,尝试URL匹配...');
|
|
390
|
+
// @ts-ignore
|
|
391
|
+
logIndex = wx.__networkLogs.findIndex((log) => log.url === requestInfo.url &&
|
|
392
|
+
log.pending === true &&
|
|
393
|
+
Math.abs(new Date(log.timestamp).getTime() - requestInfo.startTime) < 10000 // 10秒窗口
|
|
394
|
+
);
|
|
395
|
+
if (logIndex !== -1) {
|
|
396
|
+
console.log('[MCP-DEBUG] ✅ 通过URL匹配找到日志, 索引:', logIndex);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (logIndex !== -1) {
|
|
400
|
+
// @ts-ignore
|
|
401
|
+
const existingLog = wx.__networkLogs[logIndex];
|
|
402
|
+
// @ts-ignore
|
|
403
|
+
wx.__networkLogs[logIndex] = {
|
|
404
|
+
...existingLog,
|
|
405
|
+
statusCode: 200, // 能到这里说明成功
|
|
406
|
+
response: data, // 只能拿到业务数据
|
|
407
|
+
duration: duration,
|
|
408
|
+
completedAt: new Date().toISOString(),
|
|
409
|
+
pending: false,
|
|
410
|
+
success: true
|
|
411
|
+
};
|
|
412
|
+
console.log('[MCP-DEBUG] ✅ 请求记录已更新 (合并响应), 索引:', logIndex);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
console.log('[MCP-DEBUG] ❌ 完全未找到匹配的日志记录, requestId:', requestInfo.id, ', url:', requestInfo.url);
|
|
416
|
+
}
|
|
417
|
+
// 清理config缓存
|
|
418
|
+
// @ts-ignore
|
|
419
|
+
if (savedConfig) {
|
|
420
|
+
// @ts-ignore
|
|
421
|
+
delete wx.__requestConfigMap[requestInfo.id];
|
|
422
|
+
}
|
|
423
|
+
// @ts-ignore - wx在小程序环境可用
|
|
424
|
+
console.log('[MCP-DEBUG] 📊 状态 - 日志:', wx.__networkLogs.length, ', pending:', wx.__pendingQueue.length, ', config缓存:', Object.keys(wx.__requestConfigMap || {}).length);
|
|
425
|
+
return data; // 必须返回data继续拦截器链
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
console.log('[MCP-DEBUG] ❌ 响应拦截器异常:', error);
|
|
429
|
+
return data; // 即使出错也要返回data,不能中断业务逻辑
|
|
430
|
+
}
|
|
431
|
+
}, function onError(error) {
|
|
432
|
+
try {
|
|
433
|
+
console.log('[MCP-DEBUG] 🔴 响应拦截器被触发(错误)');
|
|
434
|
+
console.log('[MCP-DEBUG] 🔍 错误对象:', error);
|
|
435
|
+
// 从Pending队列获取最早的请求(FIFO匹配)
|
|
436
|
+
// @ts-ignore
|
|
437
|
+
const requestInfo = wx.__pendingQueue.shift();
|
|
438
|
+
if (!requestInfo) {
|
|
439
|
+
console.log('[MCP-DEBUG] ⚠️ Pending队列为空,无法匹配错误请求');
|
|
440
|
+
return Promise.reject(error);
|
|
441
|
+
}
|
|
442
|
+
const duration = Date.now() - requestInfo.startTime;
|
|
443
|
+
console.log('[MCP-DEBUG] 📦 从队列取出请求(错误):', {
|
|
444
|
+
requestId: requestInfo.id,
|
|
445
|
+
url: requestInfo.url,
|
|
446
|
+
error: error.errMsg || error.msg || error.message || String(error),
|
|
447
|
+
duration: duration + 'ms'
|
|
448
|
+
});
|
|
449
|
+
// @ts-ignore
|
|
450
|
+
// 找到对应的日志记录并更新
|
|
451
|
+
let logIndex = wx.__networkLogs.findIndex((log) => log.id === requestInfo.id);
|
|
452
|
+
// 增强:如果按ID找不到,尝试按URL和时间窗口匹配(fallback策略)
|
|
453
|
+
if (logIndex === -1) {
|
|
454
|
+
console.log('[MCP-DEBUG] ⚠️ 按ID未找到日志(错误场景),尝试URL匹配...');
|
|
455
|
+
// @ts-ignore
|
|
456
|
+
logIndex = wx.__networkLogs.findIndex((log) => log.url === requestInfo.url &&
|
|
457
|
+
log.pending === true &&
|
|
458
|
+
Math.abs(new Date(log.timestamp).getTime() - requestInfo.startTime) < 10000 // 10秒窗口
|
|
459
|
+
);
|
|
460
|
+
if (logIndex !== -1) {
|
|
461
|
+
console.log('[MCP-DEBUG] ✅ 通过URL匹配找到日志(错误场景), 索引:', logIndex);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
if (logIndex !== -1) {
|
|
465
|
+
// @ts-ignore
|
|
466
|
+
const existingLog = wx.__networkLogs[logIndex];
|
|
467
|
+
// @ts-ignore
|
|
468
|
+
wx.__networkLogs[logIndex] = {
|
|
469
|
+
...existingLog,
|
|
470
|
+
error: error.errMsg || error.msg || error.message || String(error),
|
|
471
|
+
statusCode: error.status || error.statusCode,
|
|
472
|
+
duration: duration,
|
|
473
|
+
completedAt: new Date().toISOString(),
|
|
474
|
+
pending: false,
|
|
475
|
+
success: false
|
|
476
|
+
};
|
|
477
|
+
console.log('[MCP-DEBUG] ✅ 请求记录已更新 (合并错误), 索引:', logIndex);
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
console.log('[MCP-DEBUG] ❌ 完全未找到匹配的日志记录(错误场景), requestId:', requestInfo.id, ', url:', requestInfo.url);
|
|
481
|
+
}
|
|
482
|
+
// 清理config缓存
|
|
483
|
+
// @ts-ignore
|
|
484
|
+
if (wx.__requestConfigMap && wx.__requestConfigMap[requestInfo.id]) {
|
|
485
|
+
// @ts-ignore
|
|
486
|
+
delete wx.__requestConfigMap[requestInfo.id];
|
|
487
|
+
}
|
|
488
|
+
// @ts-ignore - wx在小程序环境可用
|
|
489
|
+
console.log('[MCP-DEBUG] 📊 状态 - 日志:', wx.__networkLogs.length, ', pending:', wx.__pendingQueue.length);
|
|
490
|
+
return Promise.reject(error); // 保持错误传播
|
|
491
|
+
}
|
|
492
|
+
catch (innerError) {
|
|
493
|
+
console.log('[MCP-DEBUG] ❌ 错误拦截器异常:', innerError);
|
|
494
|
+
return Promise.reject(error); // 即使出错也要传播原始错误,不能中断业务逻辑
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
498
|
+
wx.__networkInterceptorsInstalled = 'mpx';
|
|
499
|
+
console.log('[MCP] ✅ Mpx拦截器安装完成');
|
|
500
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
501
|
+
console.log('[MCP-DEBUG] 拦截器已标记为已安装: wx.__networkInterceptorsInstalled =', wx.__networkInterceptorsInstalled);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
console.log('[MCP] ⚠️ 未检测到Mpx框架或$xfetch不可用');
|
|
505
|
+
}
|
|
506
|
+
// ===== 模式2:wx.request回退方案(用于非Mpx框架或直接调用wx API的场景) =====
|
|
507
|
+
if (!hasMpxFetch) {
|
|
508
|
+
console.log('[MCP] ⚠️ 未检测到Mpx框架,使用wx.request拦截模式');
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
511
|
+
console.log('[MCP-DEBUG] Mpx模式下,同时安装wx.request回退拦截器(双保险)');
|
|
512
|
+
}
|
|
513
|
+
// 保存原始方法引用(通过getter获取)
|
|
514
|
+
// @ts-ignore
|
|
515
|
+
const _originalRequest = wx.request;
|
|
516
|
+
// @ts-ignore
|
|
517
|
+
const _originalUploadFile = wx.uploadFile;
|
|
518
|
+
// @ts-ignore
|
|
519
|
+
const _originalDownloadFile = wx.downloadFile;
|
|
520
|
+
console.log('[MCP-DEBUG] 原始方法类型:', {
|
|
521
|
+
requestType: typeof _originalRequest,
|
|
522
|
+
uploadFileType: typeof _originalUploadFile,
|
|
523
|
+
downloadFileType: typeof _originalDownloadFile
|
|
524
|
+
});
|
|
525
|
+
// 拦截 wx.request
|
|
526
|
+
// 关键:先删除getter属性,然后重新定义为普通属性
|
|
527
|
+
// @ts-ignore
|
|
528
|
+
delete wx.request;
|
|
529
|
+
// @ts-ignore
|
|
530
|
+
Object.defineProperty(wx, 'request', {
|
|
531
|
+
configurable: true,
|
|
532
|
+
enumerable: true,
|
|
533
|
+
writable: true,
|
|
534
|
+
value: function (options) {
|
|
535
|
+
const requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
|
|
536
|
+
const startTime = Date.now();
|
|
537
|
+
console.log('[MCP-DEBUG] 🔵 wx.request 被调用:', {
|
|
538
|
+
requestId: requestId,
|
|
539
|
+
method: options.method || 'GET',
|
|
540
|
+
url: options.url,
|
|
541
|
+
hasData: !!options.data,
|
|
542
|
+
timestamp: new Date().toISOString()
|
|
543
|
+
});
|
|
544
|
+
// 包装success回调
|
|
545
|
+
const originalSuccess = options.success;
|
|
546
|
+
options.success = function (res) {
|
|
547
|
+
console.log('[MCP-DEBUG] 🟢 wx.request 成功回调:', {
|
|
548
|
+
requestId: requestId,
|
|
549
|
+
statusCode: res.statusCode,
|
|
550
|
+
duration: Date.now() - startTime
|
|
551
|
+
});
|
|
552
|
+
// @ts-ignore
|
|
553
|
+
wx.__networkLogs.push({
|
|
554
|
+
id: requestId,
|
|
555
|
+
type: 'request',
|
|
556
|
+
url: options.url,
|
|
557
|
+
method: options.method || 'GET',
|
|
558
|
+
headers: options.header,
|
|
559
|
+
data: options.data,
|
|
560
|
+
statusCode: res.statusCode,
|
|
561
|
+
response: res.data,
|
|
562
|
+
duration: Date.now() - startTime,
|
|
563
|
+
timestamp: new Date().toISOString(),
|
|
564
|
+
source: 'wx.request',
|
|
565
|
+
success: true
|
|
566
|
+
});
|
|
567
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
568
|
+
console.log('[MCP-DEBUG] ✅ wx.request 已记录, 当前总数:', wx.__networkLogs.length);
|
|
569
|
+
if (originalSuccess)
|
|
570
|
+
originalSuccess.call(this, res);
|
|
571
|
+
};
|
|
572
|
+
// 包装fail回调
|
|
573
|
+
const originalFail = options.fail;
|
|
574
|
+
options.fail = function (err) {
|
|
575
|
+
console.log('[MCP-DEBUG] 🔴 wx.request 失败回调:', {
|
|
576
|
+
requestId: requestId,
|
|
577
|
+
error: err.errMsg,
|
|
578
|
+
duration: Date.now() - startTime
|
|
579
|
+
});
|
|
580
|
+
// @ts-ignore
|
|
581
|
+
wx.__networkLogs.push({
|
|
582
|
+
id: requestId,
|
|
583
|
+
type: 'request',
|
|
584
|
+
url: options.url,
|
|
585
|
+
method: options.method || 'GET',
|
|
586
|
+
headers: options.header,
|
|
587
|
+
data: options.data,
|
|
588
|
+
error: err.errMsg || String(err),
|
|
589
|
+
duration: Date.now() - startTime,
|
|
590
|
+
timestamp: new Date().toISOString(),
|
|
591
|
+
source: 'wx.request',
|
|
592
|
+
success: false
|
|
593
|
+
});
|
|
594
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
595
|
+
console.log('[MCP-DEBUG] ✅ wx.request 错误已记录, 当前总数:', wx.__networkLogs.length);
|
|
596
|
+
if (originalFail)
|
|
597
|
+
originalFail.call(this, err);
|
|
598
|
+
};
|
|
599
|
+
// 调用原始方法
|
|
600
|
+
return _originalRequest.call(this, options);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
console.log('[MCP-DEBUG] ✅ wx.request 拦截器已安装');
|
|
604
|
+
// 拦截 wx.uploadFile
|
|
605
|
+
// 关键:先删除getter属性
|
|
606
|
+
// @ts-ignore
|
|
607
|
+
delete wx.uploadFile;
|
|
608
|
+
// @ts-ignore
|
|
609
|
+
Object.defineProperty(wx, 'uploadFile', {
|
|
610
|
+
configurable: true,
|
|
611
|
+
enumerable: true,
|
|
612
|
+
writable: true,
|
|
613
|
+
value: function (options) {
|
|
614
|
+
const requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
|
|
615
|
+
const startTime = Date.now();
|
|
616
|
+
const originalSuccess = options.success;
|
|
617
|
+
options.success = function (res) {
|
|
618
|
+
// @ts-ignore
|
|
619
|
+
wx.__networkLogs.push({
|
|
620
|
+
id: requestId,
|
|
621
|
+
type: 'uploadFile',
|
|
622
|
+
url: options.url,
|
|
623
|
+
headers: options.header,
|
|
624
|
+
data: {
|
|
625
|
+
filePath: options.filePath,
|
|
626
|
+
name: options.name,
|
|
627
|
+
formData: options.formData
|
|
628
|
+
},
|
|
629
|
+
statusCode: res.statusCode,
|
|
630
|
+
response: res.data,
|
|
631
|
+
duration: Date.now() - startTime,
|
|
632
|
+
timestamp: new Date().toISOString(),
|
|
633
|
+
source: 'wx.uploadFile',
|
|
634
|
+
success: true
|
|
635
|
+
});
|
|
636
|
+
if (originalSuccess)
|
|
637
|
+
originalSuccess.call(this, res);
|
|
638
|
+
};
|
|
639
|
+
const originalFail = options.fail;
|
|
640
|
+
options.fail = function (err) {
|
|
641
|
+
// @ts-ignore
|
|
642
|
+
wx.__networkLogs.push({
|
|
643
|
+
id: requestId,
|
|
644
|
+
type: 'uploadFile',
|
|
645
|
+
url: options.url,
|
|
646
|
+
headers: options.header,
|
|
647
|
+
data: {
|
|
648
|
+
filePath: options.filePath,
|
|
649
|
+
name: options.name,
|
|
650
|
+
formData: options.formData
|
|
651
|
+
},
|
|
652
|
+
error: err.errMsg || String(err),
|
|
653
|
+
duration: Date.now() - startTime,
|
|
654
|
+
timestamp: new Date().toISOString(),
|
|
655
|
+
source: 'wx.uploadFile',
|
|
656
|
+
success: false
|
|
657
|
+
});
|
|
658
|
+
if (originalFail)
|
|
659
|
+
originalFail.call(this, err);
|
|
660
|
+
};
|
|
661
|
+
return _originalUploadFile.call(this, options);
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
// 拦截 wx.downloadFile
|
|
665
|
+
// 关键:先删除getter属性
|
|
666
|
+
// @ts-ignore
|
|
667
|
+
delete wx.downloadFile;
|
|
668
|
+
// @ts-ignore
|
|
669
|
+
Object.defineProperty(wx, 'downloadFile', {
|
|
670
|
+
configurable: true,
|
|
671
|
+
enumerable: true,
|
|
672
|
+
writable: true,
|
|
673
|
+
value: function (options) {
|
|
674
|
+
const requestId = 'req_' + Date.now() + '_' + Math.random().toString(36).substring(2, 9);
|
|
675
|
+
const startTime = Date.now();
|
|
676
|
+
const originalSuccess = options.success;
|
|
677
|
+
options.success = function (res) {
|
|
678
|
+
// @ts-ignore
|
|
679
|
+
wx.__networkLogs.push({
|
|
680
|
+
id: requestId,
|
|
681
|
+
type: 'downloadFile',
|
|
682
|
+
url: options.url,
|
|
683
|
+
headers: options.header,
|
|
684
|
+
statusCode: res.statusCode,
|
|
685
|
+
response: {
|
|
686
|
+
tempFilePath: res.tempFilePath,
|
|
687
|
+
filePath: res.filePath
|
|
688
|
+
},
|
|
689
|
+
duration: Date.now() - startTime,
|
|
690
|
+
timestamp: new Date().toISOString(),
|
|
691
|
+
source: 'wx.downloadFile',
|
|
692
|
+
success: true
|
|
693
|
+
});
|
|
694
|
+
if (originalSuccess)
|
|
695
|
+
originalSuccess.call(this, res);
|
|
696
|
+
};
|
|
697
|
+
const originalFail = options.fail;
|
|
698
|
+
options.fail = function (err) {
|
|
699
|
+
// @ts-ignore
|
|
700
|
+
wx.__networkLogs.push({
|
|
701
|
+
id: requestId,
|
|
702
|
+
type: 'downloadFile',
|
|
703
|
+
url: options.url,
|
|
704
|
+
headers: options.header,
|
|
705
|
+
error: err.errMsg || String(err),
|
|
706
|
+
duration: Date.now() - startTime,
|
|
707
|
+
timestamp: new Date().toISOString(),
|
|
708
|
+
source: 'wx.downloadFile',
|
|
709
|
+
success: false
|
|
710
|
+
});
|
|
711
|
+
if (originalFail)
|
|
712
|
+
originalFail.call(this, err);
|
|
713
|
+
};
|
|
714
|
+
return _originalDownloadFile.call(this, options);
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
// 标记拦截器已安装
|
|
718
|
+
// @ts-ignore
|
|
719
|
+
wx.__networkInterceptorsInstalled = true;
|
|
720
|
+
}, clearExisting);
|
|
721
|
+
// 设置监听状态
|
|
722
|
+
context.networkStorage.isMonitoring = true;
|
|
723
|
+
context.networkStorage.startTime = new Date().toISOString();
|
|
724
|
+
response.appendResponseLine('✅ 网络监听已启动(使用增强型拦截)');
|
|
725
|
+
response.appendResponseLine(`监听开始时间: ${context.networkStorage.startTime}`);
|
|
726
|
+
response.appendResponseLine(`清除历史记录: ${clearExisting ? '是' : '否'}`);
|
|
727
|
+
response.appendResponseLine('');
|
|
728
|
+
response.appendResponseLine('已拦截以下方法:');
|
|
729
|
+
response.appendResponseLine(' - wx.request');
|
|
730
|
+
response.appendResponseLine(' - wx.uploadFile');
|
|
731
|
+
response.appendResponseLine(' - wx.downloadFile');
|
|
732
|
+
response.appendResponseLine('');
|
|
733
|
+
response.appendResponseLine('💡 使用 evaluate() 方式注入,可绕过 Mpx 等框架限制');
|
|
734
|
+
response.appendResponseLine(' 所有网络请求都将被捕获,使用 get_network_requests 查看');
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
738
|
+
throw new Error(`启动网络监听失败: ${errorMessage}`);
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
});
|
|
742
|
+
/**
|
|
743
|
+
* 停止网络监听工具
|
|
744
|
+
*
|
|
745
|
+
* 注意:使用evaluate()注入的拦截器无法完全恢复
|
|
746
|
+
* 只能清除标记,实际拦截器会继续工作
|
|
747
|
+
*/
|
|
748
|
+
export const stopNetworkMonitoringTool = defineTool({
|
|
749
|
+
name: 'stop_network_monitoring',
|
|
750
|
+
description: '停止对微信小程序网络请求的监听,恢复原始的网络方法',
|
|
751
|
+
schema: z.object({}),
|
|
752
|
+
annotations: {
|
|
753
|
+
audience: ['developers'],
|
|
754
|
+
},
|
|
755
|
+
handler: async (request, response, context) => {
|
|
756
|
+
if (!context.miniProgram) {
|
|
757
|
+
throw new Error('请先连接到微信开发者工具');
|
|
758
|
+
}
|
|
759
|
+
if (!context.networkStorage.isMonitoring) {
|
|
760
|
+
response.appendResponseLine('网络监听未在运行');
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
// 从小程序环境读取最终的请求数据并清除标记
|
|
765
|
+
const result = await context.miniProgram.evaluate(function () {
|
|
766
|
+
// @ts-ignore
|
|
767
|
+
const wxObj = typeof wx !== 'undefined' ? wx : null;
|
|
768
|
+
if (!wxObj) {
|
|
769
|
+
return { logs: [], success: false };
|
|
770
|
+
}
|
|
771
|
+
const logs = wxObj.__networkLogs || [];
|
|
772
|
+
// 清除安装标记(允许重新安装)
|
|
773
|
+
// 注意:实际的拦截器无法恢复,因为我们使用了Object.defineProperty
|
|
774
|
+
// 这是evaluate()方式的一个限制,但好处是可以绕过框架缓存
|
|
775
|
+
wxObj.__networkInterceptorsInstalled = false;
|
|
776
|
+
return { logs, success: true };
|
|
777
|
+
});
|
|
778
|
+
if (!result.success) {
|
|
779
|
+
throw new Error('无法访问wx对象');
|
|
780
|
+
}
|
|
781
|
+
const logs = result.logs;
|
|
782
|
+
// 更新监听状态
|
|
783
|
+
context.networkStorage.isMonitoring = false;
|
|
784
|
+
response.appendResponseLine('✅ 网络监听已停止');
|
|
785
|
+
response.appendResponseLine(`监听期间收集到 ${logs.length} 个网络请求`);
|
|
786
|
+
// 统计各类型请求数量
|
|
787
|
+
const stats = logs.reduce((acc, req) => {
|
|
788
|
+
acc[req.type] = (acc[req.type] || 0) + 1;
|
|
789
|
+
return acc;
|
|
790
|
+
}, {});
|
|
791
|
+
response.appendResponseLine('');
|
|
792
|
+
response.appendResponseLine('请求类型统计:');
|
|
793
|
+
if (stats.request)
|
|
794
|
+
response.appendResponseLine(` - request: ${stats.request}`);
|
|
795
|
+
if (stats.uploadFile)
|
|
796
|
+
response.appendResponseLine(` - uploadFile: ${stats.uploadFile}`);
|
|
797
|
+
if (stats.downloadFile)
|
|
798
|
+
response.appendResponseLine(` - downloadFile: ${stats.downloadFile}`);
|
|
799
|
+
response.appendResponseLine('');
|
|
800
|
+
response.appendResponseLine('⚠️ 注意: 拦截器将继续工作(evaluate方式的特性)');
|
|
801
|
+
response.appendResponseLine(' 使用 clear_network_requests 清除数据');
|
|
802
|
+
response.appendResponseLine(' 使用 start_network_monitoring 重新开始记录');
|
|
803
|
+
}
|
|
804
|
+
catch (error) {
|
|
805
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
806
|
+
throw new Error(`停止网络监听失败: ${errorMessage}`);
|
|
807
|
+
}
|
|
808
|
+
},
|
|
809
|
+
});
|
|
810
|
+
/**
|
|
811
|
+
* 获取网络请求工具
|
|
812
|
+
*/
|
|
813
|
+
export const getNetworkRequestsTool = defineTool({
|
|
814
|
+
name: 'get_network_requests',
|
|
815
|
+
description: '获取收集到的网络请求记录,支持按类型、URL、状态过滤',
|
|
816
|
+
schema: z.object({
|
|
817
|
+
type: z.enum(['all', 'request', 'uploadFile', 'downloadFile']).optional().default('all').describe('请求类型过滤'),
|
|
818
|
+
urlPattern: z.string().optional().describe('URL 匹配模式(支持正则表达式)'),
|
|
819
|
+
successOnly: z.boolean().optional().default(false).describe('仅返回成功的请求'),
|
|
820
|
+
limit: z.number().optional().default(50).describe('限制返回条数'),
|
|
821
|
+
since: z.string().optional().describe('获取指定时间之后的记录,格式:ISO 8601'),
|
|
822
|
+
}),
|
|
823
|
+
annotations: {
|
|
824
|
+
audience: ['developers'],
|
|
825
|
+
},
|
|
826
|
+
handler: async (request, response, context) => {
|
|
827
|
+
const { type, urlPattern, successOnly, limit, since } = request.params;
|
|
828
|
+
if (!context.miniProgram) {
|
|
829
|
+
throw new Error('请先连接到微信开发者工具');
|
|
830
|
+
}
|
|
831
|
+
if (!context.networkStorage) {
|
|
832
|
+
throw new Error('网络存储未初始化');
|
|
833
|
+
}
|
|
834
|
+
try {
|
|
835
|
+
// 从小程序环境读取网络请求数据
|
|
836
|
+
const logs = await context.miniProgram.evaluate(function () {
|
|
837
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
838
|
+
const wxObj = typeof wx !== 'undefined' ? wx : null;
|
|
839
|
+
return wxObj?.__networkLogs || [];
|
|
840
|
+
});
|
|
841
|
+
const sinceTime = since ? new Date(since) : null;
|
|
842
|
+
const urlRegex = urlPattern ? new RegExp(urlPattern) : null;
|
|
843
|
+
// 过滤函数
|
|
844
|
+
const filters = [
|
|
845
|
+
// 过滤无效记录(type='response' 或 url为空/undefined)
|
|
846
|
+
(req) => {
|
|
847
|
+
// 过滤掉 type='response' 的记录(不应该存在)
|
|
848
|
+
if (req.type === 'response') {
|
|
849
|
+
return false;
|
|
850
|
+
}
|
|
851
|
+
// 过滤掉 URL 为空或 'undefined' 的记录
|
|
852
|
+
if (!req.url || req.url === 'undefined') {
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
// 过滤掉 ID 为空或 'N/A' 的记录
|
|
856
|
+
if (!req.id || req.id === 'N/A') {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
return true;
|
|
860
|
+
},
|
|
861
|
+
// 类型过滤
|
|
862
|
+
(req) => type === 'all' || req.type === type,
|
|
863
|
+
// 时间过滤
|
|
864
|
+
(req) => !sinceTime || new Date(req.timestamp) >= sinceTime,
|
|
865
|
+
// URL 过滤
|
|
866
|
+
(req) => !urlRegex || urlRegex.test(req.url),
|
|
867
|
+
// 成功状态过滤
|
|
868
|
+
(req) => !successOnly || req.success,
|
|
869
|
+
];
|
|
870
|
+
const filteredRequests = logs
|
|
871
|
+
.filter(req => filters.every(filter => filter(req)))
|
|
872
|
+
.slice(-limit);
|
|
873
|
+
// 生成响应
|
|
874
|
+
response.appendResponseLine('=== 网络请求记录 ===');
|
|
875
|
+
response.appendResponseLine(`监听状态: ${context.networkStorage.isMonitoring ? '运行中' : '已停止'}`);
|
|
876
|
+
response.appendResponseLine(`监听开始时间: ${context.networkStorage.startTime || '未设置'}`);
|
|
877
|
+
response.appendResponseLine(`总请求数: ${logs.length}`);
|
|
878
|
+
response.appendResponseLine(`过滤后: ${filteredRequests.length} 条`);
|
|
879
|
+
response.appendResponseLine('');
|
|
880
|
+
if (filteredRequests.length === 0) {
|
|
881
|
+
response.appendResponseLine('暂无符合条件的网络请求记录');
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
filteredRequests.forEach((req, index) => {
|
|
885
|
+
response.appendResponseLine(`--- 请求 ${index + 1} ---`);
|
|
886
|
+
response.appendResponseLine(`ID: ${req.id || 'N/A'}`);
|
|
887
|
+
response.appendResponseLine(`类型: ${req.type}`);
|
|
888
|
+
// 过滤掉旧的、无效的记录
|
|
889
|
+
if (!req.url || req.url === 'undefined') {
|
|
890
|
+
response.appendResponseLine(`⚠️ 无效记录(可能是旧数据)`);
|
|
891
|
+
response.appendResponseLine('');
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
response.appendResponseLine(`URL: ${req.url}`);
|
|
895
|
+
if (req.method) {
|
|
896
|
+
response.appendResponseLine(`方法: ${req.method}`);
|
|
897
|
+
}
|
|
898
|
+
// 优化的状态判断逻辑
|
|
899
|
+
const isPending = req.pending === true;
|
|
900
|
+
const isCompleted = req.pending === false;
|
|
901
|
+
const isSuccess = req.success === true;
|
|
902
|
+
const isFailed = req.success === false;
|
|
903
|
+
if (isPending) {
|
|
904
|
+
response.appendResponseLine(`状态: ⏳ 请求中(未收到响应)`);
|
|
905
|
+
}
|
|
906
|
+
else if (isCompleted) {
|
|
907
|
+
if (isSuccess) {
|
|
908
|
+
response.appendResponseLine(`状态: ✅ 成功`);
|
|
909
|
+
}
|
|
910
|
+
else if (isFailed) {
|
|
911
|
+
response.appendResponseLine(`状态: ❌ 失败`);
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
response.appendResponseLine(`状态: ⚠️ 未知(success=${req.success})`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
else {
|
|
918
|
+
// 兼容旧格式(wx.request等,没有pending字段)
|
|
919
|
+
if (isSuccess) {
|
|
920
|
+
response.appendResponseLine(`状态: ✅ 成功`);
|
|
921
|
+
}
|
|
922
|
+
else if (isFailed) {
|
|
923
|
+
response.appendResponseLine(`状态: ❌ 失败`);
|
|
924
|
+
}
|
|
925
|
+
else {
|
|
926
|
+
response.appendResponseLine(`状态: ⚠️ 未知状态`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
if (req.statusCode) {
|
|
930
|
+
response.appendResponseLine(`状态码: ${req.statusCode}`);
|
|
931
|
+
}
|
|
932
|
+
if (req.duration !== undefined) {
|
|
933
|
+
response.appendResponseLine(`耗时: ${req.duration}ms`);
|
|
934
|
+
}
|
|
935
|
+
response.appendResponseLine(`时间: ${req.timestamp}`);
|
|
936
|
+
if (req.source) {
|
|
937
|
+
response.appendResponseLine(`来源: ${req.source}`);
|
|
938
|
+
}
|
|
939
|
+
// === 请求信息 ===
|
|
940
|
+
if (req.headers && Object.keys(req.headers).length > 0) {
|
|
941
|
+
response.appendResponseLine(`请求头: ${JSON.stringify(req.headers)}`);
|
|
942
|
+
}
|
|
943
|
+
if (req.data) {
|
|
944
|
+
const dataStr = typeof req.data === 'string'
|
|
945
|
+
? req.data
|
|
946
|
+
: JSON.stringify(req.data);
|
|
947
|
+
const truncatedData = dataStr.length > 200
|
|
948
|
+
? dataStr.substring(0, 200) + '...'
|
|
949
|
+
: dataStr;
|
|
950
|
+
response.appendResponseLine(`请求数据: ${truncatedData}`);
|
|
951
|
+
}
|
|
952
|
+
if (req.params) {
|
|
953
|
+
response.appendResponseLine(`请求参数: ${JSON.stringify(req.params)}`);
|
|
954
|
+
}
|
|
955
|
+
// === 响应信息 ===
|
|
956
|
+
if (req.response) {
|
|
957
|
+
const respStr = typeof req.response === 'string'
|
|
958
|
+
? req.response
|
|
959
|
+
: JSON.stringify(req.response);
|
|
960
|
+
const truncatedResp = respStr.length > 200
|
|
961
|
+
? respStr.substring(0, 200) + '...'
|
|
962
|
+
: respStr;
|
|
963
|
+
response.appendResponseLine(`响应数据: ${truncatedResp}`);
|
|
964
|
+
}
|
|
965
|
+
if (req.responseHeaders && Object.keys(req.responseHeaders).length > 0) {
|
|
966
|
+
response.appendResponseLine(`响应头: ${JSON.stringify(req.responseHeaders)}`);
|
|
967
|
+
}
|
|
968
|
+
if (req.error) {
|
|
969
|
+
response.appendResponseLine(`错误信息: ${req.error}`);
|
|
970
|
+
}
|
|
971
|
+
if (req.completedAt) {
|
|
972
|
+
response.appendResponseLine(`完成时间: ${req.completedAt}`);
|
|
973
|
+
}
|
|
974
|
+
response.appendResponseLine('');
|
|
975
|
+
});
|
|
976
|
+
response.appendResponseLine('=== 获取完成 ===');
|
|
977
|
+
}
|
|
978
|
+
catch (error) {
|
|
979
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
980
|
+
throw new Error(`获取网络请求失败: ${errorMessage}`);
|
|
981
|
+
}
|
|
982
|
+
},
|
|
983
|
+
});
|
|
984
|
+
/**
|
|
985
|
+
* 诊断拦截器状态工具 - 用于调试
|
|
986
|
+
*/
|
|
987
|
+
export const diagnoseInterceptorTool = defineTool({
|
|
988
|
+
name: 'diagnose_interceptor',
|
|
989
|
+
description: '诊断网络拦截器安装状态和运行情况',
|
|
990
|
+
schema: z.object({}),
|
|
991
|
+
annotations: {
|
|
992
|
+
audience: ['developers'],
|
|
993
|
+
},
|
|
994
|
+
handler: async (request, response, context) => {
|
|
995
|
+
if (!context.miniProgram) {
|
|
996
|
+
throw new Error('请先连接到微信开发者工具');
|
|
997
|
+
}
|
|
998
|
+
try {
|
|
999
|
+
const result = await context.miniProgram.evaluate(() => {
|
|
1000
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
1001
|
+
const wxObj = typeof wx !== 'undefined' ? wx : null;
|
|
1002
|
+
// 测试console.log
|
|
1003
|
+
console.log('[INTERCEPTOR-DIAGNOSE] === 开始诊断拦截器 ===');
|
|
1004
|
+
console.log('[INTERCEPTOR-DIAGNOSE] wx对象存在:', !!wxObj);
|
|
1005
|
+
// @ts-ignore - getApp is available in WeChat miniprogram environment
|
|
1006
|
+
const hasGetApp = typeof getApp !== 'undefined';
|
|
1007
|
+
// @ts-ignore - getApp is available in WeChat miniprogram environment
|
|
1008
|
+
const app = hasGetApp ? getApp() : null;
|
|
1009
|
+
const diagnosticInfo = {
|
|
1010
|
+
environment: {
|
|
1011
|
+
hasWx: !!wxObj,
|
|
1012
|
+
hasGetApp: hasGetApp,
|
|
1013
|
+
},
|
|
1014
|
+
interceptor: {
|
|
1015
|
+
installed: !!(wxObj && wxObj.__networkInterceptorsInstalled),
|
|
1016
|
+
hasNetworkLogs: !!(wxObj && wxObj.__networkLogs),
|
|
1017
|
+
networkLogsLength: wxObj && wxObj.__networkLogs ? wxObj.__networkLogs.length : 0,
|
|
1018
|
+
},
|
|
1019
|
+
mpx: {
|
|
1020
|
+
hasGetApp: hasGetApp,
|
|
1021
|
+
hasApp: !!app,
|
|
1022
|
+
has$xfetch: !!(app && app.$xfetch),
|
|
1023
|
+
},
|
|
1024
|
+
networkLogs: wxObj && wxObj.__networkLogs ? wxObj.__networkLogs.slice(-5) : [],
|
|
1025
|
+
};
|
|
1026
|
+
console.log('[INTERCEPTOR-DIAGNOSE] 诊断信息:', JSON.stringify(diagnosticInfo, null, 2));
|
|
1027
|
+
console.log('[INTERCEPTOR-DIAGNOSE] === 诊断完成 ===');
|
|
1028
|
+
return diagnosticInfo;
|
|
1029
|
+
});
|
|
1030
|
+
response.appendResponseLine('=== 拦截器诊断结果 ===\n');
|
|
1031
|
+
response.appendResponseLine(`环境检查:`);
|
|
1032
|
+
response.appendResponseLine(` wx对象: ${result.environment.hasWx ? '✅' : '❌'}`);
|
|
1033
|
+
response.appendResponseLine(` getApp: ${result.environment.hasGetApp ? '✅' : '❌'}`);
|
|
1034
|
+
response.appendResponseLine('');
|
|
1035
|
+
response.appendResponseLine(`拦截器状态:`);
|
|
1036
|
+
response.appendResponseLine(` 已安装: ${result.interceptor.installed ? '✅' : '❌'}`);
|
|
1037
|
+
response.appendResponseLine(` 日志数组: ${result.interceptor.hasNetworkLogs ? '✅' : '❌'}`);
|
|
1038
|
+
response.appendResponseLine(` 记录数量: ${result.interceptor.networkLogsLength}`);
|
|
1039
|
+
response.appendResponseLine('');
|
|
1040
|
+
response.appendResponseLine(`Mpx框架:`);
|
|
1041
|
+
response.appendResponseLine(` getApp可用: ${result.mpx.hasGetApp ? '✅' : '❌'}`);
|
|
1042
|
+
response.appendResponseLine(` App实例: ${result.mpx.hasApp ? '✅' : '❌'}`);
|
|
1043
|
+
response.appendResponseLine(` $xfetch: ${result.mpx.has$xfetch ? '✅' : '❌'}`);
|
|
1044
|
+
response.appendResponseLine('');
|
|
1045
|
+
if (result.networkLogs && result.networkLogs.length > 0) {
|
|
1046
|
+
response.appendResponseLine(`最近${result.networkLogs.length}条网络日志:`);
|
|
1047
|
+
result.networkLogs.forEach((log, index) => {
|
|
1048
|
+
response.appendResponseLine(` ${index + 1}. [${log.type}] ${log.url || log.method}`);
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
catch (error) {
|
|
1053
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1054
|
+
throw new Error(`诊断失败: ${errorMessage}`);
|
|
1055
|
+
}
|
|
1056
|
+
},
|
|
1057
|
+
});
|
|
1058
|
+
/**
|
|
1059
|
+
* 清除网络请求工具
|
|
1060
|
+
*/
|
|
1061
|
+
export const clearNetworkRequestsTool = defineTool({
|
|
1062
|
+
name: 'clear_network_requests',
|
|
1063
|
+
description: '清除已收集的网络请求记录',
|
|
1064
|
+
schema: z.object({
|
|
1065
|
+
type: z.enum(['all', 'request', 'uploadFile', 'downloadFile']).optional().default('all').describe('清除的请求类型'),
|
|
1066
|
+
}),
|
|
1067
|
+
annotations: {
|
|
1068
|
+
audience: ['developers'],
|
|
1069
|
+
},
|
|
1070
|
+
handler: async (request, response, context) => {
|
|
1071
|
+
const { type } = request.params;
|
|
1072
|
+
if (!context.miniProgram) {
|
|
1073
|
+
throw new Error('请先连接到微信开发者工具');
|
|
1074
|
+
}
|
|
1075
|
+
if (!context.networkStorage) {
|
|
1076
|
+
throw new Error('网络存储未初始化');
|
|
1077
|
+
}
|
|
1078
|
+
try {
|
|
1079
|
+
// 获取当前数量
|
|
1080
|
+
const beforeCount = await context.miniProgram.evaluate(function () {
|
|
1081
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
1082
|
+
const wxObj = typeof wx !== 'undefined' ? wx : null;
|
|
1083
|
+
return (wxObj?.__networkLogs || []).length;
|
|
1084
|
+
});
|
|
1085
|
+
// 在小程序环境清除数据
|
|
1086
|
+
const afterCount = await context.miniProgram.evaluate(function (typeToDelete) {
|
|
1087
|
+
// @ts-ignore - wx is available in WeChat miniprogram environment
|
|
1088
|
+
const wxObj = typeof wx !== 'undefined' ? wx : null;
|
|
1089
|
+
if (!wxObj || !wxObj.__networkLogs) {
|
|
1090
|
+
return 0;
|
|
1091
|
+
}
|
|
1092
|
+
if (typeToDelete === 'all') {
|
|
1093
|
+
wxObj.__networkLogs = [];
|
|
1094
|
+
}
|
|
1095
|
+
else {
|
|
1096
|
+
wxObj.__networkLogs = wxObj.__networkLogs.filter((req) => req.type !== typeToDelete);
|
|
1097
|
+
}
|
|
1098
|
+
return wxObj.__networkLogs.length;
|
|
1099
|
+
}, type);
|
|
1100
|
+
const clearedCount = beforeCount - afterCount;
|
|
1101
|
+
response.appendResponseLine('✅ 网络请求记录清除完成');
|
|
1102
|
+
response.appendResponseLine(`清除类型: ${type}`);
|
|
1103
|
+
response.appendResponseLine(`清除数量: ${clearedCount} 条`);
|
|
1104
|
+
response.appendResponseLine(`剩余数量: ${afterCount} 条`);
|
|
1105
|
+
}
|
|
1106
|
+
catch (error) {
|
|
1107
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1108
|
+
throw new Error(`清除网络请求失败: ${errorMessage}`);
|
|
1109
|
+
}
|
|
1110
|
+
},
|
|
1111
|
+
});
|