ios-webkit-mcp 0.18.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 (171) hide show
  1. package/CONTRIBUTING.md +61 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +16 -0
  4. package/README.md +187 -0
  5. package/build/src/bin/ios-webkit-mcp.d.ts +2 -0
  6. package/build/src/bin/ios-webkit-mcp.js +14 -0
  7. package/build/src/bin/ios-webkit-mcp.js.map +1 -0
  8. package/build/src/capability.d.ts +61 -0
  9. package/build/src/capability.js +146 -0
  10. package/build/src/capability.js.map +1 -0
  11. package/build/src/index.d.ts +1 -0
  12. package/build/src/index.js +2 -0
  13. package/build/src/index.js.map +1 -0
  14. package/build/src/page-session.d.ts +108 -0
  15. package/build/src/page-session.js +331 -0
  16. package/build/src/page-session.js.map +1 -0
  17. package/build/src/server.d.ts +11 -0
  18. package/build/src/server.js +144 -0
  19. package/build/src/server.js.map +1 -0
  20. package/build/src/tools/analyze_performance.d.ts +40 -0
  21. package/build/src/tools/analyze_performance.js +112 -0
  22. package/build/src/tools/analyze_performance.js.map +1 -0
  23. package/build/src/tools/breakpoints.d.ts +80 -0
  24. package/build/src/tools/breakpoints.js +167 -0
  25. package/build/src/tools/breakpoints.js.map +1 -0
  26. package/build/src/tools/clear_console.d.ts +11 -0
  27. package/build/src/tools/clear_console.js +32 -0
  28. package/build/src/tools/clear_console.js.map +1 -0
  29. package/build/src/tools/click.d.ts +20 -0
  30. package/build/src/tools/click.js +88 -0
  31. package/build/src/tools/click.js.map +1 -0
  32. package/build/src/tools/debug_element.d.ts +36 -0
  33. package/build/src/tools/debug_element.js +66 -0
  34. package/build/src/tools/debug_element.js.map +1 -0
  35. package/build/src/tools/debugger.d.ts +44 -0
  36. package/build/src/tools/debugger.js +117 -0
  37. package/build/src/tools/debugger.js.map +1 -0
  38. package/build/src/tools/delete_cookie.d.ts +18 -0
  39. package/build/src/tools/delete_cookie.js +33 -0
  40. package/build/src/tools/delete_cookie.js.map +1 -0
  41. package/build/src/tools/drag.d.ts +40 -0
  42. package/build/src/tools/drag.js +202 -0
  43. package/build/src/tools/drag.js.map +1 -0
  44. package/build/src/tools/evaluate_script.d.ts +24 -0
  45. package/build/src/tools/evaluate_script.js +172 -0
  46. package/build/src/tools/evaluate_script.js.map +1 -0
  47. package/build/src/tools/fill.d.ts +22 -0
  48. package/build/src/tools/fill.js +104 -0
  49. package/build/src/tools/fill.js.map +1 -0
  50. package/build/src/tools/fill_form.d.ts +34 -0
  51. package/build/src/tools/fill_form.js +110 -0
  52. package/build/src/tools/fill_form.js.map +1 -0
  53. package/build/src/tools/gc_heap.d.ts +16 -0
  54. package/build/src/tools/gc_heap.js +66 -0
  55. package/build/src/tools/gc_heap.js.map +1 -0
  56. package/build/src/tools/get_capability_snapshot.d.ts +17 -0
  57. package/build/src/tools/get_capability_snapshot.js +50 -0
  58. package/build/src/tools/get_capability_snapshot.js.map +1 -0
  59. package/build/src/tools/get_console_message.d.ts +18 -0
  60. package/build/src/tools/get_console_message.js +92 -0
  61. package/build/src/tools/get_console_message.js.map +1 -0
  62. package/build/src/tools/get_cookies.d.ts +20 -0
  63. package/build/src/tools/get_cookies.js +59 -0
  64. package/build/src/tools/get_cookies.js.map +1 -0
  65. package/build/src/tools/get_event_listeners.d.ts +29 -0
  66. package/build/src/tools/get_event_listeners.js +129 -0
  67. package/build/src/tools/get_event_listeners.js.map +1 -0
  68. package/build/src/tools/get_network_request.d.ts +22 -0
  69. package/build/src/tools/get_network_request.js +121 -0
  70. package/build/src/tools/get_network_request.js.map +1 -0
  71. package/build/src/tools/get_performance_metrics.d.ts +27 -0
  72. package/build/src/tools/get_performance_metrics.js +185 -0
  73. package/build/src/tools/get_performance_metrics.js.map +1 -0
  74. package/build/src/tools/get_resource_tree.d.ts +18 -0
  75. package/build/src/tools/get_resource_tree.js +82 -0
  76. package/build/src/tools/get_resource_tree.js.map +1 -0
  77. package/build/src/tools/handle_dialog.d.ts +39 -0
  78. package/build/src/tools/handle_dialog.js +182 -0
  79. package/build/src/tools/handle_dialog.js.map +1 -0
  80. package/build/src/tools/highlight_node.d.ts +41 -0
  81. package/build/src/tools/highlight_node.js +95 -0
  82. package/build/src/tools/highlight_node.js.map +1 -0
  83. package/build/src/tools/hover.d.ts +20 -0
  84. package/build/src/tools/hover.js +89 -0
  85. package/build/src/tools/hover.js.map +1 -0
  86. package/build/src/tools/intercept.d.ts +81 -0
  87. package/build/src/tools/intercept.js +161 -0
  88. package/build/src/tools/intercept.js.map +1 -0
  89. package/build/src/tools/list_console_messages.d.ts +20 -0
  90. package/build/src/tools/list_console_messages.js +105 -0
  91. package/build/src/tools/list_console_messages.js.map +1 -0
  92. package/build/src/tools/list_indexeddb.d.ts +16 -0
  93. package/build/src/tools/list_indexeddb.js +55 -0
  94. package/build/src/tools/list_indexeddb.js.map +1 -0
  95. package/build/src/tools/list_network_requests.d.ts +25 -0
  96. package/build/src/tools/list_network_requests.js +108 -0
  97. package/build/src/tools/list_network_requests.js.map +1 -0
  98. package/build/src/tools/list_pages.d.ts +17 -0
  99. package/build/src/tools/list_pages.js +113 -0
  100. package/build/src/tools/list_pages.js.map +1 -0
  101. package/build/src/tools/navigate_page.d.ts +18 -0
  102. package/build/src/tools/navigate_page.js +73 -0
  103. package/build/src/tools/navigate_page.js.map +1 -0
  104. package/build/src/tools/network_runtime.d.ts +51 -0
  105. package/build/src/tools/network_runtime.js +101 -0
  106. package/build/src/tools/network_runtime.js.map +1 -0
  107. package/build/src/tools/press_key.d.ts +32 -0
  108. package/build/src/tools/press_key.js +108 -0
  109. package/build/src/tools/press_key.js.map +1 -0
  110. package/build/src/tools/query_dom_node.d.ts +22 -0
  111. package/build/src/tools/query_dom_node.js +103 -0
  112. package/build/src/tools/query_dom_node.js.map +1 -0
  113. package/build/src/tools/record_cpu_profile.d.ts +38 -0
  114. package/build/src/tools/record_cpu_profile.js +174 -0
  115. package/build/src/tools/record_cpu_profile.js.map +1 -0
  116. package/build/src/tools/record_memory_tracking.d.ts +32 -0
  117. package/build/src/tools/record_memory_tracking.js +103 -0
  118. package/build/src/tools/record_memory_tracking.js.map +1 -0
  119. package/build/src/tools/record_timeline.d.ts +20 -0
  120. package/build/src/tools/record_timeline.js +110 -0
  121. package/build/src/tools/record_timeline.js.map +1 -0
  122. package/build/src/tools/reload_page.d.ts +18 -0
  123. package/build/src/tools/reload_page.js +59 -0
  124. package/build/src/tools/reload_page.js.map +1 -0
  125. package/build/src/tools/select_page.d.ts +18 -0
  126. package/build/src/tools/select_page.js +54 -0
  127. package/build/src/tools/select_page.js.map +1 -0
  128. package/build/src/tools/set_extra_http_headers.d.ts +16 -0
  129. package/build/src/tools/set_extra_http_headers.js +55 -0
  130. package/build/src/tools/set_extra_http_headers.js.map +1 -0
  131. package/build/src/tools/set_init_script.d.ts +16 -0
  132. package/build/src/tools/set_init_script.js +43 -0
  133. package/build/src/tools/set_init_script.js.map +1 -0
  134. package/build/src/tools/set_outer_html.d.ts +18 -0
  135. package/build/src/tools/set_outer_html.js +52 -0
  136. package/build/src/tools/set_outer_html.js.map +1 -0
  137. package/build/src/tools/set_user_agent.d.ts +40 -0
  138. package/build/src/tools/set_user_agent.js +83 -0
  139. package/build/src/tools/set_user_agent.js.map +1 -0
  140. package/build/src/tools/summarize_console_errors.d.ts +40 -0
  141. package/build/src/tools/summarize_console_errors.js +131 -0
  142. package/build/src/tools/summarize_console_errors.js.map +1 -0
  143. package/build/src/tools/take_memory_snapshot.d.ts +18 -0
  144. package/build/src/tools/take_memory_snapshot.js +88 -0
  145. package/build/src/tools/take_memory_snapshot.js.map +1 -0
  146. package/build/src/tools/take_node_screenshot.d.ts +29 -0
  147. package/build/src/tools/take_node_screenshot.js +57 -0
  148. package/build/src/tools/take_node_screenshot.js.map +1 -0
  149. package/build/src/tools/take_screenshot.d.ts +51 -0
  150. package/build/src/tools/take_screenshot.js +108 -0
  151. package/build/src/tools/take_screenshot.js.map +1 -0
  152. package/build/src/tools/take_snapshot.d.ts +22 -0
  153. package/build/src/tools/take_snapshot.js +142 -0
  154. package/build/src/tools/take_snapshot.js.map +1 -0
  155. package/build/src/tools/type_text.d.ts +34 -0
  156. package/build/src/tools/type_text.js +203 -0
  157. package/build/src/tools/type_text.js.map +1 -0
  158. package/build/src/tools/wait_for.d.ts +24 -0
  159. package/build/src/tools/wait_for.js +93 -0
  160. package/build/src/tools/wait_for.js.map +1 -0
  161. package/build/src/tools/wip_send.d.ts +22 -0
  162. package/build/src/tools/wip_send.js +81 -0
  163. package/build/src/tools/wip_send.js.map +1 -0
  164. package/build/src/wip-client/iwdp-http.d.ts +17 -0
  165. package/build/src/wip-client/iwdp-http.js +98 -0
  166. package/build/src/wip-client/iwdp-http.js.map +1 -0
  167. package/build/src/wip-client/ws-session.d.ts +41 -0
  168. package/build/src/wip-client/ws-session.js +290 -0
  169. package/build/src/wip-client/ws-session.js.map +1 -0
  170. package/docs/spec.md +689 -0
  171. package/package.json +77 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * batch-14 intercept full state machine — fulfills the contract frozen in
