DEV Community

Custodia-Admin
Custodia-Admin

Posted on • Originally published at pagebolt.dev

I Added Screenshot Proof to Every Cursor MCP Tool Call

I Added Screenshot Proof to Every Cursor MCP Tool Call

I've been building MCP tools in Cursor for two months. Last week I realized: I have no idea what's actually happening.

Cursor calls my tools. The tools run browser automation (fill forms, click buttons, navigate pages). Cursor gets back structured JSON. But I don't see the page. I don't know if the button click worked. I'm flying blind.

So I did something stupid. I added PageBolt screenshots after every tool call.

Best decision I made.

The Problem: Invisible Tool Calls

Here's what using MCP tools in Cursor feels like:

  1. I tell Cursor: "Fill out this form and submit it"
  2. Cursor calls my fill_form MCP tool with parameters
  3. Tool runs. Returns JSON: { success: true, filled_fields: 5 }
  4. Cursor reads the JSON and continues
  5. I have no idea if the form actually filled or if my tool is lying

Cursor can't see the page. I can't see the page. Only my tool knows what actually happened. And if my tool has a bug, I won't find out until something breaks in production.

The Idea: Screenshot Proof

I thought: what if every time my tool takes an action, it takes a screenshot? Not for users. Just for me. Proof that the action actually happened.

I found PageBolt. One API call returns a PNG. Binary response. Done.

So I added it to my MCP tools.

How I Did It

My tool handler now looks like this:

// Cursor MCP tool: fill a form and take proof
async function fillFormWithProof(url, formData) {
  // Step 1: Puppeteer fills the form (my existing automation)
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto(url);

  for (const [field, value] of Object.entries(formData)) {
    await page.type(`input[name="${field}"]`, value);
  }

  await page.click('button[type="submit"]');
  await page.waitForNavigation();

  // Step 2: Capture screenshot proof via PageBolt
  const screenshotResponse = await fetch('https://api.pagebolt.dev/v1/screenshot', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url: page.url(),  // Screenshot current page after form submit
      format: 'png',
      width: 1280,
      height: 720
    })
  });

  if (!screenshotResponse.ok) {
    throw new Error(`Screenshot failed: ${screenshotResponse.status}`);
  }

  const buffer = await screenshotResponse.arrayBuffer();
  const filename = `proof-${Date.now()}.png`;
  fs.writeFileSync(filename, Buffer.from(buffer));

  await browser.close();

  // Step 3: Return result + proof
  return {
    success: true,
    filled_fields: Object.keys(formData),
    submitted: true,
    visual_proof: filename,
    message: `Form submitted. See ${filename} for proof of page state.`
  };
}
Enter fullscreen mode Exit fullscreen mode

Now when Cursor calls my tool, it gets:

{
  "success": true,
  "filled_fields": ["name", "email", "phone"],
  "submitted": true,
  "visual_proof": "proof-1234567890.png",
  "message": "Form submitted. See proof-1234567890.png for proof of page state."
}
Enter fullscreen mode Exit fullscreen mode

I can actually see what my tool did. The page after form submission. The confirmation page. Whatever.

What Changed

Before: Tool calls return JSON. I assume they worked.

After: Tool calls return JSON + screenshot. I can verify they worked.

This sounds small. It's not. It changed how I debug MCP tools completely.

Real Example: Why This Matters

I have an MCP tool that scrapes product listings from an e-commerce site. It navigates to the category page, extracts product names, prices, and ratings, returns structured JSON.

Last week the tool returned:

{
  "success": true,
  "products_found": 12,
  "category": "laptops"
}
Enter fullscreen mode Exit fullscreen mode

But something felt off. The number was too consistent. Every run, exactly 12 products.

Normally I'd dig through code. But I had screenshots now. I opened the latest proof image.

Blank page. The navigation failed silently. My CSS selectors were scraping an empty div.

I would have wasted 30 minutes debugging code. The screenshot showed me in 5 seconds: the page isn't loading.

How I Integrated This Into My Cursor Workflow

  1. After each tool action: Screenshot the page
  2. Save with timestamp: proof-${Date.now()}.png
  3. Return filename in JSON: Cursor can tell me the proof file
  4. Store in versioned folder: ./proofs/ so I can review later
  5. Add to git: Track proof images alongside tool versions

My MCP tool handler now has this pattern:

// Generic proof capture wrapper
async function withProof(toolName, action) {
  try {
    const result = await action();

    // Capture proof after action
    const proofResponse = await fetch('https://api.pagebolt.dev/v1/screenshot', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.PAGEBOLT_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        url: getCurrentPageUrl(),
        format: 'png'
      })
    });

    const buffer = await proofResponse.arrayBuffer();
    const proofFile = `./proofs/${toolName}-${Date.now()}.png`;
    fs.writeFileSync(proofFile, Buffer.from(buffer));

    return {
      ...result,
      visual_proof: proofFile
    };
  } catch (error) {
    return { error: error.message };
  }
}

// Usage in any tool
async function myTool(params) {
  return withProof('myTool', async () => {
    // Your automation here
    return { success: true };
  });
}
Enter fullscreen mode Exit fullscreen mode

Now every tool automatically captures proof.

The Surprising Benefit: Tool Reliability

Once I started capturing proofs, I found bugs I didn't know existed:

  • Cloudflare blocking my requests (proof: redirect page)
  • Lazy-loaded content not appearing (proof: blank areas)
  • Form validation errors (proof: error message visible)
  • Timing issues (proof: page halfway rendered)

Without visual proof, I would have blamed my code. The screenshots showed me: it's the page, not my tool.

Why Cursor Developers Need This

Cursor is powerful for MCP tool development. You describe what you want, Cursor implements it, Cursor tests it. But "testing" means checking JSON output. You can't see the actual page.

Screenshot proof bridges that gap.

Pricing

Plan Requests/Month Cost Best For
Free 100 $0 Development & debugging
Starter 5,000 $29 Small MCP tools
Growth 25,000 $79 Production tools
Scale 100,000 $199 Enterprise automation

What I'd Do Differently

If I were starting over:

  1. Add PageBolt proof capture from day one (not month two)
  2. Store proofs in a cloud bucket, not local files (easier to review)
  3. Screenshot before AND after each action (see the delta)
  4. Use fullPage: true to catch lazy-loaded content

TL;DR

  • Cursor MCP tools are invisible
  • PageBolt screenshots give them eyes
  • One API call after each tool action
  • Visual proof beats guessing every time
  • I should have done this from day one

Try it: PageBolt free — 100 requests/month, no credit card →

Top comments (0)