safari-devtools-mcp 1.7.0 → 1.8.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 +10 -1
- package/build/src/index.d.ts.map +1 -1
- package/build/src/index.js +4 -0
- package/build/src/index.js.map +1 -1
- package/build/src/tools/ios-validation.d.ts +8 -0
- package/build/src/tools/ios-validation.d.ts.map +1 -0
- package/build/src/tools/ios-validation.js +282 -0
- package/build/src/tools/ios-validation.js.map +1 -0
- package/build/src/tools/webkit-compat.d.ts +10 -0
- package/build/src/tools/webkit-compat.d.ts.map +1 -0
- package/build/src/tools/webkit-compat.js +165 -0
- package/build/src/tools/webkit-compat.js.map +1 -0
- package/build/src/version.d.ts +1 -1
- package/build/src/version.js +1 -1
- package/package.json +1 -1
- package/skills/safari-specific-debugging/SKILL.md +18 -7
package/README.md
CHANGED
|
@@ -208,7 +208,7 @@ The server exposes guided debugging workflows as MCP prompts. Clients that suppo
|
|
|
208
208
|
| `safari-specific-debugging` | Debug WebKit quirks — CSS prefixes, JS feature gaps, ITP/CORS issues |
|
|
209
209
|
| `performance-debugging` | Performance analysis — Navigation Timing, Core Web Vitals, resource waterfall |
|
|
210
210
|
|
|
211
|
-
## Tools (
|
|
211
|
+
## Tools (45)
|
|
212
212
|
|
|
213
213
|
### Debugging
|
|
214
214
|
|
|
@@ -286,6 +286,15 @@ The server exposes guided debugging workflows as MCP prompts. Clients that suppo
|
|
|
286
286
|
| `press_key` | Press a key or combination (e.g., `Meta+A`, `Enter`) |
|
|
287
287
|
| `upload_file` | Upload a file through a file input |
|
|
288
288
|
|
|
289
|
+
### iOS Safari validation
|
|
290
|
+
|
|
291
|
+
| Tool | Description |
|
|
292
|
+
| ----------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
293
|
+
| `inspect_viewport_meta` | Parse the viewport meta tag and validate against iOS best practices (width, zoom, viewport-fit) |
|
|
294
|
+
| `get_safe_area_insets` | Read CSS safe-area-inset values and check whether the page handles notched devices correctly |
|
|
295
|
+
| `check_ios_web_app_readiness` | Audit the page for Add to Home Screen / PWA readiness (apple-touch-icon, manifest, splash screens, status bar) |
|
|
296
|
+
| `check_webkit_compatibility` | Check page CSS against the live Safari session via CSS.supports() |
|
|
297
|
+
|
|
289
298
|
## Architecture
|
|
290
299
|
|
|
291
300
|
```
|
package/build/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAClE,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAClE,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAyC/C,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,aAAkB,GAAG;IAClE,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,YAAY,CAAC;CACtB,CA2CA"}
|
package/build/src/index.js
CHANGED
|
@@ -22,6 +22,8 @@ import { tools as cookieTools } from './tools/cookies.js';
|
|
|
22
22
|
import { tools as storageTools } from './tools/storage.js';
|
|
23
23
|
import { tools as inputTools } from './tools/input.js';
|
|
24
24
|
import { tools as emulationTools, deviceTools } from './tools/emulation.js';
|
|
25
|
+
import { tools as iosValidationTools } from './tools/ios-validation.js';
|
|
26
|
+
import { tools as webkitCompatTools } from './tools/webkit-compat.js';
|
|
25
27
|
const allTools = [
|
|
26
28
|
...consoleTools,
|
|
27
29
|
...networkTools,
|
|
@@ -37,6 +39,8 @@ const allTools = [
|
|
|
37
39
|
...inputTools,
|
|
38
40
|
...emulationTools,
|
|
39
41
|
...deviceTools,
|
|
42
|
+
...iosValidationTools,
|
|
43
|
+
...webkitCompatTools,
|
|
40
44
|
];
|
|
41
45
|
export function createSafariMcpServer(options = {}) {
|
|
42
46
|
const { slim = false } = options;
|
package/build/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAC,SAAS,EAAC,MAAM,yCAAyC,CAAC;AAClE,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAC,eAAe,EAAC,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAC,OAAO,EAAC,MAAM,cAAc,CAAC;AAGrC,8CAA8C;AAC9C,OAAO,EAAC,KAAK,IAAI,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAC,KAAK,IAAI,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAC,KAAK,IAAI,WAAW,EAAC,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAC,KAAK,IAAI,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAC,KAAK,IAAI,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAC,KAAK,IAAI,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAC,KAAK,IAAI,gBAAgB,EAAC,MAAM,yBAAyB,CAAC;AAClE,OAAO,EAAC,KAAK,IAAI,WAAW,EAAC,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAC,KAAK,IAAI,QAAQ,EAAC,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAC,KAAK,IAAI,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAC,KAAK,IAAI,YAAY,EAAC,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAC,KAAK,IAAI,UAAU,EAAC,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAC,KAAK,IAAI,cAAc,EAAE,WAAW,EAAC,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iOS Safari validation tools.
|
|
3
|
+
*
|
|
4
|
+
* Inspect viewport meta tags, safe-area insets, and PWA readiness —
|
|
5
|
+
* the three things every iOS Safari developer has to fight with.
|
|
6
|
+
*/
|
|
7
|
+
export declare const tools: import("./types.js").ToolDef<import("zod").ZodRawShape>[];
|
|
8
|
+
//# sourceMappingURL=ios-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ios-validation.d.ts","sourceRoot":"","sources":["../../../src/tools/ios-validation.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAyEH,eAAO,MAAM,KAAK,2DA6QjB,CAAC"}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iOS Safari validation tools.
|
|
3
|
+
*
|
|
4
|
+
* Inspect viewport meta tags, safe-area insets, and PWA readiness —
|
|
5
|
+
* the three things every iOS Safari developer has to fight with.
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from './types.js';
|
|
8
|
+
function analyzeViewport(attrs) {
|
|
9
|
+
const issues = [];
|
|
10
|
+
if (Object.keys(attrs).length === 0) {
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
severity: 'error',
|
|
14
|
+
message: 'No viewport meta tag found. iOS Safari will render at 980px width and scale down.',
|
|
15
|
+
},
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
if (!attrs.width) {
|
|
19
|
+
issues.push({
|
|
20
|
+
severity: 'error',
|
|
21
|
+
message: 'Missing "width=device-width". Without this, Safari uses a default 980px layout width.',
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else if (attrs.width !== 'device-width') {
|
|
25
|
+
issues.push({
|
|
26
|
+
severity: 'warning',
|
|
27
|
+
message: `width="${attrs.width}" — consider using "device-width" for responsive layouts.`,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
if (!attrs['initial-scale']) {
|
|
31
|
+
issues.push({
|
|
32
|
+
severity: 'warning',
|
|
33
|
+
message: 'Missing "initial-scale=1". Some iOS Safari versions may not zoom correctly without it.',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (attrs['user-scalable'] === 'no' || attrs['maximum-scale'] === '1') {
|
|
37
|
+
issues.push({
|
|
38
|
+
severity: 'error',
|
|
39
|
+
message: 'Zoom is disabled (user-scalable=no or maximum-scale=1). This is an accessibility violation (WCAG 1.4.4) and Safari 10+ ignores it anyway.',
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (attrs['viewport-fit'] !== 'cover') {
|
|
43
|
+
issues.push({
|
|
44
|
+
severity: 'warning',
|
|
45
|
+
message: 'Missing "viewport-fit=cover". Required for safe-area-inset-* CSS env() values to work on notched devices (iPhone X+).',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
if (attrs['minimum-scale'] && parseFloat(attrs['minimum-scale']) < 1) {
|
|
49
|
+
issues.push({
|
|
50
|
+
severity: 'info',
|
|
51
|
+
message: `minimum-scale=${attrs['minimum-scale']}. Values below 1 allow zoom-out on iOS, which can cause layout issues.`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return issues;
|
|
55
|
+
}
|
|
56
|
+
// ---- Tools ----
|
|
57
|
+
export const tools = [
|
|
58
|
+
defineTool({
|
|
59
|
+
name: 'inspect_viewport_meta',
|
|
60
|
+
description: 'Parse the viewport meta tag and validate it against iOS Safari ' +
|
|
61
|
+
'best practices. Reports issues like missing width=device-width, ' +
|
|
62
|
+
'disabled zoom (accessibility violation), missing viewport-fit=cover ' +
|
|
63
|
+
'for safe areas, and other common misconfiguration.',
|
|
64
|
+
slimDescription: 'Validate iOS viewport meta tag.',
|
|
65
|
+
schema: {},
|
|
66
|
+
handler: async (_params, driver) => {
|
|
67
|
+
const result = await driver.runScript(`(() => {
|
|
68
|
+
const meta = document.querySelector('meta[name="viewport"]');
|
|
69
|
+
if (!meta) return { content: null, attrs: {} };
|
|
70
|
+
const content = meta.getAttribute('content') || '';
|
|
71
|
+
const attrs = {};
|
|
72
|
+
for (const part of content.split(',')) {
|
|
73
|
+
const [key, val] = part.split('=').map(s => s.trim());
|
|
74
|
+
if (key) attrs[key] = val || '';
|
|
75
|
+
}
|
|
76
|
+
return { content, attrs };
|
|
77
|
+
})()`);
|
|
78
|
+
const issues = analyzeViewport(result.attrs);
|
|
79
|
+
const lines = [];
|
|
80
|
+
if (result.content) {
|
|
81
|
+
lines.push(`Viewport meta: ${result.content}`);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
lines.push('⚠ No <meta name="viewport"> tag found.');
|
|
85
|
+
}
|
|
86
|
+
lines.push('');
|
|
87
|
+
if (issues.length === 0) {
|
|
88
|
+
lines.push('✅ No issues found. Viewport is correctly configured for iOS Safari.');
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
const errors = issues.filter(i => i.severity === 'error');
|
|
92
|
+
const warnings = issues.filter(i => i.severity === 'warning');
|
|
93
|
+
const infos = issues.filter(i => i.severity === 'info');
|
|
94
|
+
if (errors.length > 0) {
|
|
95
|
+
lines.push('Errors:');
|
|
96
|
+
for (const i of errors)
|
|
97
|
+
lines.push(` ❌ ${i.message}`);
|
|
98
|
+
}
|
|
99
|
+
if (warnings.length > 0) {
|
|
100
|
+
lines.push('Warnings:');
|
|
101
|
+
for (const i of warnings)
|
|
102
|
+
lines.push(` ⚠ ${i.message}`);
|
|
103
|
+
}
|
|
104
|
+
if (infos.length > 0) {
|
|
105
|
+
lines.push('Info:');
|
|
106
|
+
for (const i of infos)
|
|
107
|
+
lines.push(` ℹ ${i.message}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
}),
|
|
115
|
+
defineTool({
|
|
116
|
+
name: 'get_safe_area_insets',
|
|
117
|
+
description: 'Read the current CSS safe-area-inset values (top, right, bottom, left) ' +
|
|
118
|
+
'as seen by the page. These are non-zero on notched iPhones when ' +
|
|
119
|
+
'viewport-fit=cover is set. Also checks whether the page uses ' +
|
|
120
|
+
'env(safe-area-inset-*) in its stylesheets.',
|
|
121
|
+
slimDescription: 'Get iOS safe area inset values.',
|
|
122
|
+
schema: {},
|
|
123
|
+
handler: async (_params, driver) => {
|
|
124
|
+
const result = await driver.runScript(`(() => {
|
|
125
|
+
// Read computed env() values via a probe element
|
|
126
|
+
const probe = document.createElement('div');
|
|
127
|
+
probe.style.cssText =
|
|
128
|
+
'position:fixed;top:env(safe-area-inset-top,0px);' +
|
|
129
|
+
'right:env(safe-area-inset-right,0px);' +
|
|
130
|
+
'bottom:env(safe-area-inset-bottom,0px);' +
|
|
131
|
+
'left:env(safe-area-inset-left,0px);' +
|
|
132
|
+
'pointer-events:none;visibility:hidden;';
|
|
133
|
+
document.body.appendChild(probe);
|
|
134
|
+
const cs = getComputedStyle(probe);
|
|
135
|
+
const insets = {
|
|
136
|
+
top: cs.top, right: cs.right,
|
|
137
|
+
bottom: cs.bottom, left: cs.left,
|
|
138
|
+
};
|
|
139
|
+
probe.remove();
|
|
140
|
+
|
|
141
|
+
// Check viewport-fit=cover
|
|
142
|
+
const meta = document.querySelector('meta[name="viewport"]');
|
|
143
|
+
const viewportFitCover = meta
|
|
144
|
+
? (meta.getAttribute('content') || '').includes('viewport-fit=cover')
|
|
145
|
+
: false;
|
|
146
|
+
|
|
147
|
+
// Scan stylesheets for env(safe-area-inset usage
|
|
148
|
+
let usedInCSS = false;
|
|
149
|
+
try {
|
|
150
|
+
for (const sheet of document.styleSheets) {
|
|
151
|
+
try {
|
|
152
|
+
const text = [...sheet.cssRules].map(r => r.cssText).join(' ');
|
|
153
|
+
if (text.includes('safe-area-inset')) { usedInCSS = true; break; }
|
|
154
|
+
} catch {}
|
|
155
|
+
}
|
|
156
|
+
} catch {}
|
|
157
|
+
|
|
158
|
+
return { insets, viewportFitCover, usedInCSS };
|
|
159
|
+
})()`);
|
|
160
|
+
const lines = ['Safe area insets:'];
|
|
161
|
+
lines.push(` top: ${result.insets.top}`);
|
|
162
|
+
lines.push(` right: ${result.insets.right}`);
|
|
163
|
+
lines.push(` bottom: ${result.insets.bottom}`);
|
|
164
|
+
lines.push(` left: ${result.insets.left}`);
|
|
165
|
+
lines.push('');
|
|
166
|
+
if (!result.viewportFitCover) {
|
|
167
|
+
lines.push('⚠ viewport-fit=cover is NOT set. Safe area insets will always be 0 ' +
|
|
168
|
+
'even on notched devices. Add viewport-fit=cover to your viewport meta tag.');
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
lines.push('✅ viewport-fit=cover is set.');
|
|
172
|
+
}
|
|
173
|
+
lines.push(result.usedInCSS
|
|
174
|
+
? '✅ env(safe-area-inset-*) found in stylesheets.'
|
|
175
|
+
: '⚠ env(safe-area-inset-*) not found in any stylesheet. Content may be obscured by the notch/home indicator.');
|
|
176
|
+
return {
|
|
177
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
178
|
+
};
|
|
179
|
+
},
|
|
180
|
+
}),
|
|
181
|
+
defineTool({
|
|
182
|
+
name: 'check_ios_web_app_readiness',
|
|
183
|
+
description: 'Audit the page for iOS Safari "Add to Home Screen" / PWA readiness. ' +
|
|
184
|
+
'Checks apple-touch-icon, apple-mobile-web-app-capable, status bar style, ' +
|
|
185
|
+
'theme-color, manifest link, and splash screen configuration.',
|
|
186
|
+
slimDescription: 'Audit iOS PWA/home screen readiness.',
|
|
187
|
+
schema: {},
|
|
188
|
+
handler: async (_params, driver) => {
|
|
189
|
+
const result = await driver.runScript(`(() => {
|
|
190
|
+
const getMeta = (name) => {
|
|
191
|
+
const el = document.querySelector('meta[name="' + name + '"]');
|
|
192
|
+
return el ? el.getAttribute('content') : null;
|
|
193
|
+
};
|
|
194
|
+
const getLink = (rel) => {
|
|
195
|
+
const el = document.querySelector('link[rel="' + rel + '"]');
|
|
196
|
+
return el ? el.getAttribute('href') : null;
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const touchIcons = [...document.querySelectorAll('link[rel="apple-touch-icon"]')].map(el => ({
|
|
200
|
+
sizes: el.getAttribute('sizes') || 'unspecified',
|
|
201
|
+
href: el.getAttribute('href') || '',
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
const splashScreens = document.querySelectorAll('link[rel="apple-touch-startup-image"]').length;
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
capable: getMeta('apple-mobile-web-app-capable'),
|
|
208
|
+
statusBarStyle: getMeta('apple-mobile-web-app-status-bar-style'),
|
|
209
|
+
themeColor: getMeta('theme-color'),
|
|
210
|
+
manifestHref: getLink('manifest'),
|
|
211
|
+
touchIcons,
|
|
212
|
+
title: document.title,
|
|
213
|
+
appleSplashScreens: splashScreens,
|
|
214
|
+
};
|
|
215
|
+
})()`);
|
|
216
|
+
const checks = [];
|
|
217
|
+
// apple-mobile-web-app-capable
|
|
218
|
+
checks.push({
|
|
219
|
+
pass: result.capable === 'yes',
|
|
220
|
+
label: 'apple-mobile-web-app-capable',
|
|
221
|
+
detail: result.capable === 'yes'
|
|
222
|
+
? 'Set to "yes" — app will run in standalone mode.'
|
|
223
|
+
: 'Not set or not "yes". The app will open in Safari, not standalone.',
|
|
224
|
+
});
|
|
225
|
+
// apple-touch-icon
|
|
226
|
+
const has180 = result.touchIcons.some(i => i.sizes === '180x180');
|
|
227
|
+
checks.push({
|
|
228
|
+
pass: result.touchIcons.length > 0,
|
|
229
|
+
label: 'apple-touch-icon',
|
|
230
|
+
detail: result.touchIcons.length > 0
|
|
231
|
+
? `${result.touchIcons.length} icon(s): ${result.touchIcons.map(i => i.sizes).join(', ')}` +
|
|
232
|
+
(has180 ? '' : '. ⚠ Missing 180x180 (recommended for iPhone).')
|
|
233
|
+
: 'No apple-touch-icon found. iOS will use a screenshot as the icon.',
|
|
234
|
+
});
|
|
235
|
+
// theme-color
|
|
236
|
+
checks.push({
|
|
237
|
+
pass: result.themeColor !== null,
|
|
238
|
+
label: 'theme-color',
|
|
239
|
+
detail: result.themeColor !== null
|
|
240
|
+
? `Set to "${result.themeColor}".`
|
|
241
|
+
: 'Not set. Safari 15+ uses theme-color for the tab bar tint.',
|
|
242
|
+
});
|
|
243
|
+
// status bar style
|
|
244
|
+
checks.push({
|
|
245
|
+
pass: result.statusBarStyle !== null,
|
|
246
|
+
label: 'apple-mobile-web-app-status-bar-style',
|
|
247
|
+
detail: result.statusBarStyle !== null
|
|
248
|
+
? `Set to "${result.statusBarStyle}".`
|
|
249
|
+
: 'Not set. Defaults to "default" (black text on white). Consider "black-translucent" for full-screen feel.',
|
|
250
|
+
});
|
|
251
|
+
// manifest
|
|
252
|
+
checks.push({
|
|
253
|
+
pass: result.manifestHref !== null,
|
|
254
|
+
label: 'Web App Manifest',
|
|
255
|
+
detail: result.manifestHref !== null
|
|
256
|
+
? `Found: ${result.manifestHref}`
|
|
257
|
+
: 'No <link rel="manifest"> found. Required for PWA install prompts.',
|
|
258
|
+
});
|
|
259
|
+
// splash screens
|
|
260
|
+
checks.push({
|
|
261
|
+
pass: result.appleSplashScreens > 0,
|
|
262
|
+
label: 'apple-touch-startup-image',
|
|
263
|
+
detail: result.appleSplashScreens > 0
|
|
264
|
+
? `${result.appleSplashScreens} splash screen(s) defined.`
|
|
265
|
+
: 'No splash screens. Users will see a white screen while the app loads.',
|
|
266
|
+
});
|
|
267
|
+
const passed = checks.filter(c => c.pass).length;
|
|
268
|
+
const lines = [
|
|
269
|
+
`iOS Web App Readiness: ${passed}/${checks.length} checks passed`,
|
|
270
|
+
'',
|
|
271
|
+
];
|
|
272
|
+
for (const c of checks) {
|
|
273
|
+
lines.push(`${c.pass ? '✅' : '❌'} ${c.label}`);
|
|
274
|
+
lines.push(` ${c.detail}`);
|
|
275
|
+
}
|
|
276
|
+
return {
|
|
277
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
278
|
+
};
|
|
279
|
+
},
|
|
280
|
+
}),
|
|
281
|
+
];
|
|
282
|
+
//# sourceMappingURL=ios-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ios-validation.js","sourceRoot":"","sources":["../../../src/tools/ios-validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebKit CSS compatibility checker.
|
|
3
|
+
*
|
|
4
|
+
* Runs inside a live Safari WebDriver session. Extracts CSS from the page
|
|
5
|
+
* using structured DOM APIs (rule.style iteration — no regex, no false
|
|
6
|
+
* positives on custom properties), then runs CSS.supports() in the actual
|
|
7
|
+
* browser to report what is genuinely broken right now.
|
|
8
|
+
*/
|
|
9
|
+
export declare const tools: import("./types.js").ToolDef<import("zod").ZodRawShape>[];
|
|
10
|
+
//# sourceMappingURL=webkit-compat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webkit-compat.d.ts","sourceRoot":"","sources":["../../../src/tools/webkit-compat.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAuBH,eAAO,MAAM,KAAK,2DAkKjB,CAAC"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebKit CSS compatibility checker.
|
|
3
|
+
*
|
|
4
|
+
* Runs inside a live Safari WebDriver session. Extracts CSS from the page
|
|
5
|
+
* using structured DOM APIs (rule.style iteration — no regex, no false
|
|
6
|
+
* positives on custom properties), then runs CSS.supports() in the actual
|
|
7
|
+
* browser to report what is genuinely broken right now.
|
|
8
|
+
*/
|
|
9
|
+
import { defineTool } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Behavioral quirks that CSS.supports() cannot detect — the property IS
|
|
12
|
+
* "supported", it just renders incorrectly. Keep this list small, precise,
|
|
13
|
+
* and each entry must be a confirmed Safari rendering bug.
|
|
14
|
+
*/
|
|
15
|
+
const BEHAVIORAL_QUIRKS = [
|
|
16
|
+
{
|
|
17
|
+
property: 'position',
|
|
18
|
+
matchValue: 'sticky',
|
|
19
|
+
message: 'position:sticky silently fails inside an overflow:hidden or overflow:auto ' +
|
|
20
|
+
'ancestor in Safari. Use overflow:clip on the ancestor instead.',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
export const tools = [
|
|
24
|
+
defineTool({
|
|
25
|
+
name: 'check_webkit_compatibility',
|
|
26
|
+
description: 'Check CSS on the current page against the live Safari session. ' +
|
|
27
|
+
'Extracts every CSS property via structured DOM APIs, runs CSS.supports() ' +
|
|
28
|
+
'in the actual browser, and reports what is broken right now — unsupported ' +
|
|
29
|
+
'properties, missing -webkit- prefixes, and known Safari rendering quirks.',
|
|
30
|
+
slimDescription: 'Check CSS against live Safari via CSS.supports().',
|
|
31
|
+
schema: {},
|
|
32
|
+
handler: async (_params, driver) => {
|
|
33
|
+
// -----------------------------------------------------------------
|
|
34
|
+
// Step 1: Extract structured CSS from the page + run CSS.supports()
|
|
35
|
+
// in one round-trip. Uses rule.style iteration for canonical property
|
|
36
|
+
// names — custom properties (--*) are excluded automatically.
|
|
37
|
+
// -----------------------------------------------------------------
|
|
38
|
+
const results = await driver.runScript(`(() => {
|
|
39
|
+
// Collect unique property:value pairs from all accessible stylesheets
|
|
40
|
+
const seen = new Map();
|
|
41
|
+
|
|
42
|
+
function collectFromStyle(style) {
|
|
43
|
+
for (const prop of style) {
|
|
44
|
+
if (prop.startsWith('--')) continue;
|
|
45
|
+
if (seen.has(prop)) continue;
|
|
46
|
+
seen.set(prop, style.getPropertyValue(prop).trim());
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function processRules(rules) {
|
|
51
|
+
for (const rule of rules) {
|
|
52
|
+
// Skip @font-face — its descriptors (src, font-display, unicode-range)
|
|
53
|
+
// are not CSS properties and CSS.supports() always returns false for them
|
|
54
|
+
if (rule.constructor?.name === 'CSSFontFaceRule') continue;
|
|
55
|
+
if (rule.style) collectFromStyle(rule.style);
|
|
56
|
+
if (rule.cssRules) {
|
|
57
|
+
try { processRules(rule.cssRules); } catch {}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const sheet of document.styleSheets) {
|
|
63
|
+
try { processRules(sheet.cssRules); } catch {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Inline styles
|
|
67
|
+
for (const el of document.querySelectorAll('[style]')) {
|
|
68
|
+
collectFromStyle(el.style);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const totalProperties = seen.size;
|
|
72
|
+
const unsupported = [];
|
|
73
|
+
const needsPrefix = [];
|
|
74
|
+
|
|
75
|
+
for (const [prop, value] of seen) {
|
|
76
|
+
let supported = false;
|
|
77
|
+
try { supported = CSS.supports(prop, value); } catch {}
|
|
78
|
+
|
|
79
|
+
if (!supported) {
|
|
80
|
+
// Try again with a generic value — the extracted value might be
|
|
81
|
+
// a computed/resolved form CSS.supports doesn't accept
|
|
82
|
+
try { supported = CSS.supports(prop, 'initial'); } catch {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (supported) continue;
|
|
86
|
+
|
|
87
|
+
// Not supported unprefixed — check if -webkit- variant works
|
|
88
|
+
const webkitProp = '-webkit-' + prop;
|
|
89
|
+
let webkitSupported = false;
|
|
90
|
+
try { webkitSupported = CSS.supports(webkitProp, value); } catch {}
|
|
91
|
+
if (!webkitSupported) {
|
|
92
|
+
try { webkitSupported = CSS.supports(webkitProp, 'initial'); } catch {}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (webkitSupported) {
|
|
96
|
+
needsPrefix.push({ property: prop, value });
|
|
97
|
+
} else {
|
|
98
|
+
unsupported.push({ property: prop, value });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Collect a subset of computed properties for behavioral quirk checks
|
|
103
|
+
const body = document.body;
|
|
104
|
+
const computedProperties = {};
|
|
105
|
+
if (body) {
|
|
106
|
+
const quirkProps = ${JSON.stringify(BEHAVIORAL_QUIRKS.map(q => q.property))};
|
|
107
|
+
for (const prop of quirkProps) {
|
|
108
|
+
if (seen.has(prop)) {
|
|
109
|
+
computedProperties[prop] = seen.get(prop);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { totalProperties, unsupported, needsPrefix, computedProperties };
|
|
115
|
+
})()`);
|
|
116
|
+
// -----------------------------------------------------------------
|
|
117
|
+
// Step 2: Check behavioral quirks
|
|
118
|
+
// -----------------------------------------------------------------
|
|
119
|
+
const quirks = [];
|
|
120
|
+
for (const quirk of BEHAVIORAL_QUIRKS) {
|
|
121
|
+
const value = results.computedProperties[quirk.property];
|
|
122
|
+
if (value === undefined)
|
|
123
|
+
continue;
|
|
124
|
+
if (quirk.matchValue && !value.includes(quirk.matchValue))
|
|
125
|
+
continue;
|
|
126
|
+
quirks.push(quirk.message);
|
|
127
|
+
}
|
|
128
|
+
// -----------------------------------------------------------------
|
|
129
|
+
// Step 3: Format output
|
|
130
|
+
// -----------------------------------------------------------------
|
|
131
|
+
const total = results.unsupported.length + results.needsPrefix.length + quirks.length;
|
|
132
|
+
const lines = [];
|
|
133
|
+
if (total === 0) {
|
|
134
|
+
lines.push(`✅ No issues found. ${results.totalProperties} CSS properties checked via CSS.supports() in this Safari session.`);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
lines.push(`Found ${total} issue(s) — ${results.totalProperties} properties checked via CSS.supports() in this Safari session.`);
|
|
138
|
+
lines.push('');
|
|
139
|
+
if (results.unsupported.length > 0) {
|
|
140
|
+
lines.push(`❌ Unsupported in this Safari (${results.unsupported.length}):`);
|
|
141
|
+
for (const { property, value } of results.unsupported) {
|
|
142
|
+
const preview = value.length > 60 ? value.slice(0, 57) + '...' : value;
|
|
143
|
+
lines.push(` ${property}: ${preview}`);
|
|
144
|
+
}
|
|
145
|
+
lines.push('');
|
|
146
|
+
}
|
|
147
|
+
if (results.needsPrefix.length > 0) {
|
|
148
|
+
lines.push(`⚠ Needs -webkit- prefix (${results.needsPrefix.length}):`);
|
|
149
|
+
for (const { property } of results.needsPrefix) {
|
|
150
|
+
lines.push(` ${property} → use -webkit-${property} alongside it`);
|
|
151
|
+
}
|
|
152
|
+
lines.push('');
|
|
153
|
+
}
|
|
154
|
+
if (quirks.length > 0) {
|
|
155
|
+
lines.push(`ℹ Known Safari rendering quirks (${quirks.length}):`);
|
|
156
|
+
for (const msg of quirks) {
|
|
157
|
+
lines.push(` ${msg}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
];
|
|
165
|
+
//# sourceMappingURL=webkit-compat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"webkit-compat.js","sourceRoot":"","sources":["../../../src/tools/webkit-compat.ts"],"names":[],"mappings":"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"}
|
package/build/src/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "1.
|
|
1
|
+
export declare const VERSION = "1.8.1";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/build/src/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safari-devtools-mcp",
|
|
3
3
|
"mcpName": "io.github.HayoDev/safari-devtools-mcp",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.8.1",
|
|
5
5
|
"description": "Safari DevTools MCP — real browser debugging with network interception, DOM inspection, cookie/storage management, and CSS analysis for AI agents on macOS",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "build/src/index.js",
|
|
@@ -7,18 +7,23 @@ or when debugging WebKit-specific behavior.
|
|
|
7
7
|
|
|
8
8
|
### CSS issues
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
Start by running `check_webkit_compatibility` — it checks every CSS property
|
|
11
|
+
on the page against the live Safari session via `CSS.supports()` and reports
|
|
12
|
+
what is actually broken (unsupported properties, missing `-webkit-` prefixes):
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
check_webkit_compatibility
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For specific elements, inspect computed styles directly:
|
|
12
19
|
|
|
13
20
|
```
|
|
14
21
|
get_computed_style uid="<element-uid>" properties=["display", "gap", "aspect-ratio", "container-type", "backdrop-filter"]
|
|
15
22
|
```
|
|
16
23
|
|
|
17
|
-
**
|
|
24
|
+
**Common Safari CSS gotchas:**
|
|
18
25
|
|
|
19
|
-
-
|
|
20
|
-
- `aspect-ratio`: Works, but may not apply in some flex/grid contexts.
|
|
21
|
-
- `-webkit-backdrop-filter`: Still requires prefix in some versions.
|
|
26
|
+
- `-webkit-backdrop-filter`: Still requires prefix in Safari <18.
|
|
22
27
|
- `100vh` on iOS Safari includes the address bar — use `100dvh` instead.
|
|
23
28
|
- `position: sticky` inside `overflow: auto` containers often breaks.
|
|
24
29
|
- `:has()` selector: Safari was first to ship it, but edge cases may differ.
|
|
@@ -115,7 +120,13 @@ see what the a11y tree reports, then `take_screenshot` for visual comparison.
|
|
|
115
120
|
evaluate_script function="() => { /* feature detection code */ }"
|
|
116
121
|
```
|
|
117
122
|
|
|
118
|
-
6. Check CSS
|
|
123
|
+
6. Check CSS compatibility across the page:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
check_webkit_compatibility
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
7. Inspect a specific element's styles:
|
|
119
130
|
```
|
|
120
131
|
get_computed_style uid="<element-uid>"
|
|
121
132
|
```
|