3
+ * cross-review v2.0.1 R3 + spec §"intercept 完整状态机 (batch-14 契约)":
4
+ *
5
+ * - requestId 生命周期:`Network.requestIntercepted` → buffered in PageSession
6
+ * → consumed by `intercept_continue` / `intercept_respond` → drained.
7
+ * - 并发上限:16 pending. Overflow auto-continues the oldest.
8
+ * - 超时:30s without consumption → auto-continue (page does NOT hang).
9
+ * - 显式 abort 语义:`intercept_continue(reqId, abort=true)` blocks the
10
+ * request via `Network.interceptContinue` with `errorReason=BlockedByClient`.
11
+ *
12
+ * Apple WIP methods (probe-confirmed 2026-05-17):
13
+ * - Network.setInterceptionEnabled ✅ returns {}
14
+ * - Network.addInterception ✅ returns {} for any url pattern
15
+ * - Network.interceptContinue ✅ (probe shows method exists)
16
+ * - Network.interceptWithResponse ✅ (probe shows method exists)
17
+ *
18
+ * ⚠ **iOS WKWebView limitation (2026-05-17 实测)**: Even though the methods
19
+ * above all return `{}` success, `Network.requestIntercepted` event is NEVER
20
+ * fired for matching requests on iOS 26.5.0 + Apple system WKWebView. The
21
+ * underlying WebKit interception machinery is a no-op on iOS. iwdp-mcp's
22
+ * fulfill/continue flow validated against macOS Safari, where it works.
23
+ *
24
+ * Tools below are coded correctly per the WIP spec — they will Just Work the
25
+ * day Apple ships interception runtime in iOS WKWebView (or when this MCP is
26
+ * pointed at macOS Safari via a different transport). For now, calling
27
+ * `set_request_interception` does nothing observable; `list_intercepted_requests`
28
+ * always returns 0.
29
+ */
30
+ import { z } from 'zod';
31
+ import { PageSession } from '../page-session.js';
32
+ export const listInterceptedRequestsTool = {
33
+ name: 'list_intercepted_requests',
34
+ description: [
35
+ 'List currently-pending intercepted requests buffered by the page session.',
36
+ 'Each entry has a `requestId` you can pass to `intercept_continue` (release / block) or `intercept_respond` (mock body).',
37
+ 'Buffer cap: 16 concurrent (oldest auto-continues on overflow). Auto-continue: 30s if not consumed (page does not hang).',
38
+ ].join(' '),
39
+ inputSchema: {},
40
+ handler: async () => {
41
+ let ps;
42
+ try {
43
+ ps = await PageSession.get();
44
+ }
45
+ catch (e) {
46
+ return errorResult(`Unable to attach: ${describe(e)}`);
47
+ }
48
+ const list = [...ps.intercepted.values()];
49
+ if (list.length === 0) {
50
+ return textResult('# list_intercepted_requests\n\nNo pending intercepted requests. Call `set_request_interception({enabled:true})` and trigger network activity to populate.');
51
+ }
52
+ const lines = [
53
+ `# list_intercepted_requests — ${list.length} pending`,
54
+ '',
55
+ '| requestId | method | url | stage | age (s) |',
56
+ '|---|---|---|---|---|',
57
+ ];
58
+ const now = Date.now();
59
+ for (const e of list) {
60
+ const age = ((now - e.arrivedAt) / 1000).toFixed(1);
61
+ const url = e.url.length > 80 ? e.url.slice(0, 77) + '…' : e.url;
62
+ lines.push(`| \`${e.requestId}\` | ${e.method ?? '?'} | ${url.replace(/\|/g, '\\|')} | ${e.stage ?? '?'} | ${age} |`);
63
+ }
64
+ return textResult(lines.join('\n'));
65
+ },
66
+ };
67
+ export const interceptContinueTool = {
68
+ name: 'intercept_continue',
69
+ description: [
70
+ 'Release a pending intercepted request via WIP `Network.interceptContinue`. By default the request flows to the network unchanged; pass `abort=true` to block (page will see a network-error).',
71
+ 'Pair with `list_intercepted_requests` to discover requestIds.',
72
+ ].join(' '),
73
+ inputSchema: {
74
+ requestId: z.string().min(1).describe('Intercepted requestId from `list_intercepted_requests`.'),
75
+ abort: z
76
+ .boolean()
77
+ .optional()
78
+ .describe('`true` blocks the request (page sees error); default false continues normally.'),
79
+ },
80
+ handler: async ({ requestId, abort = false }) => {
81
+ let ps;
82
+ try {
83
+ ps = await PageSession.get();
84
+ }
85
+ catch (e) {
86
+ return errorResult(`Unable to attach: ${describe(e)}`);
87
+ }
88
+ const entry = ps.consumeIntercepted(requestId);
89
+ if (!entry) {
90
+ return textResult(`# intercept_continue — requestId \`${requestId}\` not in pending set (may have already been continued / timed out / never intercepted)`);
91
+ }
92
+ try {
93
+ if (abort) {
94
+ // WIP "abort" pattern — interceptContinue with errorReason. Apple WIP errorReason values vary.
95
+ await ps.session.send('Network.interceptContinue', { requestId, errorReason: 'BlockedByClient' }, 5_000);
96
+ return textResult(`# intercept_continue ✓ \`${requestId}\` aborted\n\nRequest blocked. Page will see a network error.`);
97
+ }
98
+ await ps.session.send('Network.interceptContinue', { requestId }, 5_000);
99
+ return textResult(`# intercept_continue ✓ \`${requestId}\` released\n\nURL: ${entry.url}\nFlowing to network unchanged.`);
100
+ }
101
+ catch (e) {
102
+ return errorResult(`Network.interceptContinue failed: ${describe(e)}`);
103
+ }
104
+ },
105
+ };
106
+ export const interceptRespondTool = {
107
+ name: 'intercept_respond',
108
+ description: [
109
+ 'Reply to an intercepted request with a mock response via WIP `Network.interceptWithResponse`. Useful for testing error handling, staging fixture data, or diverting an API call without backend changes.',
110
+ 'Body can be plain text/JSON (string) or base64-encoded binary (set `base64=true`).',
111
+ 'Pair with `list_intercepted_requests` to discover requestIds.',
112
+ ].join(' '),
113
+ inputSchema: {
114
+ requestId: z.string().min(1).describe('Intercepted requestId from `list_intercepted_requests`.'),
115
+ statusCode: z
116
+ .number()
117
+ .int()
118
+ .min(100)
119
+ .max(599)
120
+ .optional()
121
+ .describe('HTTP status code (default 200).'),
122
+ headers: z
123
+ .record(z.string(), z.string())
124
+ .optional()
125
+ .describe('Additional response headers map (Content-Type defaults to text/plain unless overridden).'),
126
+ body: z.string().describe('Response body. Plain text or JSON string when `base64=false`; base64-encoded bytes when `base64=true`.'),
127
+ base64: z
128
+ .boolean()
129
+ .optional()
130
+ .describe('Treat `body` as base64 (default false).'),
131
+ },
132
+ handler: async ({ requestId, statusCode = 200, headers, body, base64 = false }) => {
133
+ let ps;
134
+ try {
135
+ ps = await PageSession.get();
136
+ }
137
+ catch (e) {
138
+ return errorResult(`Unable to attach: ${describe(e)}`);
139
+ }
140
+ const entry = ps.consumeIntercepted(requestId);
141
+ if (!entry) {
142
+ return textResult(`# intercept_respond — requestId \`${requestId}\` not in pending set (may have already been responded / timed out / never intercepted)`);
143
+ }
144
+ const hdrs = { 'Content-Type': 'text/plain', ...(headers ?? {}) };
145
+ const content = base64 ? body : Buffer.from(body, 'utf-8').toString('base64');
146
+ try {
147
+ await ps.session.send('Network.interceptWithResponse', { requestId, statusCode, headers: hdrs, content, base64Encoded: true }, 5_000);
148
+ return textResult(`# intercept_respond ✓ \`${requestId}\` mocked\n\n` +
149
+ `URL: ${entry.url}\n` +
150
+ `Status: ${statusCode}\n` +
151
+ `Body: ${base64 ? `${body.length} bytes (base64)` : `"${body.slice(0, 80)}${body.length > 80 ? '…' : ''}"`}\n`);
152
+ }
153
+ catch (e) {
154
+ return errorResult(`Network.interceptWithResponse failed: ${describe(e)}`);
155
+ }
156
+ },
157
+ };
158
+ function textResult(text) { return { content: [{ type: 'text', text }] }; }
159
+ function errorResult(text) { return { content: [{ type: 'text', text }], isError: true }; }
160
+ function describe(e) { return e instanceof Error ? e.message : String(e); }
161
+ //# sourceMappingURL=intercept.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intercept.js","sourceRoot":"","sources":["../../../src/tools/intercept.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+ export declare const listConsoleMessagesTool: {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: {
6
+ limit: z.ZodOptional<z.ZodNumber>;
7
+ levels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
8
+ waitMs: z.ZodOptional<z.ZodNumber>;
9
+ };
10
+ handler: ({ limit, levels, waitMs, }: {
11
+ limit?: number;
12
+ levels?: string[];
13
+ waitMs?: number;
14
+ }) => Promise<{
15
+ content: {
16
+ type: "text";
17
+ text: string;
18
+ }[];
19
+ }>;
20
+ };
@@ -0,0 +1,105 @@
1
+ import { z } from 'zod';
2
+ import { PageSession } from '../page-session.js';
3
+ export const listConsoleMessagesTool = {
4
+ name: 'list_console_messages',
5
+ description: [
6
+ 'Return recent console messages and uncaught exceptions captured from the inspected iOS WebView page.',
7
+ 'Wraps WIP `Console.messageAdded` + `Runtime.exceptionThrown` events.',
8
+ 'Messages are captured in a rolling buffer (cap 200) once the session attaches.',
9
+ 'Optional `waitMs` will block up to that long if the buffer is currently empty (useful right after attach).',
10
+ ].join(' '),
11
+ inputSchema: {
12
+ limit: z
13
+ .number()
14
+ .int()
15
+ .min(1)
16
+ .max(500)
17
+ .optional()
18
+ .describe('Max messages to return (most recent first). Default 50.'),
19
+ levels: z
20
+ .array(z.string())
21
+ .optional()
22
+ .describe('Filter by level (e.g. ["error","warning"]). Default: all.'),
23
+ waitMs: z
24
+ .number()
25
+ .int()
26
+ .min(0)
27
+ .max(60_000)
28
+ .optional()
29
+ .describe('If buffer is empty, wait up to this many ms for messages to arrive before returning. Default 0.'),
30
+ },
31
+ handler: async ({ limit = 50, levels, waitMs = 0, }) => {
32
+ let ps;
33
+ try {
34
+ ps = await PageSession.get();
35
+ }
36
+ catch (e) {
37
+ return errorResult(`Unable to attach to a WKWebView page: ${describe(e)}`);
38
+ }
39
+ if (ps.consoleBuffer.length === 0 && waitMs > 0) {
40
+ await sleep(waitMs);
41
+ }
42
+ let entries = ps.consoleBuffer.slice();
43
+ if (levels && levels.length > 0) {
44
+ const set = new Set(levels.map(s => s.toLowerCase()));
45
+ entries = entries.filter(e => set.has(e.level.toLowerCase()));
46
+ }
47
+ entries = entries.slice(-limit).reverse();
48
+ const lines = [];
49
+ lines.push(`# Console messages — ${entries.length} of ${ps.consoleBuffer.length} buffered`);
50
+ lines.push('');
51
+ if (ps.attachInfo) {
52
+ lines.push(`Attached to: \`${ps.attachInfo.pageTitle || ps.attachInfo.pageUrl}\` (page id ${ps.attachInfo.pageId}, device ${ps.attachInfo.deviceId.slice(0, 12)}…, iOS ${ps.attachInfo.deviceOSVersion ?? '?'})`);
53
+ lines.push(`Attached at: ${new Date(ps.attachInfo.attachedAt).toISOString()}`);
54
+ lines.push('');
55
+ }
56
+ if (ps.enableErrors.length > 0) {
57
+ lines.push(`⚠ enable errors: ${ps.enableErrors.map(e => `${e.method}(${e.error})`).join(', ')}`);
58
+ lines.push('');
59
+ }
60
+ if (entries.length === 0) {
61
+ lines.push(ps.consoleBuffer.length === 0
62
+ ? 'No messages captured yet. Trigger page activity (reload / interact) and call again.'
63
+ : '(All buffered messages filtered out by levels.)');
64
+ return textResult(lines.join('\n'));
65
+ }
66
+ for (const [i, e] of entries.entries()) {
67
+ const ts = typeof e.timestamp === 'number' ? new Date(e.timestamp * 1000).toISOString() : '';
68
+ lines.push(`## [${i + 1}] ${e.level} · ${e.source}${ts ? ` · ${ts}` : ''}`);
69
+ lines.push(` ${escapeNewlines(e.text)}`);
70
+ if (e.url)
71
+ lines.push(` at ${e.url}${e.line != null ? `:${e.line}` : ''}${e.column != null ? `:${e.column}` : ''}`);
72
+ if (e.args && e.args.length > 0) {
73
+ const compact = e.args.map(a => safeStringify(a, 100)).join(', ');
74
+ lines.push(` args: [${compact}]`);
75
+ }
76
+ lines.push('');
77
+ }
78
+ return textResult(lines.join('\n'));
79
+ },
80
+ };
81
+ function textResult(text) {
82
+ return { content: [{ type: 'text', text }] };
83
+ }
84
+ function errorResult(text) {
85
+ return { content: [{ type: 'text', text }], isError: true };
86
+ }
87
+ function describe(e) {
88
+ return e instanceof Error ? e.message : String(e);
89
+ }
90
+ function sleep(ms) {
91
+ return new Promise(r => setTimeout(r, ms));
92
+ }
93
+ function escapeNewlines(s) {
94
+ return s.replace(/\n/g, ' ⏎ ');
95
+ }
96
+ function safeStringify(o, maxLen = 200) {
97
+ try {
98
+ const s = JSON.stringify(o);
99
+ return s && s.length > maxLen ? s.slice(0, maxLen) + '…' : (s ?? String(o));
100
+ }
101
+ catch {
102
+ return String(o);
103
+ }
104
+ }
105
+ //# sourceMappingURL=list_console_messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list_console_messages.js","sourceRoot":"","sources":["../../../src/tools/list_console_messages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { z } from 'zod';
2
+ export declare const listIndexeddbTool: {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: {
6
+ securityOrigin: z.ZodOptional<z.ZodString>;
7
+ };
8
+ handler: ({ securityOrigin }: {
9
+ securityOrigin?: string;
10
+ }) => Promise<{
11
+ content: {
12
+ type: "text";
13
+ text: string;
14
+ }[];
15
+ }>;
16
+ };
@@ -0,0 +1,55 @@
1
+ import { z } from 'zod';
2
+ import { PageSession } from '../page-session.js';
3
+ export const listIndexeddbTool = {
4
+ name: 'list_indexeddb',
5
+ description: [
6
+ 'List IndexedDB database names for a given origin via WIP `IndexedDB.requestDatabaseNames` (Apple-specific, probe-confirmed 2026-05-16).',
7
+ 'Origin defaults to the currently-attached page\'s origin. Pass `securityOrigin` explicitly to inspect a different origin scope.',
8
+ 'NOTE: WIP\'s `Storage.*` domain is NOT available (probe ❌), so deep IndexedDB inspection (table contents) requires `IndexedDB.requestData` with a schema we haven\'t fully reverse-engineered yet.',
9
+ ].join(' '),
10
+ inputSchema: {
11
+ securityOrigin: z.string().optional().describe('Origin URL, e.g. `https://example.com`. Defaults to the current page\'s origin.'),
12
+ },
13
+ handler: async ({ securityOrigin }) => {
14
+ let ps;
15
+ try {
16
+ ps = await PageSession.get();
17
+ }
18
+ catch (e) {
19
+ return errorResult(`Unable to attach: ${describe(e)}`);
20
+ }
21
+ let origin = securityOrigin;
22
+ if (!origin) {
23
+ try {
24
+ const r = await ps.session.send('Runtime.evaluate', {
25
+ expression: 'location.origin', returnByValue: true,
26
+ }, 5_000);
27
+ origin = r?.result?.value;
28
+ }
29
+ catch { }
30
+ }
31
+ if (!origin)
32
+ return errorResult('Cannot determine origin — pass `securityOrigin` explicitly.');
33
+ let r;
34
+ try {
35
+ r = await ps.session.send('IndexedDB.requestDatabaseNames', { securityOrigin: origin }, 10_000);
36
+ }
37
+ catch (e) {
38
+ return errorResult(`IndexedDB.requestDatabaseNames failed: ${describe(e)}`);
39
+ }
40
+ const names = r.databaseNames ?? [];
41
+ const lines = [`# list_indexeddb — ${names.length} database(s) for \`${origin}\``, ''];
42
+ if (names.length === 0) {
43
+ lines.push('(no databases — page may not use IndexedDB or origin mismatch)');
44
+ }
45
+ else {
46
+ for (const n of names)
47
+ lines.push(`- ${n}`);
48
+ }
49
+ return textResult(lines.join('\n'));
50
+ },
51
+ };
52
+ function textResult(text) { return { content: [{ type: 'text', text }] }; }
53
+ function errorResult(text) { return { content: [{ type: 'text', text }], isError: true }; }
54
+ function describe(e) { return e instanceof Error ? e.message : String(e); }
55
+ //# sourceMappingURL=list_indexeddb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list_indexeddb.js","sourceRoot":"","sources":["../../../src/tools/list_indexeddb.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAIjD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,IAAI,EAAE,gBAAgB;IACtB,WAAW,EAAE;QACX,yIAAyI;QACzI,iIAAiI;QACjI,oMAAoM;KACrM,CAAC,IAAI,CAAC,GAAG,CAAC;IAEX,WAAW,EAAE;QACX,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iFAAiF,CAAC;KAClI;IAED,OAAO,EAAE,KAAK,EAAE,EAAE,cAAc,EAA+B,EAAE,EAAE;QACjE,IAAI,EAAe,CAAC;QACpB,IAAI,CAAC;YAAC,EAAE,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,CAAC;QAAC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YAAC,OAAO,WAAW,CAAC,qBAAqB,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAAC,CAAC;QAE3G,IAAI,MAAM,GAAG,cAAc,CAAC;QAC5B,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,IAAI,CAAC;gBACH,MAAM,CAAC,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAgC,kBAAkB,EAAE;oBACjF,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,IAAI;iBACnD,EAAE,KAAK,CAAC,CAAC;gBACV,MAAM,GAAG,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC;YAC5B,CAAC;YAAC,MAAM,CAAC,CAAA,CAAC;QACZ,CAAC;QACD,IAAI,CAAC,MAAM;YAAE,OAAO,WAAW,CAAC,6DAA6D,CAAC,CAAC;QAE/F,IAAI,CAAS,CAAC;QACd,IAAI,CAAC;YAAC,CAAC,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,IAAI,CAAS,gCAAgC,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC;QAAC,CAAC;QAChH,OAAO,CAAC,EAAE,CAAC;YAAC,OAAO,WAAW,CAAC,0CAA0C,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAAC,CAAC;QAE1F,MAAM,KAAK,GAAG,CAAC,CAAC,aAAa,IAAI,EAAE,CAAC;QACpC,MAAM,KAAK,GAAG,CAAC,sBAAsB,KAAK,CAAC,MAAM,sBAAsB,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;QACvF,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,KAAK,CAAC,IAAI,CAAC,gEAAgE,CAAC,CAAC;QAC/E,CAAC;aAAM,CAAC;YACN,KAAK,MAAM,CAAC,IAAI,KAAK;gBAAE,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,CAAC;CACF,CAAC;AAEF,SAAS,UAAU,CAAC,IAAY,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;AAC5F,SAAS,WAAW,CAAC,IAAY,IAAI,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC5G,SAAS,QAAQ,CAAC,CAAU,IAAI,OAAO,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ import { type NetworkEntry } from '../page-session.js';
3
+ export declare const listNetworkRequestsTool: {
4
+ name: string;
5
+ description: string;
6
+ inputSchema: {
7
+ limit: z.ZodOptional<z.ZodNumber>;
8
+ urlSubstring: z.ZodOptional<z.ZodString>;
9
+ state: z.ZodOptional<z.ZodEnum<["pending", "response-received", "finished", "failed"]>>;
10
+ method: z.ZodOptional<z.ZodString>;
11
+ waitMs: z.ZodOptional<z.ZodNumber>;
12
+ };
13
+ handler: ({ limit, urlSubstring, state, method, waitMs, }: {
14
+ limit?: number;
15
+ urlSubstring?: string;
16
+ state?: NetworkEntry["state"];
17
+ method?: string;
18
+ waitMs?: number;
19
+ }) => Promise<{
20
+ content: {
21
+ type: "text";
22
+ text: string;
23
+ }[];
24
+ }>;
25
+ };
@@ -0,0 +1,108 @@
1
+ import { z } from 'zod';
2
+ import { PageSession } from '../page-session.js';
3
+ export const listNetworkRequestsTool = {
4
+ name: 'list_network_requests',
5
+ description: [
6
+ 'Return recent network requests captured from the inspected iOS WebView page.',
7
+ 'Wraps WIP `Network.requestWillBeSent` / `responseReceived` / `loadingFinished` / `loadingFailed` events.',
8
+ 'Requests are captured in a rolling buffer (cap 200) once the session attaches.',
9
+ 'Filters: urlSubstring (case-insensitive), state, method.',
10
+ ].join(' '),
11
+ inputSchema: {
12
+ limit: z
13
+ .number()
14
+ .int()
15
+ .min(1)
16
+ .max(500)
17
+ .optional()
18
+ .describe('Max requests to return (most recent first). Default 50.'),
19
+ urlSubstring: z
20
+ .string()
21
+ .optional()
22
+ .describe('Case-insensitive substring filter on request URL.'),
23
+ state: z
24
+ .enum(['pending', 'response-received', 'finished', 'failed'])
25
+ .optional()
26
+ .describe('Filter by lifecycle state.'),
27
+ method: z
28
+ .string()
29
+ .optional()
30
+ .describe('Filter by HTTP method (e.g. GET, POST).'),
31
+ waitMs: z
32
+ .number()
33
+ .int()
34
+ .min(0)
35
+ .max(60_000)
36
+ .optional()
37
+ .describe('If buffer is empty, wait up to this many ms for requests to arrive before returning. Default 0.'),
38
+ },
39
+ handler: async ({ limit = 50, urlSubstring, state, method, waitMs = 0, }) => {
40
+ let ps;
41
+ try {
42
+ ps = await PageSession.get();
43
+ }
44
+ catch (e) {
45
+ return errorResult(`Unable to attach to a WKWebView page: ${describe(e)}`);
46
+ }
47
+ if (ps.networkBuffer.length === 0 && waitMs > 0) {
48
+ await sleep(waitMs);
49
+ }
50
+ let entries = ps.networkBuffer.slice();
51
+ if (urlSubstring) {
52
+ const needle = urlSubstring.toLowerCase();
53
+ entries = entries.filter(e => e.url.toLowerCase().includes(needle));
54
+ }
55
+ if (state) {
56
+ entries = entries.filter(e => e.state === state);
57
+ }
58
+ if (method) {
59
+ const upper = method.toUpperCase();
60
+ entries = entries.filter(e => (e.method ?? '').toUpperCase() === upper);
61
+ }
62
+ entries = entries.slice(-limit).reverse();
63
+ const lines = [];
64
+ lines.push(`# Network requests — ${entries.length} of ${ps.networkBuffer.length} buffered`);
65
+ lines.push('');
66
+ if (ps.attachInfo) {
67
+ lines.push(`Attached to: \`${ps.attachInfo.pageTitle || ps.attachInfo.pageUrl}\` (page id ${ps.attachInfo.pageId}, device ${ps.attachInfo.deviceId.slice(0, 12)}…)`);
68
+ lines.push(`Attached at: ${new Date(ps.attachInfo.attachedAt).toISOString()}`);
69
+ lines.push('');
70
+ }
71
+ if (entries.length === 0) {
72
+ lines.push(ps.networkBuffer.length === 0
73
+ ? 'No requests captured yet. Trigger network activity (reload / interact) and call again.'
74
+ : '(All buffered requests filtered out.)');
75
+ return textResult(lines.join('\n'));
76
+ }
77
+ lines.push('| # | state | method | status | type | url |');
78
+ lines.push('|---|-------|--------|--------|------|-----|');
79
+ entries.forEach((e, i) => {
80
+ lines.push(`| ${i + 1} | ${e.state} | ${e.method ?? '?'} | ${e.status ?? ''} | ${e.type ?? ''} | ${escapeCell(e.url)} |`);
81
+ });
82
+ const fails = entries.filter(e => e.state === 'failed' || (e.status ?? 0) >= 400);
83
+ if (fails.length > 0) {
84
+ lines.push('');
85
+ lines.push(`## Failures / 4xx-5xx (${fails.length})`);
86
+ for (const f of fails) {
87
+ lines.push(`- ${f.method ?? '?'} ${f.url} → ${f.state === 'failed' ? `failed: ${f.errorText}` : `${f.status} ${f.statusText ?? ''}`}`);
88
+ }
89
+ }
90
+ return textResult(lines.join('\n'));
91
+ },
92
+ };
93
+ function textResult(text) {
94
+ return { content: [{ type: 'text', text }] };
95
+ }
96
+ function errorResult(text) {
97
+ return { content: [{ type: 'text', text }], isError: true };
98
+ }
99
+ function describe(e) {
100
+ return e instanceof Error ? e.message : String(e);
101
+ }
102
+ function sleep(ms) {
103
+ return new Promise(r => setTimeout(r, ms));
104
+ }
105
+ function escapeCell(s) {
106
+ return s.replace(/\|/g, '\\|').replace(/\n/g, ' ');
107
+ }
108
+ //# sourceMappingURL=list_network_requests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list_network_requests.js","sourceRoot":"","sources":["../../../src/tools/list_network_requests.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ export declare const listPagesTool: {
3
+ name: string;
4
+ description: string;
5
+ inputSchema: {
6
+ proxyDeviceListUrl: z.ZodOptional<z.ZodString>;
7
+ };
8
+ handler: ({ proxyDeviceListUrl, }: {
9
+ proxyDeviceListUrl?: string;
10
+ }) => Promise<{
11
+ content: Array<{
12
+ type: "text";
13
+ text: string;
14
+ }>;
15
+ isError?: boolean;
16
+ }>;
17
+ };