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:
- I tell Cursor: "Fill out this form and submit it"
- Cursor calls my
fill_formMCP tool with parameters - Tool runs. Returns JSON:
{ success: true, filled_fields: 5 } - Cursor reads the JSON and continues
- 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.`
};
}
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."
}
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"
}
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
- After each tool action: Screenshot the page
-
Save with timestamp:
proof-${Date.now()}.png - Return filename in JSON: Cursor can tell me the proof file
-
Store in versioned folder:
./proofs/so I can review later - 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 };
});
}
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:
- Add PageBolt proof capture from day one (not month two)
- Store proofs in a cloud bucket, not local files (easier to review)
- Screenshot before AND after each action (see the delta)
- Use
fullPage: trueto 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)