looking-glass-mcp 3.0.0 → 3.1.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/README.md CHANGED
@@ -6,7 +6,21 @@
6
6
 
7
7
  <p align="center">
8
8
  <strong>The AI-native browser for agents.</strong><br/>
9
- 72 MCP tools. Self-healing interactions. Semantic change detection. Structured extraction. Credential vault. Enterprise-grade security. Deploy anywhere.
9
+ 71 MCP tools. Self-healing interactions. Semantic change detection. Structured extraction. Credential vault. Enterprise-grade security. Deploy anywhere.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://www.npmjs.com/package/looking-glass-mcp"><img src="https://img.shields.io/npm/v/looking-glass-mcp?color=blue&label=npm" alt="npm" /></a>
14
+ <a href="https://www.npmjs.com/package/looking-glass-mcp"><img src="https://img.shields.io/npm/dm/looking-glass-mcp?color=orange&label=downloads" alt="downloads" /></a>
15
+ <a href="https://registry.modelcontextprotocol.io/servers/io.github.Sahib-Sawhney-WH/looking-glass-mcp"><img src="https://img.shields.io/badge/MCP_Registry-listed-brightgreen" alt="MCP Registry" /></a>
16
+ <a href="https://github.com/Sahib-Sawhney-WH/LookingGlass/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/Sahib-Sawhney-WH/LookingGlass/ci.yml?label=CI" alt="CI" /></a>
17
+ <a href="https://github.com/Sahib-Sawhney-WH/LookingGlass/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Sahib-Sawhney-WH/LookingGlass?color=purple" alt="MIT License" /></a>
18
+ <a href="https://github.com/Sahib-Sawhney-WH/LookingGlass"><img src="https://img.shields.io/github/stars/Sahib-Sawhney-WH/LookingGlass?style=social" alt="GitHub Stars" /></a>
19
+ <img src="https://img.shields.io/badge/tools-71-ff69b4" alt="71 tools" />
20
+ <img src="https://img.shields.io/badge/tests-79-success" alt="79 tests" />
21
+ <img src="https://img.shields.io/badge/node-%3E%3D20-339933?logo=node.js&logoColor=white" alt="Node 20+" />
22
+ <img src="https://img.shields.io/badge/Playwright-1.50+-2EAD33?logo=playwright&logoColor=white" alt="Playwright" />
23
+ <img src="https://img.shields.io/badge/Azure-ready-0078D4?logo=microsoftazure&logoColor=white" alt="Azure Ready" />
10
24
  </p>
11
25
 
12
26
  <p align="center">
@@ -18,10 +32,6 @@
18
32
  <a href="#configuration">Configuration</a>
19
33
  </p>
20
34
 
21
- <p align="center">
22
- <a href="https://portal.azure.com/#create/Microsoft.Template"><img src="https://aka.ms/deploytoazurebutton" alt="Deploy to Azure" /></a>
23
- </p>
24
-
25
35
  ---
26
36
 
27
37
  ## What's New in v3.0
@@ -46,9 +56,22 @@ Looking Glass v3.0 transforms from a browser automation tool into an **AI-native
46
56
  **Claude Code:**
47
57
 
48
58
  ```bash
59
+ # Basic (headless, uses local-dev profile by default for stdio)
49
60
  claude mcp add looking-glass -- npx looking-glass-mcp
61
+
62
+ # With visible browser window
63
+ claude mcp add looking-glass -e BROWSER_HEADLESS=false -- npx looking-glass-mcp
64
+
65
+ # With all options
66
+ claude mcp add looking-glass \
67
+ -e BROWSER_HEADLESS=false \
68
+ -e BROWSER_SECURITY_PROFILE=local-dev \
69
+ -e VAULT_ENCRYPTION_KEY=your-passphrase-at-least-32-characters \
70
+ -- npx looking-glass-mcp
50
71
  ```
51
72
 
73
+ > **Tip:** After changing env vars, restart Claude Code (new conversation) for the changes to take effect.
74
+
52
75
  **GitHub Copilot / VS Code:**
