looking-glass-mcp 3.1.0 → 3.1.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 +1 -1
- package/build/index.js +111 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Looking Glass v3.0 transforms from a browser automation tool into an **AI-native
|
|
|
44
44
|
- **Intent-based navigation** -- `browser_go "settings page"` figures out how to get there
|
|
45
45
|
- **Semantic waiting** -- `browser_wait_for "search results loaded"` instead of guessing CSS selectors
|
|
46
46
|
- **Workflow tracking** -- every response includes page type, form progress, breadcrumbs, modal state, and step indicators
|
|
47
|
-
- **Credential vault** -- AES-256-GCM encrypted credentials with PBKDF2 key derivation and blind injection (the agent never sees passwords)
|
|
47
|
+
- **Credential vault** -- AES-256-GCM encrypted credentials with PBKDF2 key derivation and blind injection (the agent never sees passwords). Smart field matching works on modern React forms with no `name` attributes
|
|
48
48
|
- **HTTP transport** -- deploy as a cloud service with `StreamableHTTPServerTransport`
|
|
49
49
|
- **Docker + Terraform** -- one-command Azure deployment with Key Vault, managed identity, and hardened security
|
|
50
50
|
- **Enterprise security** -- timing-safe auth, rate limiting, auth lockout, deep audit logging, non-root containers
|
package/build/index.js
CHANGED
|
@@ -29010,6 +29010,117 @@ async function injectCredentials(profile) {
|
|
|
29010
29010
|
await label.fill(value, { timeout: 3e3 });
|
|
29011
29011
|
injected++;
|
|
29012
29012
|
matched.push(name);
|
|
29013
|
+
filled = true;
|
|
29014
|
+
}
|
|
29015
|
+
} catch {
|
|
29016
|
+
}
|
|
29017
|
+
}
|
|
29018
|
+
if (!filled) {
|
|
29019
|
+
const keywords = nameLower.split(/[\s_-]+/).filter((w) => w.length > 2);
|
|
29020
|
+
if (keywords.length === 0) keywords.push(nameLower);
|
|
29021
|
+
for (const kw of keywords) {
|
|
29022
|
+
if (filled) break;
|
|
29023
|
+
try {
|
|
29024
|
+
const ariaLocator = page.locator(`input[aria-label*="${kw}" i], textarea[aria-label*="${kw}" i]`).first();
|
|
29025
|
+
if (await ariaLocator.isVisible({ timeout: 1e3 })) {
|
|
29026
|
+
await ariaLocator.fill(value, { timeout: 3e3 });
|
|
29027
|
+
injected++;
|
|
29028
|
+
matched.push(name);
|
|
29029
|
+
filled = true;
|
|
29030
|
+
}
|
|
29031
|
+
} catch {
|
|
29032
|
+
}
|
|
29033
|
+
}
|
|
29034
|
+
if (!filled) {
|
|
29035
|
+
for (const kw of keywords) {
|
|
29036
|
+
if (filled) break;
|
|
29037
|
+
try {
|
|
29038
|
+
const phLocator = page.getByPlaceholder(kw, { exact: false }).first();
|
|
29039
|
+
if (await phLocator.isVisible({ timeout: 1e3 })) {
|
|
29040
|
+
await phLocator.fill(value, { timeout: 3e3 });
|
|
29041
|
+
injected++;
|
|
29042
|
+
matched.push(name);
|
|
29043
|
+
filled = true;
|
|
29044
|
+
}
|
|
29045
|
+
} catch {
|
|
29046
|
+
}
|
|
29047
|
+
}
|
|
29048
|
+
}
|
|
29049
|
+
}
|
|
29050
|
+
if (!filled) {
|
|
29051
|
+
try {
|
|
29052
|
+
const keywords = nameLower.split(/[\s_-]+/).filter((w) => w.length > 2);
|
|
29053
|
+
if (keywords.length === 0) keywords.push(nameLower);
|
|
29054
|
+
const selector = await page.evaluate((kws) => {
|
|
29055
|
+
const labels = document.querySelectorAll("label");
|
|
29056
|
+
for (const label of labels) {
|
|
29057
|
+
const labelText = label.textContent?.trim().toLowerCase() || "";
|
|
29058
|
+
if (!kws.some((kw) => labelText.includes(kw))) continue;
|
|
29059
|
+
if (label.htmlFor) {
|
|
29060
|
+
const target = document.getElementById(label.htmlFor);
|
|
29061
|
+
if (target && ["INPUT", "TEXTAREA", "SELECT"].includes(target.tagName)) {
|
|
29062
|
+
return `#${CSS.escape(label.htmlFor)}`;
|
|
29063
|
+
}
|
|
29064
|
+
}
|
|
29065
|
+
const wrapped = label.querySelector("input, textarea, select");
|
|
29066
|
+
if (wrapped) {
|
|
29067
|
+
const all = [...document.querySelectorAll(wrapped.tagName.toLowerCase())];
|
|
29068
|
+
const idx = all.indexOf(wrapped);
|
|
29069
|
+
if (idx >= 0) return `${wrapped.tagName.toLowerCase()}:nth-of-type(${idx + 1})`;
|
|
29070
|
+
}
|
|
29071
|
+
}
|
|
29072
|
+
for (const input of document.querySelectorAll("input, textarea, select")) {
|
|
29073
|
+
const labelledBy = input.getAttribute("aria-labelledby");
|
|
29074
|
+
if (!labelledBy) continue;
|
|
29075
|
+
const labelEl = document.getElementById(labelledBy);
|
|
29076
|
+
const labelText = labelEl?.textContent?.trim().toLowerCase() || "";
|
|
29077
|
+
if (kws.some((kw) => labelText.includes(kw))) {
|
|
29078
|
+
return `[aria-labelledby="${CSS.escape(labelledBy)}"]`;
|
|
29079
|
+
}
|
|
29080
|
+
}
|
|
29081
|
+
return null;
|
|
29082
|
+
}, keywords);
|
|
29083
|
+
if (selector) {
|
|
29084
|
+
const locator = page.locator(selector).first();
|
|
29085
|
+
if (await locator.isVisible({ timeout: 1e3 })) {
|
|
29086
|
+
await locator.fill(value, { timeout: 3e3 });
|
|
29087
|
+
injected++;
|
|
29088
|
+
matched.push(name);
|
|
29089
|
+
filled = true;
|
|
29090
|
+
}
|
|
29091
|
+
}
|
|
29092
|
+
} catch {
|
|
29093
|
+
}
|
|
29094
|
+
}
|
|
29095
|
+
if (!filled && /email|username|user|login|account/i.test(nameLower)) {
|
|
29096
|
+
try {
|
|
29097
|
+
const hasPasswordField = await page.locator('input[type="password"]').count() > 0;
|
|
29098
|
+
if (hasPasswordField) {
|
|
29099
|
+
const candidates = page.locator('input[type="text"]:visible, input[type="email"]:visible');
|
|
29100
|
+
const count = await candidates.count();
|
|
29101
|
+
for (let i = 0; i < count && !filled; i++) {
|
|
29102
|
+
const candidate = candidates.nth(i);
|
|
29103
|
+
try {
|
|
29104
|
+
const isBeforePassword = await page.evaluate((idx) => {
|
|
29105
|
+
const inputs = [...document.querySelectorAll("input")];
|
|
29106
|
+
const visible = inputs.filter((inp) => {
|
|
29107
|
+
const t = inp.type?.toLowerCase() || "text";
|
|
29108
|
+
return (t === "text" || t === "email") && inp.offsetParent !== null;
|
|
29109
|
+
});
|
|
29110
|
+
const pw = inputs.find((inp) => inp.type === "password");
|
|
29111
|
+
if (!visible[idx] || !pw) return false;
|
|
29112
|
+
return inputs.indexOf(visible[idx]) < inputs.indexOf(pw);
|
|
29113
|
+
}, i);
|
|
29114
|
+
if (isBeforePassword) {
|
|
29115
|
+
await candidate.fill(value, { timeout: 3e3 });
|
|
29116
|
+
injected++;
|
|
29117
|
+
matched.push(name);
|
|
29118
|
+
filled = true;
|
|
29119
|
+
}
|
|
29120
|
+
} catch {
|
|
29121
|
+
continue;
|
|
29122
|
+
}
|
|
29123
|
+
}
|
|
29013
29124
|
}
|
|
29014
29125
|
} catch {
|
|
29015
29126
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "looking-glass-mcp",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"mcpName": "io.github.Sahib-Sawhney-WH/looking-glass-mcp",
|
|
5
5
|
"description": "AI-native browser for agents — semantic change detection, self-healing interactions, structured extraction, credential vault, and enterprise Azure deployment",
|
|
6
6
|
"main": "build/index.js",
|