@debugg-ai/debugg-ai-mcp 3.2.0 → 3.3.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/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to the DebuggAI MCP project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [3.3.0]
9
+
10
+ ### Added — Run artifacts returned as resource links
11
+
12
+ `check_app_in_browser` and `executions {action:"get"}` now surface execution
13
+ artifacts — **run recording, HAR, console log** — as MCP
14
+ [`resource_link`](https://modelcontextprotocol.io/specification/2025-06-18/server/tools)
15
+ content blocks pointing at their presigned URLs, instead of base64-inlining them.
16
+ Leaner responses, and the URLs stay renewable / fetchable on demand. The legacy
17
+ run-recording GIF (previously downloaded and inlined as multi-MB base64) is now a
18
+ link; the `browserSession` presigned URLs are auto-detected and linked
19
+ (deduped).
20
+
21
+ Screenshots are **deliberately kept inline** as image blocks so vision-capable
22
+ clients can still see them — the core visual-verification workflow. Helpers:
23
+ `resourceLinkBlock` + `artifactResourceLinks` in `utils/imageUtils.ts`.
24
+
8
25
  ## [3.2.0]
9
26
 
10
27
  ### Added — Structured tool output (`structuredContent`)
@@ -13,7 +13,7 @@ import { handleExternalServiceError } from '../utils/errors.js';
13
13
  import { DebuggAIServerClient } from '../services/index.js';
14
14
  import { config } from '../config/index.js';
15
15
  import { toPaginationParams } from '../utils/pagination.js';
16
- import { fetchImageAsBase64, imageContentBlock } from '../utils/imageUtils.js';
16
+ import { fetchImageAsBase64, imageContentBlock, resourceLinkBlock, artifactResourceLinks } from '../utils/imageUtils.js';
17
17
  const logger = new Logger({ module: 'searchExecutionsHandler' });
18
18
  function notFound(uuid) {
19
19
  return {
@@ -80,10 +80,25 @@ export async function searchExecutionsHandler(input, _context) {
80
80
  if (img)
81
81
  content.push(imageContentBlock(img.data, img.mimeType));
82
82
  }
83
- if (gifUrl) {
84
- const gif = await fetchImageAsBase64(gifUrl).catch(() => null);
85
- if (gif)
86
- content.push(imageContentBlock(gif.data, 'image/gif'));
83
+ // Artifact links (bead 8qndk): run recording (legacy GIF field) + the
84
+ // browserSession presigned URLs (HAR / console log / recording). Linked,
85
+ // not base64-inlined. Screenshot stays inline above for vision.
86
+ const artifactLinks = [
87
+ ...(gifUrl
88
+ ? [resourceLinkBlock(gifUrl, `run-recording-${input.uuid}.gif`, {
89
+ mimeType: 'image/gif',
90
+ title: 'Run recording',
91
+ description: 'Animated recording of the execution (presigned URL — open or fetch on demand).',
92
+ })]
93
+ : []),
94
+ ...artifactResourceLinks(execution.browserSession),
95
+ ];
96
+ const seenArtifactUris = new Set();
97
+ for (const link of artifactLinks) {
98
+ if (link.uri && !seenArtifactUris.has(link.uri)) {
99
+ seenArtifactUris.add(link.uri);
100
+ content.push(link);
101
+ }
87
102
  }
88
103
  return { content };
89
104
  }
@@ -6,7 +6,7 @@
6
6
  import { config } from '../config/index.js';
7
7
  import { Logger } from '../utils/logger.js';
8
8
  import { handleExternalServiceError } from '../utils/errors.js';
9
- import { fetchImageAsBase64, imageContentBlock } from '../utils/imageUtils.js';
9
+ import { fetchImageAsBase64, imageContentBlock, resourceLinkBlock, artifactResourceLinks } from '../utils/imageUtils.js';
10
10
  import { DebuggAIServerClient } from '../services/index.js';
11
11
  import { TunnelProvisionError } from '../services/tunnels.js';
12
12
  import { resolveTargetUrl, buildContext, findExistingTunnel, ensureTunnel, sanitizeResponseUrls, touchTunnelById, } from '../utils/tunnelContext.js';
@@ -539,11 +539,26 @@ async function testPageChangesHandlerInner(input, context, rawProgressCallback)
539
539
  if (img)
540
540
  content.push(imageContentBlock(img.data, img.mimeType));
541
541
  }
542
- if (gifUrl) {
543
- logger.info(`Embedding GIF/video: ${gifUrl}`);
544
- const gif = await fetchImageAsBase64(gifUrl).catch(() => null);
545
- if (gif)
546
- content.push(imageContentBlock(gif.data, 'image/gif'));
542
+ // Artifact links (bead 8qndk): run recording (legacy GIF field) + the
543
+ // browserSession presigned URLs (HAR / console log / recording). Returned as
544
+ // resource_links, not base64-inlined. Screenshots stay inline above so
545
+ // vision-capable clients still SEE them.
546
+ const artifactLinks = [
547
+ ...(gifUrl
548
+ ? [resourceLinkBlock(gifUrl, 'run-recording.gif', {
549
+ mimeType: 'image/gif',
550
+ title: 'Run recording',
551
+ description: 'Animated recording of the run (presigned URL — open or fetch on demand).',
552
+ })]
553
+ : []),
554
+ ...artifactResourceLinks(sanitizedPayload.browserSession),
555
+ ];
556
+ const seenArtifactUris = new Set();
557
+ for (const link of artifactLinks) {
558
+ if (link.uri && !seenArtifactUris.has(link.uri)) {
559
+ seenArtifactUris.add(link.uri);
560
+ content.push(link);
561
+ }
547
562
  }
548
563
  return { content };
549
564
  }
@@ -39,3 +39,60 @@ function inferMimeFromUrl(url) {
39
39
  export function imageContentBlock(data, mimeType) {
40
40
  return { type: 'image', data, mimeType };
41
41
  }
42
+ /**
43
+ * Build an MCP resource_link content block (MCP 2025-06-18) pointing at an
44
+ * (often presigned) artifact URL — leaner than inlining the bytes, and the URL
45
+ * stays renewable/on-demand. Use for large non-vision artifacts (run-recording
46
+ * GIFs, HAR, console logs) rather than base64-embedding them.
47
+ */
48
+ export function resourceLinkBlock(uri, name, opts = {}) {
49
+ const block = { type: 'resource_link', uri, name };
50
+ if (opts.mimeType)
51
+ block.mimeType = opts.mimeType;
52
+ if (opts.title)
53
+ block.title = opts.title;
54
+ if (opts.description)
55
+ block.description = opts.description;
56
+ return block;
57
+ }
58
+ const MIME_BY_EXT = {
59
+ gif: 'image/gif', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg',
60
+ webp: 'image/webp', mp4: 'video/mp4', webm: 'video/webm',
61
+ har: 'application/json', json: 'application/json', txt: 'text/plain', log: 'text/plain',
62
+ };
63
+ function titleize(key) {
64
+ return key
65
+ .replace(/[_-]+/g, ' ')
66
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
67
+ .replace(/\bUrl\b|\bUri\b/gi, '')
68
+ .replace(/\s+/g, ' ')
69
+ .trim()
70
+ .replace(/^\w/, (c) => c.toUpperCase());
71
+ }
72
+ /**
73
+ * Build resource_link blocks for every (presigned) artifact URL found one level
74
+ * deep in `source` (e.g. an execution's browserSession: HAR, console log, run
75
+ * recording). Defensive about exact field names — it links any https value and
76
+ * skips tunnel/ngrok hosts. Returns [] for nullish/empty input.
77
+ */
78
+ export function artifactResourceLinks(source) {
79
+ if (!source || typeof source !== 'object')
80
+ return [];
81
+ const out = [];
82
+ for (const [key, value] of Object.entries(source)) {
83
+ if (typeof value !== 'string')
84
+ continue;
85
+ if (!/^https?:\/\//i.test(value))
86
+ continue;
87
+ if (/ngrok|tunnel/i.test(value))
88
+ continue;
89
+ const ext = (value.split('?')[0].match(/\.([a-z0-9]+)$/i)?.[1] ?? '').toLowerCase();
90
+ const name = ext ? `${key}.${ext}` : key;
91
+ out.push(resourceLinkBlock(value, name, {
92
+ mimeType: MIME_BY_EXT[ext],
93
+ title: titleize(key),
94
+ description: 'Execution artifact (presigned URL — open or fetch on demand).',
95
+ }));
96
+ }
97
+ return out;
98
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugg-ai/debugg-ai-mcp",
3
- "version": "3.2.0",
3
+ "version": "3.3.0",
4
4
  "description": "Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.",
5
5
  "type": "module",
6
6
  "bin": {