@real-a11y-dev/mcp 0.1.0-beta.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/LICENSE +21 -0
- package/README.md +99 -0
- package/dist/browser.d.ts +201 -0
- package/dist/browser.js +3 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunk-EXVRX5JP.js +401 -0
- package/dist/chunk-EXVRX5JP.js.map +1 -0
- package/dist/chunk-SF5GJYVW.js +411 -0
- package/dist/chunk-SF5GJYVW.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +47 -0
- package/dist/server.js +4 -0
- package/dist/server.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
|
|
5
|
+
// src/browser.ts
|
|
6
|
+
function bundlePath() {
|
|
7
|
+
const require2 = createRequire(import.meta.url);
|
|
8
|
+
const testingEntry = require2.resolve("@real-a11y-dev/testing");
|
|
9
|
+
return join(dirname(testingEntry), "page-bundle.iife.global.js");
|
|
10
|
+
}
|
|
11
|
+
var cachedBundle;
|
|
12
|
+
function readBundle() {
|
|
13
|
+
if (!cachedBundle) {
|
|
14
|
+
try {
|
|
15
|
+
cachedBundle = readFileSync(bundlePath(), "utf8");
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"Real A11y extraction bundle is missing \u2014 reinstall dependencies (is @real-a11y-dev/testing installed?)."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return cachedBundle;
|
|
23
|
+
}
|
|
24
|
+
function assertOpenableUrl(url) {
|
|
25
|
+
let scheme;
|
|
26
|
+
try {
|
|
27
|
+
scheme = new URL(url).protocol.replace(/:$/, "").toLowerCase();
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error(`Not a valid absolute URL: ${JSON.stringify(url)}`);
|
|
30
|
+
}
|
|
31
|
+
if (scheme === "http" || scheme === "https" || scheme === "data") return;
|
|
32
|
+
if (scheme === "file" && process.env.REAL_A11Y_MCP_ALLOW_FILE === "1") return;
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Refusing to open a ${scheme}: URL \u2014 only http(s) (and data:) are allowed` + (scheme === "file" ? " (set REAL_A11Y_MCP_ALLOW_FILE=1 to permit file://)." : ".")
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
var NATIVE_DROP = /* @__PURE__ */ new Set([
|
|
38
|
+
"StaticText",
|
|
39
|
+
"InlineTextBox",
|
|
40
|
+
"LineBreak",
|
|
41
|
+
"LabelText",
|
|
42
|
+
"generic",
|
|
43
|
+
"none",
|
|
44
|
+
"presentation",
|
|
45
|
+
"RootWebArea"
|
|
46
|
+
]);
|
|
47
|
+
var NATIVE_ROLE_MAP = { image: "img" };
|
|
48
|
+
function serializeNativeAX(nodes) {
|
|
49
|
+
const byId = new Map(nodes.map((n) => [n.nodeId, n]));
|
|
50
|
+
const roots = nodes.filter((n) => !n.parentId);
|
|
51
|
+
const treeLines = [];
|
|
52
|
+
const pairs = [];
|
|
53
|
+
const walk = (node, depth) => {
|
|
54
|
+
const role = node.role?.value ?? "";
|
|
55
|
+
const drop = node.ignored || NATIVE_DROP.has(role);
|
|
56
|
+
let childDepth = depth;
|
|
57
|
+
if (!drop && role) {
|
|
58
|
+
const mapped = NATIVE_ROLE_MAP[role] ?? role;
|
|
59
|
+
const name = (node.name?.value ?? "").replace(/\s+/g, " ").trim();
|
|
60
|
+
const pair = name ? `${mapped} "${name}"` : mapped;
|
|
61
|
+
treeLines.push(`${" ".repeat(depth)}${pair}`);
|
|
62
|
+
pairs.push(pair);
|
|
63
|
+
childDepth = depth + 1;
|
|
64
|
+
}
|
|
65
|
+
for (const cid of node.childIds ?? []) {
|
|
66
|
+
const child = byId.get(cid);
|
|
67
|
+
if (child) walk(child, childDepth);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
for (const root of roots) walk(root, 0);
|
|
71
|
+
return { tree: treeLines.join("\n"), pairs };
|
|
72
|
+
}
|
|
73
|
+
var cachedExpr;
|
|
74
|
+
function bundleExpression() {
|
|
75
|
+
if (!cachedExpr) {
|
|
76
|
+
cachedExpr = `(function(){
|
|
77
|
+
${readBundle()}
|
|
78
|
+
;globalThis.__realA11y__=__realA11y__;})()`;
|
|
79
|
+
}
|
|
80
|
+
return cachedExpr;
|
|
81
|
+
}
|
|
82
|
+
var BrowserSession = class {
|
|
83
|
+
constructor(opts = {}) {
|
|
84
|
+
this.opts = opts;
|
|
85
|
+
if (opts.storageState && opts.cdpEndpoint) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
"storageState is for launched browsers \u2014 a CDP connection reuses the running browser's own session, so the two can't be combined."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
opts;
|
|
92
|
+
browser;
|
|
93
|
+
context;
|
|
94
|
+
page;
|
|
95
|
+
/** Signature of the current context's emulation, to detect device changes. */
|
|
96
|
+
emulationKey = "";
|
|
97
|
+
/** Over CDP: whether we created `page` (so `close()` only closes our tab). */
|
|
98
|
+
ownsPage = false;
|
|
99
|
+
/** Serializes every operation so concurrent tool calls can't race the page. */
|
|
100
|
+
queue = Promise.resolve();
|
|
101
|
+
/** Origins allowed for extraction, or null when unrestricted. */
|
|
102
|
+
get allowedOrigins() {
|
|
103
|
+
const list = this.opts.allowedOrigins;
|
|
104
|
+
return list && list.length ? new Set(list) : null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Run `fn` after any in-flight operation. MCP clients dispatch tool calls
|
|
108
|
+
* concurrently, but all tools share one mutable `page`, so we single-flight.
|
|
109
|
+
*/
|
|
110
|
+
run(fn) {
|
|
111
|
+
const result = this.queue.then(fn, fn);
|
|
112
|
+
this.queue = result.then(
|
|
113
|
+
() => void 0,
|
|
114
|
+
() => void 0
|
|
115
|
+
);
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
/** Navigate to `url`, settle, then confirm the bundle initialized. */
|
|
119
|
+
async open(url, options = {}) {
|
|
120
|
+
return this.run(async () => {
|
|
121
|
+
assertOpenableUrl(url);
|
|
122
|
+
const {
|
|
123
|
+
waitUntil = "load",
|
|
124
|
+
settleMs = 0,
|
|
125
|
+
device,
|
|
126
|
+
viewport,
|
|
127
|
+
timeoutMs
|
|
128
|
+
} = options;
|
|
129
|
+
const page = await this.ensurePage({ device, viewport });
|
|
130
|
+
try {
|
|
131
|
+
await page.goto(url, {
|
|
132
|
+
waitUntil,
|
|
133
|
+
...timeoutMs != null ? { timeout: timeoutMs } : {}
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
const isTimeout = err instanceof Error && err.name === "TimeoutError";
|
|
137
|
+
if (!(isTimeout && waitUntil === "networkidle")) throw err;
|
|
138
|
+
}
|
|
139
|
+
if (settleMs > 0) await page.waitForTimeout(settleMs);
|
|
140
|
+
if (page.url() === "about:blank") {
|
|
141
|
+
throw new Error(
|
|
142
|
+
`Navigation to ${url} did not complete (still on about:blank). Try waitUntil:"load" or a larger timeoutMs.`
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
this.assertAllowedOrigin(page.url());
|
|
146
|
+
await this.injectBundle(page);
|
|
147
|
+
await this.verifyReady(page);
|
|
148
|
+
return { title: await page.title(), url: page.url() };
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Run a named export from the injected bundle against a root selector.
|
|
153
|
+
* Re-injects first if a navigation wiped the global.
|
|
154
|
+
*/
|
|
155
|
+
async call(fn, rootSelector, args = []) {
|
|
156
|
+
return this.run(async () => {
|
|
157
|
+
const page = this.requirePage();
|
|
158
|
+
await this.ensureInjected(page);
|
|
159
|
+
return page.evaluate(
|
|
160
|
+
({ fn: fn2, selector, args: args2 }) => {
|
|
161
|
+
const ra = globalThis.__realA11y__;
|
|
162
|
+
if (!ra) throw new Error("__realA11y__ is not present on the page.");
|
|
163
|
+
if (typeof ra[fn2] !== "function") {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Real A11y bundle has no "${fn2}" \u2014 the installed @real-a11y-dev/testing is too old for this MCP server; upgrade it.`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
let root;
|
|
169
|
+
try {
|
|
170
|
+
root = document.querySelector(selector);
|
|
171
|
+
} catch {
|
|
172
|
+
throw new Error(`Invalid rootSelector: "${selector}".`);
|
|
173
|
+
}
|
|
174
|
+
if (!root) {
|
|
175
|
+
if (selector === "body" && document.body) root = document.body;
|
|
176
|
+
else {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`rootSelector "${selector}" matched no element on the page.`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return ra[fn2](root, ...args2);
|
|
183
|
+
},
|
|
184
|
+
{ fn, selector: rootSelector, args }
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Extract the a11y tree **once** and derive all four views from it inside a
|
|
190
|
+
* single `page.evaluate`, so findings / tree / outline / tab order always
|
|
191
|
+
* describe the same instant — no cross-call drift on a moving page.
|
|
192
|
+
*/
|
|
193
|
+
async snapshot(rootSelector, options = {}) {
|
|
194
|
+
return this.run(async () => {
|
|
195
|
+
const page = this.requirePage();
|
|
196
|
+
await this.ensureInjected(page);
|
|
197
|
+
return page.evaluate(
|
|
198
|
+
({ selector, rules, includeGeneric }) => {
|
|
199
|
+
const ra = globalThis.__realA11y__;
|
|
200
|
+
if (!ra || typeof ra.extractA11yTree !== "function") {
|
|
201
|
+
throw new Error(
|
|
202
|
+
"Real A11y bundle missing/too old \u2014 upgrade @real-a11y-dev/testing."
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
let el;
|
|
206
|
+
try {
|
|
207
|
+
el = document.querySelector(selector);
|
|
208
|
+
} catch {
|
|
209
|
+
throw new Error(`Invalid rootSelector: "${selector}".`);
|
|
210
|
+
}
|
|
211
|
+
if (!el) {
|
|
212
|
+
if (selector === "body" && document.body) el = document.body;
|
|
213
|
+
else {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`rootSelector "${selector}" matched no element on the page.`
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const tree = ra.extractA11yTree(el);
|
|
220
|
+
return {
|
|
221
|
+
findings: ra.collectFindings(
|
|
222
|
+
tree,
|
|
223
|
+
rules && rules.length ? rules : void 0
|
|
224
|
+
),
|
|
225
|
+
tree: ra.auditSnapshot(tree, { includeGeneric }),
|
|
226
|
+
outline: ra.outlineSnapshot(tree),
|
|
227
|
+
tabOrder: ra.tabSequenceSnapshot(tree)
|
|
228
|
+
};
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
selector: rootSelector,
|
|
232
|
+
rules: options.rules ?? null,
|
|
233
|
+
includeGeneric: options.includeGeneric ?? false
|
|
234
|
+
}
|
|
235
|
+
);
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Chromium's own accessibility tree, straight from Blink via the CDP
|
|
240
|
+
* `Accessibility` domain — the authoritative computation, not our
|
|
241
|
+
* reimplementation. Used to cross-check custom-engine fidelity.
|
|
242
|
+
*/
|
|
243
|
+
async nativeAX() {
|
|
244
|
+
return this.run(async () => {
|
|
245
|
+
const page = this.requirePage();
|
|
246
|
+
const client = await page.context().newCDPSession(page);
|
|
247
|
+
try {
|
|
248
|
+
await client.send("Accessibility.enable");
|
|
249
|
+
const { nodes } = await client.send(
|
|
250
|
+
"Accessibility.getFullAXTree"
|
|
251
|
+
);
|
|
252
|
+
return serializeNativeAX(nodes);
|
|
253
|
+
} finally {
|
|
254
|
+
await client.detach().catch(() => {
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Capture the current context's storage state (cookies + origin storage) —
|
|
261
|
+
* the "save" step behind the CLI's `login` helper. Returns the plain object;
|
|
262
|
+
* the caller owns serialization + file writing (permissions, atomicity).
|
|
263
|
+
* `indexedDB` is caller-gated: it's a no-op on Playwright < 1.51, so the CLI
|
|
264
|
+
* passes it only when the resolved version supports it.
|
|
265
|
+
*/
|
|
266
|
+
async captureStorageState(options = {}) {
|
|
267
|
+
return this.run(async () => {
|
|
268
|
+
const context = this.context;
|
|
269
|
+
if (!context) {
|
|
270
|
+
throw new Error("No browser context is open \u2014 call open() first.");
|
|
271
|
+
}
|
|
272
|
+
return context.storageState(options.indexedDB ? { indexedDB: true } : {});
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async close() {
|
|
276
|
+
return this.run(async () => {
|
|
277
|
+
if (this.opts.cdpEndpoint) {
|
|
278
|
+
if (this.ownsPage) await this.page?.close().catch(() => {
|
|
279
|
+
});
|
|
280
|
+
await this.browser?.close().catch(() => {
|
|
281
|
+
});
|
|
282
|
+
} else {
|
|
283
|
+
await this.context?.close().catch(() => {
|
|
284
|
+
});
|
|
285
|
+
await this.browser?.close().catch(() => {
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
this.browser = void 0;
|
|
289
|
+
this.context = void 0;
|
|
290
|
+
this.page = void 0;
|
|
291
|
+
this.ownsPage = false;
|
|
292
|
+
this.emulationKey = "";
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
hasPage() {
|
|
296
|
+
return this.page !== void 0;
|
|
297
|
+
}
|
|
298
|
+
// ── internals ────────────────────────────────────────────────────────────
|
|
299
|
+
requirePage() {
|
|
300
|
+
if (!this.page) {
|
|
301
|
+
throw new Error("No page is open. Call the open_page tool first.");
|
|
302
|
+
}
|
|
303
|
+
return this.page;
|
|
304
|
+
}
|
|
305
|
+
/** Refuse extraction when the final URL's origin isn't allowlisted. */
|
|
306
|
+
assertAllowedOrigin(finalUrl) {
|
|
307
|
+
const allowed = this.allowedOrigins;
|
|
308
|
+
if (!allowed) return;
|
|
309
|
+
let origin;
|
|
310
|
+
try {
|
|
311
|
+
origin = new URL(finalUrl).origin;
|
|
312
|
+
} catch {
|
|
313
|
+
throw new Error(`Refusing to audit an unparseable URL: ${finalUrl}`);
|
|
314
|
+
}
|
|
315
|
+
if (!allowed.has(origin)) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Refusing to audit ${origin}: not an allowed audit origin under an authenticated session (a redirect may have left the intended site).`
|
|
318
|
+
);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
async ensurePage(emu = {}) {
|
|
322
|
+
const { chromium, devices } = await import('playwright');
|
|
323
|
+
if (this.opts.cdpEndpoint) {
|
|
324
|
+
if (emu.device || emu.viewport) {
|
|
325
|
+
throw new Error(
|
|
326
|
+
"Device / viewport emulation is not supported over a CDP connection \u2014 it reuses the running browser's own context."
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
if (this.page && !this.page.isClosed()) return this.page;
|
|
330
|
+
if (!this.browser || !this.browser.isConnected()) {
|
|
331
|
+
this.browser = await chromium.connectOverCDP(this.opts.cdpEndpoint);
|
|
332
|
+
}
|
|
333
|
+
this.context = this.browser.contexts()[0] ?? await this.browser.newContext();
|
|
334
|
+
this.page = await this.context.newPage();
|
|
335
|
+
this.ownsPage = true;
|
|
336
|
+
return this.page;
|
|
337
|
+
}
|
|
338
|
+
const key = JSON.stringify({
|
|
339
|
+
device: emu.device ?? null,
|
|
340
|
+
viewport: emu.viewport ?? null
|
|
341
|
+
});
|
|
342
|
+
if (this.page && !this.page.isClosed() && key === this.emulationKey) {
|
|
343
|
+
return this.page;
|
|
344
|
+
}
|
|
345
|
+
if (!this.browser || !this.browser.isConnected()) {
|
|
346
|
+
this.browser = await chromium.launch({
|
|
347
|
+
headless: this.opts.headless ?? true,
|
|
348
|
+
...this.opts.proxy ? { proxy: this.opts.proxy } : {}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
let ctxOpts = {};
|
|
352
|
+
if (emu.device) {
|
|
353
|
+
const descriptor = devices[emu.device];
|
|
354
|
+
if (!descriptor) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
`Unknown device "${emu.device}". Use a Playwright device name such as "iPhone 13", "Pixel 7", or "iPad Pro 11".`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
ctxOpts = descriptor;
|
|
360
|
+
}
|
|
361
|
+
if (emu.viewport) ctxOpts = { ...ctxOpts, viewport: emu.viewport };
|
|
362
|
+
if (this.opts.storageState) {
|
|
363
|
+
ctxOpts = { ...ctxOpts, storageState: this.opts.storageState };
|
|
364
|
+
}
|
|
365
|
+
if (this.context) {
|
|
366
|
+
await this.context.close().catch(() => {
|
|
367
|
+
});
|
|
368
|
+
this.context = void 0;
|
|
369
|
+
this.page = void 0;
|
|
370
|
+
}
|
|
371
|
+
this.context = await this.browser.newContext(ctxOpts);
|
|
372
|
+
this.page = await this.context.newPage();
|
|
373
|
+
this.emulationKey = key;
|
|
374
|
+
return this.page;
|
|
375
|
+
}
|
|
376
|
+
/** Evaluate the bundle in the page (CSP / Trusted-Types-safe via CDP). */
|
|
377
|
+
async injectBundle(page) {
|
|
378
|
+
await page.evaluate(bundleExpression());
|
|
379
|
+
}
|
|
380
|
+
/** (Re)inject the bundle if a navigation wiped the global. */
|
|
381
|
+
async ensureInjected(page) {
|
|
382
|
+
if (!await this.isReady(page)) await this.injectBundle(page);
|
|
383
|
+
}
|
|
384
|
+
/** Confirm the bundle initialized; throw a clear error if not. */
|
|
385
|
+
async verifyReady(page) {
|
|
386
|
+
if (!await this.isReady(page)) {
|
|
387
|
+
throw new Error(
|
|
388
|
+
"Real A11y extraction bundle did not initialize on this page \u2014 a strict Content-Security-Policy may be blocking script evaluation here."
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
isReady(page) {
|
|
393
|
+
return page.evaluate(
|
|
394
|
+
() => typeof globalThis.__realA11y__ === "object"
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
export { BrowserSession, assertOpenableUrl };
|
|
400
|
+
//# sourceMappingURL=chunk-EXVRX5JP.js.map
|
|
401
|
+
//# sourceMappingURL=chunk-EXVRX5JP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/browser.ts"],"names":["require","fn","args"],"mappings":"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","file":"chunk-EXVRX5JP.js","sourcesContent":["/**\n * Browser session for the MCP server.\n *\n * Mirrors the architecture of `@real-a11y-dev/testing/playwright`: drive a real\n * browser with Playwright, inject the pre-built IIFE page-bundle (which sets\n * `window.__realA11y__`), and route each a11y query through `page.evaluate()`.\n *\n * A real browser is required — the extraction engine depends on\n * `getComputedStyle`/layout to decide what is exposed to assistive tech, which\n * a serverside jsdom cannot faithfully reproduce.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join } from \"node:path\";\n\nimport type { Finding } from \"@real-a11y-dev/testing\";\nimport type {\n Browser,\n BrowserContext,\n BrowserContextOptions,\n Page,\n} from \"playwright\";\n\n/** Resolve the testing package's injected page-bundle from node_modules. */\nfunction bundlePath(): string {\n const require = createRequire(import.meta.url);\n // Resolves to @real-a11y-dev/testing/dist/index.* — the bundle sits beside it.\n const testingEntry = require.resolve(\"@real-a11y-dev/testing\");\n return join(dirname(testingEntry), \"page-bundle.iife.global.js\");\n}\n\nlet cachedBundle: string | undefined;\nfunction readBundle(): string {\n if (!cachedBundle) {\n try {\n cachedBundle = readFileSync(bundlePath(), \"utf8\");\n } catch {\n // Don't leak the absolute local path into tool output.\n throw new Error(\n \"Real A11y extraction bundle is missing — reinstall dependencies (is @real-a11y-dev/testing installed?).\",\n );\n }\n }\n return cachedBundle;\n}\n\n/**\n * Reject non-web URLs before navigating. `file://` is a local-file exfiltration\n * primitive for an LLM-driven server (open `file:///…/.env`, then read the DOM\n * back), so it's blocked unless `REAL_A11Y_MCP_ALLOW_FILE=1`. `data:` is allowed\n * (it's caller-supplied inline content, not a filesystem read).\n */\nexport function assertOpenableUrl(url: string): void {\n let scheme: string;\n try {\n scheme = new URL(url).protocol.replace(/:$/, \"\").toLowerCase();\n } catch {\n throw new Error(`Not a valid absolute URL: ${JSON.stringify(url)}`);\n }\n if (scheme === \"http\" || scheme === \"https\" || scheme === \"data\") return;\n if (scheme === \"file\" && process.env.REAL_A11Y_MCP_ALLOW_FILE === \"1\") return;\n throw new Error(\n `Refusing to open a ${scheme}: URL — only http(s) (and data:) are allowed` +\n (scheme === \"file\"\n ? \" (set REAL_A11Y_MCP_ALLOW_FILE=1 to permit file://).\"\n : \".\"),\n );\n}\n\n/** A subset of a Chromium CDP `Accessibility.AXNode`. */\ninterface AXNode {\n nodeId: string;\n parentId?: string;\n childIds?: string[];\n role?: { value?: string };\n name?: { value?: string };\n ignored?: boolean;\n}\n\n// Native roles that are structural noise vs. our custom tree: text runs and\n// generic wrappers the custom serializer collapses into names / drops.\nconst NATIVE_DROP = new Set([\n \"StaticText\",\n \"InlineTextBox\",\n \"LineBreak\",\n \"LabelText\",\n \"generic\",\n \"none\",\n \"presentation\",\n \"RootWebArea\",\n]);\n// Blink AX role → the ARIA role our custom tree uses, where they differ.\nconst NATIVE_ROLE_MAP: Record<string, string> = { image: \"img\" };\n\n/**\n * Reconstruct Chromium's native accessibility tree from a flat `getFullAXTree`\n * node list and serialize it in the same `role \"name\"` shape our custom tree\n * uses — so the two are comparable. Returns the indented tree plus a flat list\n * of role+name pairs (for order/indent-insensitive diffing).\n */\nfunction serializeNativeAX(nodes: AXNode[]): { tree: string; pairs: string[] } {\n const byId = new Map(nodes.map((n) => [n.nodeId, n]));\n const roots = nodes.filter((n) => !n.parentId);\n const treeLines: string[] = [];\n const pairs: string[] = [];\n\n const walk = (node: AXNode, depth: number): void => {\n const role = node.role?.value ?? \"\";\n const drop = node.ignored || NATIVE_DROP.has(role);\n let childDepth = depth;\n if (!drop && role) {\n const mapped = NATIVE_ROLE_MAP[role] ?? role;\n const name = (node.name?.value ?? \"\").replace(/\\s+/g, \" \").trim();\n const pair = name ? `${mapped} \"${name}\"` : mapped;\n treeLines.push(`${\" \".repeat(depth)}${pair}`);\n pairs.push(pair);\n childDepth = depth + 1;\n }\n for (const cid of node.childIds ?? []) {\n const child = byId.get(cid);\n if (child) walk(child, childDepth);\n }\n };\n\n for (const root of roots) walk(root, 0);\n return { tree: treeLines.join(\"\\n\"), pairs };\n}\n\nlet cachedExpr: string | undefined;\n/**\n * The IIFE bundle wrapped as a single self-executing expression that hoists its\n * global (`var __realA11y__ = …`) onto `globalThis`.\n *\n * We inject this via `page.evaluate`, which runs through the CDP runtime and is\n * **not** subject to the page's CSP or Trusted Types. `addScriptTag` (sets\n * `<script>.text`) is blocked by `require-trusted-types-for 'script'`, and\n * `addInitScript` is CSP-gated too — both fail on YouTube, Google, GitHub, and\n * many enterprise apps. Evaluating the source directly is the robust path.\n */\nfunction bundleExpression(): string {\n if (!cachedExpr) {\n cachedExpr = `(function(){\\n${readBundle()}\\n;globalThis.__realA11y__=__realA11y__;})()`;\n }\n return cachedExpr;\n}\n\nexport interface BrowserSessionOptions {\n /** Launch headless (default true). Ignored when `cdpEndpoint` is set. */\n headless?: boolean;\n /**\n * Attach to a user's already-running Chrome over the DevTools protocol\n * instead of launching a fresh browser (e.g. \"http://localhost:9222\").\n */\n cdpEndpoint?: string;\n /**\n * Network proxy for the launched browser. Chromium does not honor\n * `HTTP_PROXY`/`HTTPS_PROXY` env vars on its own, so callers on corporate\n * networks must pass one explicitly. Ignored when `cdpEndpoint` is set (the\n * running browser already has its own network config).\n */\n proxy?: {\n server: string;\n bypass?: string;\n username?: string;\n password?: string;\n };\n /**\n * Path to a Playwright storage-state JSON (cookies + origin storage), loaded\n * into every **launched** context so pages open already authenticated. The\n * user creates it out-of-band (e.g. the CLI's `login` helper); this is never\n * an agent-supplied parameter. Applied at the single `newContext()` site, so\n * it survives device-emulation context rebuilds. Rejected together with\n * `cdpEndpoint` (a CDP connection reuses the running browser's own session).\n */\n storageState?: string;\n /**\n * When set (non-empty), extraction is refused unless the page's **final**\n * origin (after redirects) is in this allowlist. This is the control that\n * stops a redirect from an intended target to a different origin from\n * silently auditing an authenticated page the operator never asked for.\n * Empty/absent ⇒ no origin restriction (anonymous audits follow redirects\n * freely — logged-out content isn't sensitive). Origins are compared as\n * `new URL(...).origin` strings.\n */\n allowedOrigins?: string[];\n}\n\n/** Navigation / settle options for {@link A11ySession.open}. */\nexport interface OpenOptions {\n /**\n * Playwright navigation wait state. `\"networkidle\"` waits until the network\n * has been quiet for 500ms — the most reliable \"the SPA finished rendering\"\n * signal, at the cost of latency. Default `\"load\"`.\n */\n waitUntil?: \"load\" | \"domcontentloaded\" | \"networkidle\" | \"commit\";\n /**\n * Extra fixed settle time (ms) after the wait state, to let late-firing JS\n * (async content, consent dialogs) reach a stable state before extraction.\n * Default 0.\n */\n settleMs?: number;\n /**\n * Emulate a device by Playwright device name — e.g. `\"iPhone 13\"`,\n * `\"Pixel 7\"`, `\"iPad Pro 11\"`. Sets viewport, user-agent, touch, and device\n * scale so the extracted tree reflects the **mobile/tablet** layout, not\n * desktop. Changing device between calls rebuilds the browser context.\n * Not supported over a `cdpEndpoint` (reuses the running browser's context).\n */\n device?: string;\n /**\n * Explicit viewport override, e.g. `{ width: 375, height: 812 }`. Layered on\n * top of `device` when both are given.\n */\n viewport?: { width: number; height: number };\n /** Navigation timeout in ms. Default is Playwright's 30s. */\n timeoutMs?: number;\n}\n\n/** Options for a single-extraction {@link A11ySession.snapshot}. */\nexport interface SnapshotOptions {\n /** Rules to run for `findings`. Omit to run all. */\n rules?: string[];\n /** Include generic container nodes in `tree`. Default false. */\n includeGeneric?: boolean;\n}\n\n/**\n * All four views derived from **one** extraction — so they can never disagree.\n * The fix for cross-call drift: `findings`, `tree`, `outline`, and `tabOrder`\n * are computed from a single a11y tree snapshot, not four separate extractions.\n */\nexport interface PageSnapshot {\n findings: Finding[];\n tree: string;\n outline: string;\n tabOrder: string;\n}\n\n/**\n * The session surface the MCP server depends on. Implemented by\n * {@link BrowserSession} in production and faked in unit tests, so the server\n * wiring can be exercised without launching a browser.\n */\nexport interface A11ySession {\n open(\n url: string,\n options?: OpenOptions,\n ): Promise<{ title: string; url: string }>;\n call<T>(fn: string, rootSelector: string, args?: unknown[]): Promise<T>;\n snapshot(\n rootSelector: string,\n options?: SnapshotOptions,\n ): Promise<PageSnapshot>;\n /** The browser's own (native) accessibility tree via CDP — Chromium only. */\n nativeAX(): Promise<{ tree: string; pairs: string[] }>;\n close(): Promise<void>;\n}\n\nexport class BrowserSession implements A11ySession {\n private browser?: Browser;\n private context?: BrowserContext;\n private page?: Page;\n /** Signature of the current context's emulation, to detect device changes. */\n private emulationKey = \"\";\n /** Over CDP: whether we created `page` (so `close()` only closes our tab). */\n private ownsPage = false;\n /** Serializes every operation so concurrent tool calls can't race the page. */\n private queue: Promise<unknown> = Promise.resolve();\n\n constructor(private readonly opts: BrowserSessionOptions = {}) {\n if (opts.storageState && opts.cdpEndpoint) {\n throw new Error(\n \"storageState is for launched browsers — a CDP connection reuses the running browser's own session, so the two can't be combined.\",\n );\n }\n }\n\n /** Origins allowed for extraction, or null when unrestricted. */\n private get allowedOrigins(): Set<string> | null {\n const list = this.opts.allowedOrigins;\n return list && list.length ? new Set(list) : null;\n }\n\n /**\n * Run `fn` after any in-flight operation. MCP clients dispatch tool calls\n * concurrently, but all tools share one mutable `page`, so we single-flight.\n */\n private run<T>(fn: () => Promise<T>): Promise<T> {\n const result = this.queue.then(fn, fn);\n // Keep the chain alive even if this op rejects.\n this.queue = result.then(\n () => undefined,\n () => undefined,\n );\n return result;\n }\n\n /** Navigate to `url`, settle, then confirm the bundle initialized. */\n async open(\n url: string,\n options: OpenOptions = {},\n ): Promise<{ title: string; url: string }> {\n return this.run(async () => {\n assertOpenableUrl(url);\n const {\n waitUntil = \"load\",\n settleMs = 0,\n device,\n viewport,\n timeoutMs,\n } = options;\n const page = await this.ensurePage({ device, viewport });\n try {\n await page.goto(url, {\n waitUntil,\n ...(timeoutMs != null ? { timeout: timeoutMs } : {}),\n });\n } catch (err) {\n // Only tolerate a timeout for `networkidle` — chatty sites (analytics,\n // sockets) never fully idle. For other wait states a timeout means the\n // navigation didn't happen, so surface it.\n const isTimeout = err instanceof Error && err.name === \"TimeoutError\";\n if (!(isTimeout && waitUntil === \"networkidle\")) throw err;\n }\n if (settleMs > 0) await page.waitForTimeout(settleMs);\n // A tolerated timeout can leave us on about:blank (nav never committed) —\n // don't silently audit an empty tab as if it were the requested URL.\n if (page.url() === \"about:blank\") {\n throw new Error(\n `Navigation to ${url} did not complete (still on about:blank). Try waitUntil:\"load\" or a larger timeoutMs.`,\n );\n }\n // Origin pinning: with a session loaded, a redirect to a recorded cookie\n // domain would render an authenticated page we never intended to audit.\n // Enforce the allowlist on the FINAL url, before injecting or extracting.\n this.assertAllowedOrigin(page.url());\n await this.injectBundle(page);\n await this.verifyReady(page);\n return { title: await page.title(), url: page.url() };\n });\n }\n\n /**\n * Run a named export from the injected bundle against a root selector.\n * Re-injects first if a navigation wiped the global.\n */\n async call<T>(\n fn: string,\n rootSelector: string,\n args: unknown[] = [],\n ): Promise<T> {\n return this.run(async () => {\n const page = this.requirePage();\n await this.ensureInjected(page);\n return page.evaluate(\n ({ fn, selector, args }) => {\n const ra = (globalThis as Record<string, unknown>).__realA11y__ as\n | Record<string, (root: Element, ...rest: unknown[]) => unknown>\n | undefined;\n if (!ra) throw new Error(\"__realA11y__ is not present on the page.\");\n if (typeof ra[fn] !== \"function\") {\n throw new Error(\n `Real A11y bundle has no \"${fn}\" — the installed @real-a11y-dev/testing is too old for this MCP server; upgrade it.`,\n );\n }\n let root: Element | null;\n try {\n root = document.querySelector(selector);\n } catch {\n throw new Error(`Invalid rootSelector: \"${selector}\".`);\n }\n if (!root) {\n if (selector === \"body\" && document.body) root = document.body;\n else {\n throw new Error(\n `rootSelector \"${selector}\" matched no element on the page.`,\n );\n }\n }\n return ra[fn](root, ...args) as unknown;\n },\n { fn, selector: rootSelector, args },\n ) as Promise<T>;\n });\n }\n\n /**\n * Extract the a11y tree **once** and derive all four views from it inside a\n * single `page.evaluate`, so findings / tree / outline / tab order always\n * describe the same instant — no cross-call drift on a moving page.\n */\n async snapshot(\n rootSelector: string,\n options: SnapshotOptions = {},\n ): Promise<PageSnapshot> {\n return this.run(async () => {\n const page = this.requirePage();\n await this.ensureInjected(page);\n return page.evaluate(\n ({ selector, rules, includeGeneric }) => {\n const ra = (globalThis as Record<string, unknown>).__realA11y__ as\n | Record<string, (...a: unknown[]) => unknown>\n | undefined;\n if (!ra || typeof ra.extractA11yTree !== \"function\") {\n throw new Error(\n \"Real A11y bundle missing/too old — upgrade @real-a11y-dev/testing.\",\n );\n }\n let el: Element | null;\n try {\n el = document.querySelector(selector);\n } catch {\n throw new Error(`Invalid rootSelector: \"${selector}\".`);\n }\n if (!el) {\n if (selector === \"body\" && document.body) el = document.body;\n else {\n throw new Error(\n `rootSelector \"${selector}\" matched no element on the page.`,\n );\n }\n }\n const tree = ra.extractA11yTree(el); // ← the single extraction\n return {\n findings: ra.collectFindings(\n tree,\n rules && rules.length ? rules : undefined,\n ),\n tree: ra.auditSnapshot(tree, { includeGeneric }),\n outline: ra.outlineSnapshot(tree),\n tabOrder: ra.tabSequenceSnapshot(tree),\n };\n },\n {\n selector: rootSelector,\n rules: options.rules ?? null,\n includeGeneric: options.includeGeneric ?? false,\n },\n ) as Promise<PageSnapshot>;\n });\n }\n\n /**\n * Chromium's own accessibility tree, straight from Blink via the CDP\n * `Accessibility` domain — the authoritative computation, not our\n * reimplementation. Used to cross-check custom-engine fidelity.\n */\n async nativeAX(): Promise<{ tree: string; pairs: string[] }> {\n return this.run(async () => {\n const page = this.requirePage();\n const client = await page.context().newCDPSession(page);\n try {\n await client.send(\"Accessibility.enable\");\n const { nodes } = (await client.send(\n \"Accessibility.getFullAXTree\",\n )) as { nodes: AXNode[] };\n return serializeNativeAX(nodes);\n } finally {\n await client.detach().catch(() => {});\n }\n });\n }\n\n /**\n * Capture the current context's storage state (cookies + origin storage) —\n * the \"save\" step behind the CLI's `login` helper. Returns the plain object;\n * the caller owns serialization + file writing (permissions, atomicity).\n * `indexedDB` is caller-gated: it's a no-op on Playwright < 1.51, so the CLI\n * passes it only when the resolved version supports it.\n */\n async captureStorageState(\n options: { indexedDB?: boolean } = {},\n ): Promise<unknown> {\n return this.run(async () => {\n const context = this.context;\n if (!context) {\n throw new Error(\"No browser context is open — call open() first.\");\n }\n return context.storageState(options.indexedDB ? { indexedDB: true } : {});\n });\n }\n\n async close(): Promise<void> {\n return this.run(async () => {\n if (this.opts.cdpEndpoint) {\n // Attached to the user's own browser: close only the tab we created and\n // disconnect — never close their Chrome or their other tabs.\n if (this.ownsPage) await this.page?.close().catch(() => {});\n await this.browser?.close().catch(() => {});\n } else {\n await this.context?.close().catch(() => {});\n await this.browser?.close().catch(() => {});\n }\n this.browser = undefined;\n this.context = undefined;\n this.page = undefined;\n this.ownsPage = false;\n this.emulationKey = \"\";\n });\n }\n\n hasPage(): boolean {\n return this.page !== undefined;\n }\n\n // ── internals ────────────────────────────────────────────────────────────\n\n private requirePage(): Page {\n if (!this.page) {\n throw new Error(\"No page is open. Call the open_page tool first.\");\n }\n return this.page;\n }\n\n /** Refuse extraction when the final URL's origin isn't allowlisted. */\n private assertAllowedOrigin(finalUrl: string): void {\n const allowed = this.allowedOrigins;\n if (!allowed) return;\n let origin: string;\n try {\n origin = new URL(finalUrl).origin;\n } catch {\n throw new Error(`Refusing to audit an unparseable URL: ${finalUrl}`);\n }\n if (!allowed.has(origin)) {\n throw new Error(\n `Refusing to audit ${origin}: not an allowed audit origin under an authenticated session (a redirect may have left the intended site).`,\n );\n }\n }\n\n private async ensurePage(\n emu: { device?: string; viewport?: { width: number; height: number } } = {},\n ): Promise<Page> {\n // playwright is a peer dep; import lazily so importing the server API\n // (types, buildServer) never requires playwright to be installed.\n const { chromium, devices } = await import(\"playwright\");\n\n // CDP: attach to the running browser's context. Device emulation can't be\n // applied to an already-open browser, so reject it explicitly.\n if (this.opts.cdpEndpoint) {\n if (emu.device || emu.viewport) {\n throw new Error(\n \"Device / viewport emulation is not supported over a CDP connection — it reuses the running browser's own context.\",\n );\n }\n if (this.page && !this.page.isClosed()) return this.page;\n if (!this.browser || !this.browser.isConnected()) {\n this.browser = await chromium.connectOverCDP(this.opts.cdpEndpoint);\n }\n this.context =\n this.browser.contexts()[0] ?? (await this.browser.newContext());\n // Create our OWN tab rather than hijacking the user's oldest one.\n this.page = await this.context.newPage();\n this.ownsPage = true;\n return this.page;\n }\n\n // Reuse the current page only when it's live and the emulation is unchanged.\n const key = JSON.stringify({\n device: emu.device ?? null,\n viewport: emu.viewport ?? null,\n });\n if (this.page && !this.page.isClosed() && key === this.emulationKey) {\n return this.page;\n }\n\n if (!this.browser || !this.browser.isConnected()) {\n this.browser = await chromium.launch({\n headless: this.opts.headless ?? true,\n ...(this.opts.proxy ? { proxy: this.opts.proxy } : {}),\n });\n }\n\n // Resolve the device descriptor BEFORE tearing down the old context, so a\n // mistyped device name doesn't destroy the current session.\n let ctxOpts: BrowserContextOptions = {};\n if (emu.device) {\n const descriptor = devices[emu.device];\n if (!descriptor) {\n throw new Error(\n `Unknown device \"${emu.device}\". Use a Playwright device name such as \"iPhone 13\", \"Pixel 7\", or \"iPad Pro 11\".`,\n );\n }\n ctxOpts = descriptor;\n }\n if (emu.viewport) ctxOpts = { ...ctxOpts, viewport: emu.viewport };\n // Load the session into every launched context — including this one when a\n // device change rebuilds it, so auth is never silently dropped by emulation.\n if (this.opts.storageState) {\n ctxOpts = { ...ctxOpts, storageState: this.opts.storageState };\n }\n\n // Emulation changed (or first open) → rebuild the context with it.\n if (this.context) {\n await this.context.close().catch(() => {});\n this.context = undefined;\n this.page = undefined;\n }\n this.context = await this.browser.newContext(ctxOpts);\n this.page = await this.context.newPage();\n this.emulationKey = key;\n return this.page;\n }\n\n /** Evaluate the bundle in the page (CSP / Trusted-Types-safe via CDP). */\n private async injectBundle(page: Page): Promise<void> {\n await page.evaluate(bundleExpression());\n }\n\n /** (Re)inject the bundle if a navigation wiped the global. */\n private async ensureInjected(page: Page): Promise<void> {\n if (!(await this.isReady(page))) await this.injectBundle(page);\n }\n\n /** Confirm the bundle initialized; throw a clear error if not. */\n private async verifyReady(page: Page): Promise<void> {\n if (!(await this.isReady(page))) {\n throw new Error(\n \"Real A11y extraction bundle did not initialize on this page — a strict Content-Security-Policy may be blocking script evaluation here.\",\n );\n }\n }\n\n private isReady(page: Page): Promise<boolean> {\n return page.evaluate(\n () =>\n typeof (globalThis as Record<string, unknown>).__realA11y__ ===\n \"object\",\n );\n }\n}\n"]}
|