53
76
 
54
77
  Add to `.vscode/mcp.json`:
@@ -189,16 +212,17 @@ Looking Glass ships with **72 tools** across 10 categories.
189
212
  | `browser_wait_for` | **NEW** Semantic condition waiting ("results loaded") |
190
213
  | `browser_workflow` | **NEW** On-demand workflow context |
191
214
 
192
- ### Credential Vault (5 tools)
215
+ ### Credential Vault (4 tools)
193
216
 
194
217
  | Tool | Description |
195
218
  |------|-------------|
196
- | `vault_store` | Store encrypted credential profile (AES-256-GCM + PBKDF2) |
197
219
  | `vault_list` | List profile names and timestamps (no values) |
198
220
  | `vault_delete` | Delete a credential profile |
199
221
  | `vault_login` | Login using a vault profile (blind injection) |
200
222
  | `vault_inject` | Fill form fields from vault without submitting |
201
223
 
224
+ > Credentials are stored via the CLI (`looking-glass vault store`), not through the agent. See [Credential Vault](#credential-vault) below.
225
+
202
226
  ### Observation & Debugging (10 tools)
203
227
 
204
228
  `browser_screenshot`, `browser_snapshot`, `browser_evaluate`, `browser_console_messages`, `browser_network_requests`, `browser_diagnose`, `browser_error_report`, `browser_snapshot_state` / `browser_diff_state`, `browser_action_history`
@@ -291,12 +315,20 @@ See [SECURITY.md](SECURITY.md) for the full security model.
291
315
 
292
316
  ### Security Profiles
293
317
 
294
- | Profile | URL Access | JS Execution | Tool Access | Rate Limit |
295
- |---------|-----------|--------------|-------------|------------|
296
- | `restricted` (default) | localhost only | Blocked | Observation only | 30/min |
297
- | `local-dev` | All HTTP/HTTPS | Allowed | All tools | 60/min |
298
- | `open` | Everything | Allowed | All tools | 120/min |
299
- | `sandbox` | Blocked | Blocked | Observation only | 10/min |
318
+ The default profile depends on transport mode:
319
+ - **stdio** (local agent): defaults to `local-dev` (full access)
320
+ - **http** (cloud deployment): defaults to `restricted` (observation only)
321
+
322
+ Override with `BROWSER_SECURITY_PROFILE` if needed.
323
+
324
+ | Profile | URL Access | JS Execution | Tool Access | Rate Limit | Use Case |
325
+ |---------|-----------|--------------|-------------|------------|----------|
326
+ | `local-dev` | All HTTP/HTTPS | Allowed | All tools | 60/min | Local development and testing |
327
+ | `restricted` | localhost only | Blocked | Observation only | 30/min | Production monitoring — agent can screenshot, audit, and inspect but cannot click, type, or navigate |
328
+ | `open` | Everything | Allowed | All tools | 120/min | Trusted cloud environments |
329
+ | `sandbox` | Blocked | Blocked | Observation only | 10/min | Maximum lockdown — blocks all URLs by default |
330
+
331
+ > **Note:** If you're using Looking Glass locally via stdio and the agent can't interact with pages, check that your profile is set to `local-dev`. The `restricted` profile is designed for read-only monitoring where the agent should never modify page state.
300
332
 
301
333
  ### HTTP Transport Security
302
334
 
@@ -307,10 +339,56 @@ See [SECURITY.md](SECURITY.md) for the full security model.
307
339
 
308
340
  ### Credential Vault
309
341
 
342
+ Credentials are stored via the CLI — the agent never sees your passwords.
343
+
344
+ **Setup:**
345
+
346
+ 1. Set your vault encryption key (add to your shell profile or MCP config):
347
+
348
+ ```bash
349
+ export VAULT_ENCRYPTION_KEY="your-passphrase-at-least-32-characters-long"
350
+ ```
351
+
352
+ 2. Store a credential profile (interactive — passwords are masked):
353
+
354
+ ```bash
355
+ npx looking-glass-mcp vault store linkedin
356
+ ```
357
+
358
+ You'll be prompted for field name/value pairs:
359
+
360
+ ```
361
+ Storing credential profile: linkedin
362
+ Field name (empty to finish): email
363
+ Value for "email": user@example.com
364
+ Field name (empty to finish): password
365
+ Value for "password": ********
366
+ Field name (empty to finish):
367
+ ✓ Stored profile "linkedin" with 2 fields
368
+ ```
369
+
370
+ 3. Use the profile in your agent conversation:
371
+
372
+ ```
373
+ "Log in to LinkedIn with vault profile linkedin"
374
+ ```
375
+
376
+ The agent calls `vault_login` which decrypts and injects credentials directly into the page — the agent never sees the plaintext values.
377
+
378
+ **CLI commands:**
379
+
380
+ | Command | Description |
381
+ |---------|-------------|
382
+ | `npx looking-glass-mcp vault store <profile>` | Store credentials interactively (masked input) |
383
+ | `npx looking-glass-mcp vault list` | List stored profiles and timestamps |
384
+ | `npx looking-glass-mcp vault delete <profile>` | Delete a stored profile |
385
+
386
+ **Security properties:**
387
+
310
388
  - AES-256-GCM encryption with PBKDF2 key derivation (100k iterations, SHA-512)
311
389
  - Field names and values encrypted together (no metadata leakage)
312
390
  - Vault file permissions restricted to owner (`0600`)
313
- - Agent never sees plaintext credentials -- blind injection only
391
+ - Credential storage happens exclusively through the CLI never through the agent channel
314
392
  - `createdAt` and `lastUsedAt` tracking per profile
315
393
 
316
394
  ### Audit Trail
@@ -426,12 +504,12 @@ MCP Client (Claude, Copilot, etc.)
426
504
  | - Audit Logging (deep redaction) |
427
505
  | - RBAC Policy Enforcement |
428
506
  | |
429
- | 72 Tools |
507
+ | 71 Tools |
430
508
  | - Intelligence (extract, go, wait_for) |
431
509
  | - Interaction (click, type, hover) |
432
510
  | - Observation (screenshot, snapshot) |
433
511
  | - Testing (scenarios, assertions) |
434
- | - Vault (store, inject, login) |
512
+ | - Vault (inject, login, list, delete) |
435
513
  | |
436
514
  | Browser Manager |
437
515
  | - Playwright (Chromium/Firefox/WebKit) |
@@ -1,2 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import '../build/index.js';
2
+
3
+ // Route "vault" subcommand to the vault CLI
4
+ if (process.argv[2] === 'vault') {
5
+ await import('../build/vault-cli.js');
6
+ } else {
7
+ await import('../build/index.js');
8
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../build/vault-cli.js';
package/build/index.js CHANGED
@@ -22641,7 +22641,9 @@ var SECURITY_PROFILES = {
22641
22641
  }
22642
22642
  };
22643
22643
  function getConfig() {
22644
- const profile = process.env.BROWSER_SECURITY_PROFILE || "restricted";
22644
+ const transport = process.env.MCP_TRANSPORT || "stdio";
22645
+ const defaultProfile = transport === "http" ? "restricted" : "local-dev";
22646
+ const profile = process.env.BROWSER_SECURITY_PROFILE || defaultProfile;
22645
22647
  return {
22646
22648
  headless: process.env.BROWSER_HEADLESS === "true",
22647
22649
  sessionsDir: resolve(process.env.BROWSER_SESSIONS_DIR || ".browser-sessions"),
@@ -23891,6 +23893,7 @@ function formatJournal(entries) {
23891
23893
  // src/middleware/toolWrapper.ts
23892
23894
  init_manager();
23893
23895
  var _middlewares = [];
23896
+ var _hasPreSnapshot = false;
23894
23897
  var _captureSnapshot = null;
23895
23898
  var _detectChanges = null;
23896
23899
  var _captureWorkflow = null;
@@ -23930,7 +23933,7 @@ function wrapToolRegistration(server) {
23930
23933
  const errorCountBefore = getConsoleMessages().filter((m) => m.type === "error").length;
23931
23934
  const failureCountBefore = getNetworkRequests().filter((r) => r.status && r.status >= 400).length;
23932
23935
  let preSnapshot = null;
23933
- if (isInteraction && _captureSnapshot) {
23936
+ if (isInteraction && _captureSnapshot && _hasPreSnapshot) {
23934
23937
  try {
23935
23938
  preSnapshot = await _captureSnapshot();
23936
23939
  } catch {
@@ -23986,6 +23989,7 @@ function wrapToolRegistration(server) {
23986
23989
  workflow = await _captureWorkflow();
23987
23990
  }
23988
23991
  setPendingIntelligence({ changes, workflow });
23992
+ _hasPreSnapshot = true;
23989
23993
  } catch {
23990
23994
  }
23991
23995
  }
@@ -27404,7 +27408,6 @@ var INTELLIGENCE_TOOLS = /* @__PURE__ */ new Set([
27404
27408
  "browser_wait_for"
27405
27409
  ]);
27406
27410
  var VAULT_WRITE_TOOLS = /* @__PURE__ */ new Set([
27407
- "vault_store",
27408
27411
  "vault_delete",
27409
27412
  "vault_login",
27410
27413
  "vault_inject"
@@ -28927,15 +28930,6 @@ async function saveVault(vault) {
28927
28930
  } catch {
28928
28931
  }
28929
28932
  }
28930
- function encrypt(data) {
28931
- const key = requireKey();
28932
- const iv = randomBytes(12);
28933
- const cipher = createCipheriv("aes-256-gcm", key, iv);
28934
- let encrypted = cipher.update(data, "utf-8", "hex");
28935
- encrypted += cipher.final("hex");
28936
- const tag = cipher.getAuthTag().toString("hex");
28937
- return { iv: iv.toString("hex"), encrypted, tag };
28938
- }
28939
28933
  function decrypt(iv, encrypted, tag) {
28940
28934
  const key = requireKey();
28941
28935
  const decipher = createDecipheriv("aes-256-gcm", key, Buffer.from(iv, "hex"));
@@ -28944,18 +28938,6 @@ function decrypt(iv, encrypted, tag) {
28944
28938
  decrypted += decipher.final("utf-8");
28945
28939
  return decrypted;
28946
28940
  }
28947
- async function storeCredential(profile, fields) {
28948
- const vault = await loadVault();
28949
- const payload = JSON.stringify({ fieldNames: Object.keys(fields), fields });
28950
- const { iv, encrypted, tag } = encrypt(payload);
28951
- vault.profiles[profile] = {
28952
- iv,
28953
- encrypted,
28954
- tag,
28955
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
28956
- };
28957
- await saveVault(vault);
28958
- }
28959
28941
  async function listProfiles() {
28960
28942
  const vault = await loadVault();
28961
28943
  return Object.entries(vault.profiles).map(([name, p]) => ({
@@ -29208,26 +29190,6 @@ ${JSON.stringify(ctx, null, 2)}` }
29208
29190
  }
29209
29191
  }
29210
29192
  );
29211
- server.tool(
29212
- "vault_store",
29213
- {
29214
- profile: external_exports.string().describe('Profile name (e.g. "staging-admin", "bank-login")'),
29215
- fields: external_exports.record(external_exports.string()).describe('Credential fields (e.g. { email: "user@example.com", password: "..." })')
29216
- },
29217
- async ({ profile, fields }) => {
29218
- if (!isVaultConfigured()) {
29219
- return { content: [{ type: "text", text: "Vault not configured. Set VAULT_ENCRYPTION_KEY env var." }], isError: true };
29220
- }
29221
- try {
29222
- await storeCredential(profile, fields);
29223
- return {
29224
- content: [{ type: "text", text: `Vault: stored profile "${profile}" with fields: ${Object.keys(fields).join(", ")}` }]
29225
- };
29226
- } catch (err) {
29227
- return { content: [{ type: "text", text: `Vault store failed: ${err.message}` }], isError: true };
29228
- }
29229
- }
29230
- );
29231
29193
  server.tool(
29232
29194
  "vault_list",
29233
29195
  {},
@@ -29316,7 +29278,7 @@ ${lines.join("\n")}` }] };
29316
29278
  }
29317
29279
 
29318
29280
  // src/index.ts
29319
- var VERSION = "3.0.0";
29281
+ var VERSION = "3.0.1";
29320
29282
  var _browserReady = false;
29321
29283
  function createServer(config2) {
29322
29284
  const server = new McpServer({
@@ -0,0 +1,261 @@
1
+ import { createRequire } from 'module';
2
+ const require = createRequire(import.meta.url);
3
+
4
+
5
+ // src/cli/vault.ts
6
+ import { createInterface } from "node:readline";
7
+ import { resolve } from "node:path";
8
+
9
+ // src/security/vault.ts
10
+ import { createCipheriv, createDecipheriv, randomBytes, pbkdf2Sync } from "node:crypto";
11
+ import { readFile, writeFile, mkdir, chmod } from "node:fs/promises";
12
+ import { dirname } from "node:path";
13
+
14
+ // src/browser/manager.ts
15
+ import { chromium, firefox, webkit } from "playwright-core";
16
+ var _readyResolve = null;
17
+ var _readyPromise = new Promise((r) => {
18
+ _readyResolve = r;
19
+ });
20
+
21
+ // src/security/vault.ts
22
+ var PBKDF2_ITERATIONS = 1e5;
23
+ var PBKDF2_KEY_LEN = 32;
24
+ var PBKDF2_DIGEST = "sha512";
25
+ var _vaultFile = "";
26
+ var _rawKey = null;
27
+ var _derivedKey = null;
28
+ var _vaultSalt = null;
29
+ function initVault(sessionsDir, encryptionKey) {
30
+ _vaultFile = `${sessionsDir}/vault.enc`;
31
+ if (encryptionKey && encryptionKey.length >= 32) {
32
+ _rawKey = encryptionKey;
33
+ }
34
+ }
35
+ function isVaultConfigured() {
36
+ return _rawKey !== null;
37
+ }
38
+ function requireKey() {
39
+ if (!_rawKey) {
40
+ throw new Error("Vault not configured. Set VAULT_ENCRYPTION_KEY environment variable (32+ chars).");
41
+ }
42
+ if (!_derivedKey) {
43
+ throw new Error("Vault key not derived. Call loadVault() first to initialize.");
44
+ }
45
+ return _derivedKey;
46
+ }
47
+ async function loadVault() {
48
+ try {
49
+ const data = await readFile(_vaultFile, "utf-8");
50
+ const vault = JSON.parse(data);
51
+ if (vault.salt && _rawKey) {
52
+ _vaultSalt = Buffer.from(vault.salt, "hex");
53
+ _derivedKey = pbkdf2Sync(_rawKey, _vaultSalt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST);
54
+ }
55
+ return vault;
56
+ } catch {
57
+ if (_rawKey) {
58
+ _vaultSalt = randomBytes(32);
59
+ _derivedKey = pbkdf2Sync(_rawKey, _vaultSalt, PBKDF2_ITERATIONS, PBKDF2_KEY_LEN, PBKDF2_DIGEST);
60
+ }
61
+ return { version: 2, salt: _vaultSalt?.toString("hex") || "", profiles: {} };
62
+ }
63
+ }
64
+ async function saveVault(vault) {
65
+ await mkdir(dirname(_vaultFile), { recursive: true });
66
+ await writeFile(_vaultFile, JSON.stringify(vault, null, 2), "utf-8");
67
+ try {
68
+ await chmod(_vaultFile, 384);
69
+ } catch {
70
+ }
71
+ }
72
+ function encrypt(data) {
73
+ const key = requireKey();
74
+ const iv = randomBytes(12);
75
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
76
+ let encrypted = cipher.update(data, "utf-8", "hex");
77
+ encrypted += cipher.final("hex");
78
+ const tag = cipher.getAuthTag().toString("hex");
79
+ return { iv: iv.toString("hex"), encrypted, tag };
80
+ }
81
+ async function storeCredential(profile, fields) {
82
+ const vault = await loadVault();
83
+ const payload = JSON.stringify({ fieldNames: Object.keys(fields), fields });
84
+ const { iv, encrypted, tag } = encrypt(payload);
85
+ vault.profiles[profile] = {
86
+ iv,
87
+ encrypted,
88
+ tag,
89
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
90
+ };
91
+ await saveVault(vault);
92
+ }
93
+ async function listProfiles() {
94
+ const vault = await loadVault();
95
+ return Object.entries(vault.profiles).map(([name, p]) => ({
96
+ name,
97
+ createdAt: p.createdAt,
98
+ lastUsedAt: p.lastUsedAt
99
+ }));
100
+ }
101
+ async function deleteProfile(profile) {
102
+ const vault = await loadVault();
103
+ delete vault.profiles[profile];
104
+ await saveVault(vault);
105
+ }
106
+
107
+ // src/cli/vault.ts
108
+ var USAGE = `
109
+ Usage: looking-glass vault <command> [options]
110
+
111
+ Commands:
112
+ store <profile> Store credentials interactively (passwords masked)
113
+ list List stored profiles and timestamps
114
+ delete <profile> Delete a stored profile
115
+
116
+ Environment:
117
+ VAULT_ENCRYPTION_KEY Passphrase for encryption (32+ chars, required)
118
+ BROWSER_SESSIONS_DIR Sessions directory (default: .browser-sessions)
119
+
120
+ Examples:
121
+ npx looking-glass-mcp vault store linkedin
122
+ npx looking-glass-mcp vault list
123
+ npx looking-glass-mcp vault delete linkedin
124
+ `.trim();
125
+ function die(msg) {
126
+ console.error(`Error: ${msg}`);
127
+ process.exit(1);
128
+ }
129
+ function initFromEnv() {
130
+ const sessionsDir = resolve(process.env.BROWSER_SESSIONS_DIR || ".browser-sessions");
131
+ const key = process.env.VAULT_ENCRYPTION_KEY;
132
+ initVault(sessionsDir, key);
133
+ if (!isVaultConfigured()) {
134
+ die("VAULT_ENCRYPTION_KEY is not set or is shorter than 32 characters.");
135
+ }
136
+ }
137
+ function prompt(question) {
138
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
139
+ return new Promise((resolve2) => {
140
+ rl.question(question, (answer) => {
141
+ rl.close();
142
+ resolve2(answer.trim());
143
+ });
144
+ });
145
+ }
146
+ function promptMasked(question) {
147
+ return new Promise((resolve2) => {
148
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
149
+ process.stdout.write(question);
150
+ const stdin = process.stdin;
151
+ const wasRaw = stdin.isRaw;
152
+ if (stdin.isTTY && stdin.setRawMode) {
153
+ stdin.setRawMode(true);
154
+ }
155
+ let value = "";
156
+ const onData = (ch) => {
157
+ const c = ch.toString("utf-8");
158
+ if (c === "\n" || c === "\r" || c === "") {
159
+ if (stdin.isTTY && stdin.setRawMode) {
160
+ stdin.setRawMode(wasRaw ?? false);
161
+ }
162
+ stdin.removeListener("data", onData);
163
+ rl.close();
164
+ process.stdout.write("\n");
165
+ resolve2(value);
166
+ } else if (c === "") {
167
+ process.stdout.write("\n");
168
+ process.exit(1);
169
+ } else if (c === "\x7F" || c === "\b") {
170
+ if (value.length > 0) {
171
+ value = value.slice(0, -1);
172
+ process.stdout.write("\b \b");
173
+ }
174
+ } else {
175
+ value += c;
176
+ process.stdout.write("*");
177
+ }
178
+ };
179
+ stdin.on("data", onData);
180
+ });
181
+ }
182
+ function isSensitiveField(name) {
183
+ const lower = name.toLowerCase();
184
+ return ["password", "passwd", "pwd", "pass", "secret", "token", "key", "pin", "otp"].some(
185
+ (p) => lower.includes(p)
186
+ );
187
+ }
188
+ async function cmdStore(profileName) {
189
+ const profile = profileName || await prompt("Profile name: ");
190
+ if (!profile) die("Profile name is required.");
191
+ console.log(`
192
+ Storing credential profile: ${profile}`);
193
+ console.log("Enter field name/value pairs. Leave field name empty to finish.\n");
194
+ const fields = {};
195
+ while (true) {
196
+ const fieldName = await prompt("Field name (empty to finish): ");
197
+ if (!fieldName) break;
198
+ const sensitive = isSensitiveField(fieldName);
199
+ const value = sensitive ? await promptMasked(`Value for "${fieldName}": `) : await prompt(`Value for "${fieldName}": `);
200
+ if (!value) {
201
+ console.log(` Skipping empty field "${fieldName}"`);
202
+ continue;
203
+ }
204
+ fields[fieldName] = value;
205
+ }
206
+ if (Object.keys(fields).length === 0) {
207
+ die("No fields provided. Nothing stored.");
208
+ }
209
+ await storeCredential(profile, fields);
210
+ console.log(`
211
+ \u2713 Stored profile "${profile}" with ${Object.keys(fields).length} field(s)`);
212
+ }
213
+ async function cmdList() {
214
+ const profiles = await listProfiles();
215
+ if (profiles.length === 0) {
216
+ console.log("No profiles stored.");
217
+ return;
218
+ }
219
+ console.log("Vault Profiles:\n");
220
+ for (const p of profiles) {
221
+ const lastUsed = p.lastUsedAt ? `, last used: ${p.lastUsedAt}` : "";
222
+ console.log(` \u2022 ${p.name} (created: ${p.createdAt}${lastUsed})`);
223
+ }
224
+ }
225
+ async function cmdDelete(profileName) {
226
+ const profile = profileName || await prompt("Profile name to delete: ");
227
+ if (!profile) die("Profile name is required.");
228
+ await deleteProfile(profile);
229
+ console.log(`\u2713 Deleted profile "${profile}"`);
230
+ }
231
+ async function main() {
232
+ const args = process.argv.slice(2);
233
+ if (args[0] === "vault") args.shift();
234
+ const command = args[0];
235
+ const target = args[1];
236
+ if (!command || command === "--help" || command === "-h") {
237
+ console.log(USAGE);
238
+ process.exit(0);
239
+ }
240
+ initFromEnv();
241
+ switch (command) {
242
+ case "store":
243
+ await cmdStore(target);
244
+ break;
245
+ case "list":
246
+ await cmdList();
247
+ break;
248
+ case "delete":
249
+ await cmdDelete(target);
250
+ break;
251
+ default:
252
+ console.error(`Unknown command: ${command}
253
+ `);
254
+ console.log(USAGE);
255
+ process.exit(1);
256
+ }
257
+ }
258
+ main().catch((err) => {
259
+ console.error(`Fatal: ${err.message}`);
260
+ process.exit(1);
261
+ });
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "looking-glass-mcp",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
+ "mcpName": "io.github.Sahib-Sawhney-WH/looking-glass-mcp",
4
5
  "description": "AI-native browser for agents — semantic change detection, self-healing interactions, structured extraction, credential vault, and enterprise Azure deployment",
5
6
  "main": "build/index.js",
6
7
  "type": "module",
7
8
  "bin": {
8
9
  "looking-glass-mcp": "bin/looking-glass-mcp.mjs",
9
- "looking-glass": "bin/looking-glass-mcp.mjs"
10
+ "looking-glass": "bin/looking-glass-mcp.mjs",
11
+ "looking-glass-vault": "bin/looking-glass-vault.mjs"
10
12
  },
11
13
  "scripts": {
12
14
  "build": "node esbuild.config.js",