@debugg-ai/debugg-ai-mcp 6.1.6 → 6.2.2

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.
@@ -0,0 +1,71 @@
1
+ export function isLoopbackHost(h) {
2
+ return ['localhost', '127.0.0.1', '[::1]', '::1'].includes(h.toLowerCase());
3
+ }
4
+ export function safeHost(u) {
5
+ try {
6
+ return new URL(u).hostname;
7
+ }
8
+ catch {
9
+ return u;
10
+ }
11
+ }
12
+ function hostAllowed(host, allowed) {
13
+ const h = host.toLowerCase();
14
+ return allowed.some((d) => {
15
+ const a = d.trim().toLowerCase().replace(/^\*?\./, '');
16
+ return a !== '' && (h === a || h.endsWith(`.${a}`));
17
+ });
18
+ }
19
+ /** Guard every current and future page of the context. `onBlocked` receives the refused host. */
20
+ export async function installNavigationGuard(context, allowed, onBlocked) {
21
+ const check = (u) => {
22
+ try {
23
+ const h = new URL(u).hostname;
24
+ return hostAllowed(h, allowed) || isLoopbackHost(h);
25
+ }
26
+ catch {
27
+ return false;
28
+ }
29
+ };
30
+ const guard = async (pg) => {
31
+ const cdp = await context.newCDPSession(pg);
32
+ const mainFrameId = (await cdp.send('Page.getFrameTree')).frameTree.frame.id;
33
+ await cdp.send('Fetch.enable', { patterns: [
34
+ { urlPattern: '*', resourceType: 'Document', requestStage: 'Request' },
35
+ { urlPattern: '*', resourceType: 'Document', requestStage: 'Response' },
36
+ ] });
37
+ cdp.on('Fetch.requestPaused', (ev) => {
38
+ void (async () => {
39
+ try {
40
+ if (ev.frameId !== mainFrameId) {
41
+ await cdp.send(ev.responseStatusCode ? 'Fetch.continueResponse' : 'Fetch.continueRequest', { requestId: ev.requestId });
42
+ return;
43
+ }
44
+ if (ev.responseStatusCode === undefined) {
45
+ if (!check(ev.request.url)) {
46
+ onBlocked(safeHost(ev.request.url));
47
+ await cdp.send('Fetch.failRequest', { requestId: ev.requestId, errorReason: 'BlockedByClient' });
48
+ return;
49
+ }
50
+ await cdp.send('Fetch.continueRequest', { requestId: ev.requestId });
51
+ return;
52
+ }
53
+ const loc = ev.responseHeaders?.find((h) => h.name.toLowerCase() === 'location')?.value;
54
+ if (ev.responseStatusCode >= 300 && ev.responseStatusCode < 400 && loc) {
55
+ const next = new URL(loc, ev.request.url).toString();
56
+ if (!check(next)) {
57
+ onBlocked(safeHost(next));
58
+ await cdp.send('Fetch.failRequest', { requestId: ev.requestId, errorReason: 'BlockedByClient' });
59
+ return;
60
+ }
61
+ }
62
+ await cdp.send('Fetch.continueResponse', { requestId: ev.requestId });
63
+ }
64
+ catch { /* page closed */ }
65
+ })();
66
+ });
67
+ };
68
+ for (const pg of context.pages())
69
+ await guard(pg);
70
+ context.on('page', (pg) => { void guard(pg).catch(() => { }); });
71
+ }
@@ -43,11 +43,16 @@ const noProgressTimeoutMs = () => {
43
43
  */
44
44
  export const EVAL_TEMPLATE_SLUG = 'flow/e2es/app-eval'; // check_app_in_browser
45
45
  export const PAGE_PROBE_TEMPLATE_SLUG = 'flow/tools/probe'; // probe_page
46
+ export const LOCAL_BROWSE_TEMPLATE_SLUG = 'flow/tools/browse'; // check_app_in_browser browser:'local' (sense/think/act brain)
46
47
  export const RAW_CRAWL_TEMPLATE_SLUG = 'crawl-execution-workflow-template'; // trigger_crawl
47
48
  /** The slug the eval-template dispatch pins to (env-overridable). */
48
49
  export function getEvalTemplateSlug() {
49
50
  return process.env.DEBUGGAI_EVAL_TEMPLATE || EVAL_TEMPLATE_SLUG;
50
51
  }
52
+ /** The slug a local-executor check pins to: the browser_use brain cannot drive a local executor (env-overridable). */
53
+ export function getLocalBrowseTemplateSlug() {
54
+ return process.env.DEBUGGAI_LOCAL_TEMPLATE || LOCAL_BROWSE_TEMPLATE_SLUG;
55
+ }
51
56
  /** The slug the page-probe dispatch pins to (env-overridable). */
52
57
  export function getPageProbeTemplateSlug() {
53
58
  return process.env.DEBUGGAI_PROBE_TEMPLATE || PAGE_PROBE_TEMPLATE_SLUG;
@@ -84,7 +84,7 @@ export function buildProbePageTool() {
84
84
  browser: {
85
85
  type: 'string',
86
86
  enum: ['remote', 'local'],
87
- description: "Default 'remote' (cloud browser; localhost is tunneled). 'local' runs Chrome on THIS machine against the URL directly: no tunnel, nothing uploaded, screenshot also saved under ~/.debugg-ai/captures. Works only when the MCP server runs on your machine AND its owner set DEBUGGAI_LOCAL_BROWSER=1. waitForSelector/waitForLoadState/timeoutMs apply; the local mode returns screenshot, console errors, network summary, title and (optionally) HTML.",
87
+ description: "Default 'remote' (cloud browser; localhost is tunneled). 'local' runs Chrome on THIS machine against the URL directly: no tunnel, nothing uploaded, screenshot also saved under ~/.debugg-ai/captures. Works only when the MCP server runs on your machine (stdio). waitForSelector/waitForLoadState/timeoutMs apply; the local mode returns screenshot, console errors, network summary, title and (optionally) HTML.",
88
88
  },
89
89
  headless: {
90
90
  type: 'boolean',
@@ -106,6 +106,14 @@ export function buildTestPageChangesTool(ctx) {
106
106
  type: "boolean",
107
107
  description: "Default false. Set true to force a REAL login instead of reusing the warm session the backend keeps per account. Use when the login flow itself is what you're checking, when you suspect the stored session is stale, or when the app's only route between personas is a logout. Costs one login; the run re-captures afterwards, so later runs stay fast."
108
108
  },
109
+ browser: {
110
+ type: "string",
111
+ enum: ["remote", "local"],
112
+ description: "Unset: Chrome on THIS machine when it is installed, otherwise the cloud browser (a plan with cloud browsers; localhost is tunneled). 'local' runs the clicking in your own Chrome — free on every plan, no tunnel, no *.localhost limits, much faster — while the cloud agent still decides each step. 'remote' forces the cloud browser (Professional and above). Passwords passed in this call stay on this machine. Page screenshots and text are still sent to the cloud agent. Not available over the remote HTTP MCP."
113
+ },
114
+ headless: { type: "boolean", description: "browser:'local' only. Default true; false shows the Chrome window." },
115
+ useProfile: { type: "boolean", description: "browser:'local' only. Use the MCP-owned logged-in Chrome profile (never your everyday Chrome). Requires allowedDomains." },
116
+ allowedDomains: { type: "array", items: { type: "string" }, description: "browser:'local' only. Extra hostnames (and subdomains) the run may navigate to, beyond the url's own host and localhost. Navigation off this list is refused, redirects included." },
109
117
  extraOrigins: {
110
118
  type: "array",
111
119
  items: { type: "string" },
@@ -64,6 +64,12 @@ export const TestPageChangesInputSchema = z.object({
64
64
  // skip login; that is right for speed and wrong when the login itself is what
65
65
  // you are checking, or when the app's only route between personas is a logout.
66
66
  freshSession: z.boolean().optional(),
67
+ // 'local' runs the clicking in Chrome on THIS machine (no tunnel); the cloud agent still decides each step.
68
+ // Unset: your own Chrome when it is installed (stdio server), otherwise the cloud browser.
69
+ browser: z.enum(['remote', 'local']).optional(),
70
+ headless: z.boolean().optional(),
71
+ useProfile: z.boolean().optional(),
72
+ allowedDomains: z.array(z.string().min(1)).max(32).optional(),
67
73
  // Extra LOCAL origins the run may be redirected to (bead w1os.2) — e.g. an OAuth
68
74
  // vendor on :4717 (also host.docker.internal:4717) or a callback on :4304. Each is
69
75
  // tunnelled and redirects to it are followed through the tunnel. Only origins named
@@ -2,12 +2,14 @@
2
2
  * Shared ProjectRequired text (platform-y2ob.2): says what was observed and
3
3
  * what to pass. Used by check_app_in_browser and trigger_crawl.
4
4
  */
5
- export function projectRequiredMessage(repoName) {
5
+ export function projectRequiredMessage(repoName, acceptsProjectUuid = true) {
6
6
  const observed = repoName
7
7
  ? `Could not determine the DebuggAI project: no project is linked to git repo "${repoName}".`
8
8
  : 'Could not determine the DebuggAI project: this is not a git repo and no repoName was passed.';
9
9
  return observed
10
- + ' Pass `projectUuid` (list yours with the `project` tool, action "list") or `repoName` (the "org/repo" of a linked project).';
10
+ + (acceptsProjectUuid
11
+ ? ' Pass `projectUuid` (list yours with the `project` tool, action "list") or `repoName` (the "org/repo" of a linked project).'
12
+ : ' Pass `repoName` (matches a project name, slug or linked "org/repo"; list yours with the `project` tool, action "list") or `environmentId`.');
11
13
  }
12
14
  /**
13
15
  * A repo->project lookup that FAILED (platform-y2ob.4), as opposed to one that
@@ -15,7 +17,7 @@ export function projectRequiredMessage(repoName) {
15
17
  * network error says nothing about whether a project exists. Reports the HTTP
16
18
  * status and the backend's message verbatim.
17
19
  */
18
- export function projectLookupFailedPayload(repoName, err) {
20
+ export function projectLookupFailedPayload(repoName, err, acceptsProjectUuid = true) {
19
21
  const e = err;
20
22
  const statusCode = typeof e?.statusCode === 'number' ? e.statusCode : undefined;
21
23
  const reason = err instanceof Error ? err.message : String(err);
@@ -25,7 +27,9 @@ export function projectLookupFailedPayload(repoName, err) {
25
27
  return {
26
28
  error: 'ProjectLookupFailed',
27
29
  message: `Could not look up the DebuggAI project for git repo "${repoName}" (${how}). `
28
- + 'This is a lookup failure, not a missing link: pass `projectUuid` to skip the lookup, or fix the connection and retry.',
30
+ + 'This is a lookup failure, not a missing link: '
31
+ + (acceptsProjectUuid ? 'pass `projectUuid` to skip the lookup, ' : 'pass `environmentId` to skip the lookup, ')
32
+ + 'or fix the connection and retry.',
29
33
  detail: { repoName, ...(statusCode !== undefined ? { statusCode } : {}), ...(e?.networkCode ? { networkCode: e.networkCode } : {}) },
30
34
  };
31
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugg-ai/debugg-ai-mcp",
3
- "version": "6.1.6",
3
+ "version": "6.2.2",
4
4
  "description": "Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.",
5
5
  "type": "module",
6
6
  "caddy": "2.11.3",