mobile-debug-mcp 0.23.0 → 0.24.0
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/dist/interact/index.js +133 -57
- package/dist/server/common.js +66 -0
- package/dist/server/tool-definitions.js +921 -0
- package/dist/server/tool-handlers.js +320 -0
- package/dist/server-core.js +4 -801
- package/docs/CHANGELOG.md +3 -0
- package/docs/tools/TOOLS.md +15 -7
- package/docs/tools/interact.md +270 -107
- package/docs/tools/manage.md +39 -38
- package/docs/tools/observe.md +30 -8
- package/docs/tools/system.md +1 -1
- package/package.json +1 -1
- package/src/interact/index.ts +186 -58
- package/src/server/common.ts +95 -0
- package/src/server/tool-definitions.ts +921 -0
- package/src/server/tool-handlers.ts +365 -0
- package/src/server-core.ts +4 -844
- package/src/types.ts +59 -6
- package/test/unit/interact/expect_tools.test.ts +77 -0
- package/test/unit/interact/tap_element.test.ts +23 -6
- package/test/unit/server/contract.test.ts +26 -0
- package/test/unit/server/response_shapes.test.ts +69 -4
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
InstallAppResponse,
|
|
3
|
+
ResetAppDataResponse,
|
|
4
|
+
TerminateAppResponse
|
|
5
|
+
} from '../types.js'
|
|
6
|
+
import { AndroidManage, iOSManage, ToolsManage } from '../manage/index.js'
|
|
7
|
+
import { ToolsInteract } from '../interact/index.js'
|
|
8
|
+
import { ToolsObserve } from '../observe/index.js'
|
|
9
|
+
import { classifyActionOutcome } from '../interact/classify.js'
|
|
10
|
+
import { ToolsNetwork } from '../network/index.js'
|
|
11
|
+
import { getSystemStatus } from '../system/index.js'
|
|
12
|
+
import {
|
|
13
|
+
buildActionExecutionResult,
|
|
14
|
+
captureActionFingerprint,
|
|
15
|
+
inferGenericFailure,
|
|
16
|
+
inferScrollFailure,
|
|
17
|
+
ToolCallArgs,
|
|
18
|
+
ToolHandler,
|
|
19
|
+
wrapResponse
|
|
20
|
+
} from './common.js'
|
|
21
|
+
|
|
22
|
+
async function handleStartApp(args: ToolCallArgs) {
|
|
23
|
+
const { platform, appId, deviceId } = args as any
|
|
24
|
+
const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
|
|
25
|
+
ToolsNetwork.notifyActionStart()
|
|
26
|
+
const res = await (platform === 'android' ? new AndroidManage().startApp(appId, deviceId) : new iOSManage().startApp(appId, deviceId))
|
|
27
|
+
const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
|
|
28
|
+
return wrapResponse(buildActionExecutionResult({
|
|
29
|
+
actionType: 'start_app',
|
|
30
|
+
selector: { appId },
|
|
31
|
+
success: !!res.appStarted,
|
|
32
|
+
uiFingerprintBefore,
|
|
33
|
+
uiFingerprintAfter,
|
|
34
|
+
failure: res.appStarted ? undefined : inferGenericFailure((res as any).error)
|
|
35
|
+
}))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function handleTerminateApp(args: ToolCallArgs) {
|
|
39
|
+
const { platform, appId, deviceId } = args as any
|
|
40
|
+
const res = await (platform === 'android' ? new AndroidManage().terminateApp(appId, deviceId) : new iOSManage().terminateApp(appId, deviceId))
|
|
41
|
+
const response: TerminateAppResponse = { device: res.device, appTerminated: res.appTerminated }
|
|
42
|
+
return wrapResponse(response)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function handleRestartApp(args: ToolCallArgs) {
|
|
46
|
+
const { platform, appId, deviceId } = args as any
|
|
47
|
+
const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
|
|
48
|
+
ToolsNetwork.notifyActionStart()
|
|
49
|
+
const res = await (platform === 'android' ? new AndroidManage().restartApp(appId, deviceId) : new iOSManage().restartApp(appId, deviceId))
|
|
50
|
+
const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
|
|
51
|
+
return wrapResponse(buildActionExecutionResult({
|
|
52
|
+
actionType: 'restart_app',
|
|
53
|
+
selector: { appId },
|
|
54
|
+
success: !!res.appRestarted,
|
|
55
|
+
uiFingerprintBefore,
|
|
56
|
+
uiFingerprintAfter,
|
|
57
|
+
failure: res.appRestarted ? undefined : inferGenericFailure((res as any).error)
|
|
58
|
+
}))
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function handleResetAppData(args: ToolCallArgs) {
|
|
62
|
+
const { platform, appId, deviceId } = args as any
|
|
63
|
+
const res = await (platform === 'android' ? new AndroidManage().resetAppData(appId, deviceId) : new iOSManage().resetAppData(appId, deviceId))
|
|
64
|
+
const response: ResetAppDataResponse = { device: res.device, dataCleared: res.dataCleared }
|
|
65
|
+
return wrapResponse(response)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function handleInstallApp(args: ToolCallArgs) {
|
|
69
|
+
const { platform, projectType, appPath, deviceId } = args as any
|
|
70
|
+
const res = await ToolsManage.installAppHandler({ platform, appPath, deviceId, projectType })
|
|
71
|
+
const response: InstallAppResponse = {
|
|
72
|
+
device: res.device,
|
|
73
|
+
installed: res.installed,
|
|
74
|
+
output: (res as any).output,
|
|
75
|
+
error: (res as any).error
|
|
76
|
+
}
|
|
77
|
+
return wrapResponse(response)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function handleBuildApp(args: ToolCallArgs) {
|
|
81
|
+
const { platform, projectType, projectPath, variant } = args as any
|
|
82
|
+
const res = await ToolsManage.buildAppHandler({ platform, projectPath, variant, projectType })
|
|
83
|
+
return wrapResponse(res)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function handleBuildAndInstall(args: ToolCallArgs) {
|
|
87
|
+
const { platform, projectType, projectPath, deviceId, timeout } = args as any
|
|
88
|
+
const res = await ToolsManage.buildAndInstallHandler({ platform, projectPath, deviceId, timeout, projectType })
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{ type: 'text' as const, text: res.ndjson },
|
|
92
|
+
{ type: 'text' as const, text: JSON.stringify(res.result, null, 2) }
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function handleGetLogs(args: ToolCallArgs) {
|
|
98
|
+
const { platform, appId, deviceId, pid, tag, level, contains, since_seconds, limit, lines } = args as any
|
|
99
|
+
const res = await ToolsObserve.getLogsHandler({ platform, appId, deviceId, pid, tag, level, contains, since_seconds, limit, lines })
|
|
100
|
+
const filtered = !!(pid || tag || level || contains || since_seconds || appId)
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{ type: 'text' as const, text: JSON.stringify({ device: res.device, result: { count: res.logCount, filtered, crashLines: (res.crashLines || []), source: res.source, meta: res.meta || {} } }, null, 2) },
|
|
104
|
+
{ type: 'text' as const, text: JSON.stringify({ logs: res.logs }, null, 2) }
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function handleListDevices(args: ToolCallArgs) {
|
|
110
|
+
const { platform, appId } = args as any
|
|
111
|
+
const res = await ToolsManage.listDevicesHandler({ platform, appId })
|
|
112
|
+
return wrapResponse(res)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function handleGetSystemStatus() {
|
|
116
|
+
const result = await getSystemStatus()
|
|
117
|
+
return wrapResponse(result)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function handleCaptureScreenshot(args: ToolCallArgs) {
|
|
121
|
+
const { platform, deviceId } = args as any
|
|
122
|
+
const res = await ToolsObserve.captureScreenshotHandler({ platform, deviceId })
|
|
123
|
+
const mime = (res as any).screenshot_mime || 'image/png'
|
|
124
|
+
const content: Array<{ type: 'text' | 'image'; text?: string; data?: string; mimeType?: string }> = [
|
|
125
|
+
{ type: 'text', text: JSON.stringify({ device: res.device, result: { resolution: (res as any).resolution, mimeType: mime } }, null, 2) },
|
|
126
|
+
{ type: 'image', data: (res as any).screenshot, mimeType: mime }
|
|
127
|
+
]
|
|
128
|
+
if ((res as any).screenshot_fallback) {
|
|
129
|
+
content.push({ type: 'text', text: JSON.stringify({ note: 'JPEG fallback included for compatibility', mimeType: (res as any).screenshot_fallback_mime || 'image/jpeg' }) })
|
|
130
|
+
content.push({ type: 'image', data: (res as any).screenshot_fallback, mimeType: (res as any).screenshot_fallback_mime || 'image/jpeg' })
|
|
131
|
+
}
|
|
132
|
+
return { content }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function handleCaptureDebugSnapshot(args: ToolCallArgs) {
|
|
136
|
+
const { reason, includeLogs, logLines, platform, appId, deviceId, sessionId } = args as any
|
|
137
|
+
const res = await ToolsObserve.captureDebugSnapshotHandler({ reason, includeLogs, logLines, platform, appId, deviceId, sessionId })
|
|
138
|
+
return wrapResponse(res)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function handleGetUITree(args: ToolCallArgs) {
|
|
142
|
+
const { platform, deviceId } = args as any
|
|
143
|
+
const res = await ToolsObserve.getUITreeHandler({ platform, deviceId })
|
|
144
|
+
return wrapResponse(res)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function handleGetCurrentScreen(args: ToolCallArgs) {
|
|
148
|
+
const { deviceId } = args as any
|
|
149
|
+
const res = await ToolsObserve.getCurrentScreenHandler({ deviceId })
|
|
150
|
+
return wrapResponse(res)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function handleGetScreenFingerprint(args: ToolCallArgs) {
|
|
154
|
+
const { platform, deviceId } = args as any
|
|
155
|
+
const res = await ToolsObserve.getScreenFingerprintHandler({ platform, deviceId })
|
|
156
|
+
return wrapResponse(res)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function handleWaitForScreenChange(args: ToolCallArgs) {
|
|
160
|
+
const { platform, previousFingerprint, timeoutMs, pollIntervalMs, deviceId } = args as any
|
|
161
|
+
const res = await ToolsInteract.waitForScreenChangeHandler({ platform, previousFingerprint, timeoutMs, pollIntervalMs, deviceId })
|
|
162
|
+
return wrapResponse(res)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function handleExpectScreen(args: ToolCallArgs) {
|
|
166
|
+
const { platform, fingerprint, screen, deviceId } = args as any
|
|
167
|
+
const res = await ToolsInteract.expectScreenHandler({ platform, fingerprint, screen, deviceId })
|
|
168
|
+
return wrapResponse(res)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function handleExpectElementVisible(args: ToolCallArgs) {
|
|
172
|
+
const { selector, element_id, timeout_ms, poll_interval_ms, platform, deviceId } = args as any
|
|
173
|
+
const res = await ToolsInteract.expectElementVisibleHandler({ selector, element_id, timeout_ms, poll_interval_ms, platform, deviceId })
|
|
174
|
+
return wrapResponse(res)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function handleWaitForUI(args: ToolCallArgs) {
|
|
178
|
+
const { selector, condition = 'exists', timeout_ms = 60000, poll_interval_ms = 300, match, retry, platform, deviceId } = args as any
|
|
179
|
+
const res = await ToolsInteract.waitForUIHandler({ selector, condition, timeout_ms, poll_interval_ms, match, retry, platform, deviceId })
|
|
180
|
+
return wrapResponse(res)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function handleFindElement(args: ToolCallArgs) {
|
|
184
|
+
const { query, exact = false, timeoutMs = 3000, platform, deviceId } = args as any
|
|
185
|
+
const res = await ToolsInteract.findElementHandler({ query, exact, timeoutMs, platform, deviceId })
|
|
186
|
+
return wrapResponse(res)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function handleTap(args: ToolCallArgs) {
|
|
190
|
+
const { platform, x, y, deviceId } = args as any
|
|
191
|
+
const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
|
|
192
|
+
ToolsNetwork.notifyActionStart()
|
|
193
|
+
const res = await ToolsInteract.tapHandler({ platform, x, y, deviceId })
|
|
194
|
+
const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
|
|
195
|
+
return wrapResponse(buildActionExecutionResult({
|
|
196
|
+
actionType: 'tap',
|
|
197
|
+
selector: { x, y },
|
|
198
|
+
success: !!res.success,
|
|
199
|
+
uiFingerprintBefore,
|
|
200
|
+
uiFingerprintAfter,
|
|
201
|
+
failure: res.success ? undefined : inferGenericFailure((res as any).error)
|
|
202
|
+
}))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function handleTapElement(args: ToolCallArgs) {
|
|
206
|
+
const { elementId } = args as any
|
|
207
|
+
ToolsNetwork.notifyActionStart()
|
|
208
|
+
const res = await ToolsInteract.tapElementHandler({ elementId })
|
|
209
|
+
return wrapResponse(res)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function handleSwipe(args: ToolCallArgs) {
|
|
213
|
+
const { platform = 'android', x1, y1, x2, y2, duration, deviceId } = args as any
|
|
214
|
+
const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
|
|
215
|
+
ToolsNetwork.notifyActionStart()
|
|
216
|
+
const res = await ToolsInteract.swipeHandler({ platform, x1, y1, x2, y2, duration, deviceId })
|
|
217
|
+
const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
|
|
218
|
+
return wrapResponse(buildActionExecutionResult({
|
|
219
|
+
actionType: 'swipe',
|
|
220
|
+
selector: { x1, y1, x2, y2, duration },
|
|
221
|
+
success: !!res.success,
|
|
222
|
+
uiFingerprintBefore,
|
|
223
|
+
uiFingerprintAfter,
|
|
224
|
+
failure: res.success ? undefined : inferGenericFailure((res as any).error)
|
|
225
|
+
}))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function handleScrollToElement(args: ToolCallArgs) {
|
|
229
|
+
const { platform, selector, direction, maxScrolls, scrollAmount, deviceId } = args as any
|
|
230
|
+
const uiFingerprintBefore = await captureActionFingerprint(platform, deviceId)
|
|
231
|
+
ToolsNetwork.notifyActionStart()
|
|
232
|
+
const res = await ToolsInteract.scrollToElementHandler({ platform, selector, direction, maxScrolls, scrollAmount, deviceId })
|
|
233
|
+
const uiFingerprintAfter = await captureActionFingerprint(platform, deviceId)
|
|
234
|
+
return wrapResponse(buildActionExecutionResult({
|
|
235
|
+
actionType: 'scroll_to_element',
|
|
236
|
+
selector,
|
|
237
|
+
resolved: res?.success && res?.element ? {
|
|
238
|
+
elementId: null,
|
|
239
|
+
text: (res.element as any).text ?? null,
|
|
240
|
+
resource_id: (res.element as any).resourceId ?? null,
|
|
241
|
+
accessibility_id: (res.element as any).contentDesc ?? null,
|
|
242
|
+
class: (res.element as any).className ?? null,
|
|
243
|
+
bounds: (res.element as any).bounds ?? null,
|
|
244
|
+
index: null
|
|
245
|
+
} : null,
|
|
246
|
+
success: !!res.success,
|
|
247
|
+
uiFingerprintBefore,
|
|
248
|
+
uiFingerprintAfter,
|
|
249
|
+
failure: res.success ? undefined : inferScrollFailure((res as any).reason)
|
|
250
|
+
}))
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function handleTypeText(args: ToolCallArgs) {
|
|
254
|
+
const { text, deviceId } = args as any
|
|
255
|
+
const uiFingerprintBefore = await captureActionFingerprint('android', deviceId)
|
|
256
|
+
ToolsNetwork.notifyActionStart()
|
|
257
|
+
const res = await ToolsInteract.typeTextHandler({ text, deviceId })
|
|
258
|
+
const uiFingerprintAfter = await captureActionFingerprint('android', deviceId)
|
|
259
|
+
return wrapResponse(buildActionExecutionResult({
|
|
260
|
+
actionType: 'type_text',
|
|
261
|
+
selector: { text },
|
|
262
|
+
success: !!res.success,
|
|
263
|
+
uiFingerprintBefore,
|
|
264
|
+
uiFingerprintAfter,
|
|
265
|
+
failure: res.success ? undefined : inferGenericFailure((res as any).error)
|
|
266
|
+
}))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function handlePressBack(args: ToolCallArgs) {
|
|
270
|
+
const { deviceId } = args as any
|
|
271
|
+
const uiFingerprintBefore = await captureActionFingerprint('android', deviceId)
|
|
272
|
+
ToolsNetwork.notifyActionStart()
|
|
273
|
+
const res = await ToolsInteract.pressBackHandler({ deviceId })
|
|
274
|
+
const uiFingerprintAfter = await captureActionFingerprint('android', deviceId)
|
|
275
|
+
return wrapResponse(buildActionExecutionResult({
|
|
276
|
+
actionType: 'press_back',
|
|
277
|
+
selector: { key: 'back' },
|
|
278
|
+
success: !!res.success,
|
|
279
|
+
uiFingerprintBefore,
|
|
280
|
+
uiFingerprintAfter,
|
|
281
|
+
failure: res.success ? undefined : inferGenericFailure((res as any).error)
|
|
282
|
+
}))
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function handleStartLogStream(args: ToolCallArgs) {
|
|
286
|
+
const { platform, packageName, level, sessionId, deviceId } = args as any
|
|
287
|
+
const res = await ToolsObserve.startLogStreamHandler({ platform, packageName, level, sessionId, deviceId })
|
|
288
|
+
return wrapResponse(res)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function handleReadLogStream(args: ToolCallArgs) {
|
|
292
|
+
const { platform, sessionId, limit, since } = args as any
|
|
293
|
+
const res = await ToolsObserve.readLogStreamHandler({ platform, sessionId, limit, since })
|
|
294
|
+
return wrapResponse(res)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function handleStopLogStream(args: ToolCallArgs) {
|
|
298
|
+
const { platform, sessionId } = args as any
|
|
299
|
+
const res = await ToolsObserve.stopLogStreamHandler({ platform, sessionId })
|
|
300
|
+
return wrapResponse(res)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function handleClassifyActionOutcome(args: ToolCallArgs) {
|
|
304
|
+
const { uiChanged, expectedElementVisible, networkRequests, hasLogErrors } = args as any
|
|
305
|
+
const result = classifyActionOutcome({
|
|
306
|
+
uiChanged: Boolean(uiChanged),
|
|
307
|
+
expectedElementVisible: expectedElementVisible ?? null,
|
|
308
|
+
networkRequests: networkRequests ?? null,
|
|
309
|
+
hasLogErrors: hasLogErrors ?? null
|
|
310
|
+
})
|
|
311
|
+
return Promise.resolve(wrapResponse(result))
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async function handleGetNetworkActivity(args: ToolCallArgs) {
|
|
315
|
+
const { platform, deviceId } = args as any
|
|
316
|
+
const result = await ToolsNetwork.getNetworkActivity({ platform, deviceId })
|
|
317
|
+
return wrapResponse(result)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export const toolHandlers: Record<string, ToolHandler> = {
|
|
321
|
+
start_app: handleStartApp,
|
|
322
|
+
terminate_app: handleTerminateApp,
|
|
323
|
+
restart_app: handleRestartApp,
|
|
324
|
+
reset_app_data: handleResetAppData,
|
|
325
|
+
install_app: handleInstallApp,
|
|
326
|
+
build_app: handleBuildApp,
|
|
327
|
+
build_and_install: handleBuildAndInstall,
|
|
328
|
+
get_logs: handleGetLogs,
|
|
329
|
+
list_devices: handleListDevices,
|
|
330
|
+
get_system_status: handleGetSystemStatus,
|
|
331
|
+
capture_screenshot: handleCaptureScreenshot,
|
|
332
|
+
capture_debug_snapshot: handleCaptureDebugSnapshot,
|
|
333
|
+
get_ui_tree: handleGetUITree,
|
|
334
|
+
get_current_screen: handleGetCurrentScreen,
|
|
335
|
+
get_screen_fingerprint: handleGetScreenFingerprint,
|
|
336
|
+
wait_for_screen_change: handleWaitForScreenChange,
|
|
337
|
+
expect_screen: handleExpectScreen,
|
|
338
|
+
expect_element_visible: handleExpectElementVisible,
|
|
339
|
+
wait_for_ui: handleWaitForUI,
|
|
340
|
+
find_element: handleFindElement,
|
|
341
|
+
tap: handleTap,
|
|
342
|
+
tap_element: handleTapElement,
|
|
343
|
+
swipe: handleSwipe,
|
|
344
|
+
scroll_to_element: handleScrollToElement,
|
|
345
|
+
type_text: handleTypeText,
|
|
346
|
+
press_back: handlePressBack,
|
|
347
|
+
start_log_stream: handleStartLogStream,
|
|
348
|
+
read_log_stream: handleReadLogStream,
|
|
349
|
+
stop_log_stream: handleStopLogStream,
|
|
350
|
+
classify_action_outcome: handleClassifyActionOutcome,
|
|
351
|
+
get_network_activity: handleGetNetworkActivity
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export async function handleToolCall(name: string, args: ToolCallArgs = {}) {
|
|
355
|
+
const handler = toolHandlers[name]
|
|
356
|
+
if (!handler) throw new Error(`Unknown tool: ${name}`)
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
return await handler(args)
|
|
360
|
+
} catch (error) {
|
|
361
|
+
return {
|
|
362
|
+
content: [{ type: 'text' as const, text: `Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}` }]
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|