ScreenshotNeo

BlogAI agents

How to Return Website Screenshots from an MCP Server as File Attachments

Return screenshots as MCP image content, embedded resources, or resource links. Learn which format fits, how to implement it in Python, and what clients display.

By the ScreenshotNeo team4 October 20267 min read

To show a screenshot directly to a model, return an MCP image content block with base64-encoded image bytes and the correct MIME type. To represent it as a file-like resource with an identity and URI, return an embedded resource. To keep the bytes out of the immediate tool result, return a resource link that the client can fetch. These are different protocol shapes, and none guarantees that every MCP host will display a downloadable attachment: the host controls presentation.

This guide uses the official MCP tool-result specification and the MCP Python SDK media guide. The Python examples illustrate the documented patterns; check the SDK version in your project for the exact import path and signature.

Choose the right MCP content type

Decide based on where the screenshot bytes live, whether the result needs a resource URI, and how the client will consume it.

Pattern Bytes in tool result? Resource URI? Best fit
Image content block Yes, base64-encoded Not required Immediate visual or model consumption
Embedded resource Yes, embedded as resource content Yes Attachment-like content with resource identity and MIME type
Resource link No; the result contains a pointer Yes Separate retrieval or large/separately managed content

An ordinary text block containing a local path or base64 string is not equivalent to any of these image/resource content types. The client cannot infer that arbitrary text is an image attachment.

Return an image for immediate viewing

Use image content when the next step is for the model or user interface to inspect the pixels immediately. MCP image content includes a content type, base64 data, and MIME type; encode the actual image bytes, and identify the format those bytes use.

Python SDK example

from mcp.server.fastmcp import Image

@mcp.tool()
def screenshot() -> Image:
    image_bytes = capture_website_screenshot()
    return Image(data=image_bytes, format="png")

capture_website_screenshot() stands for your browser or screenshot API integration. The example assumes mcp is the FastMCP server instance already configured by your application. The SDK Image helper accepts exactly one of path= or data=; for bytes, pass format= so the MIME type matches the encoded image.

Use a path or raw bytes

from mcp.server.fastmcp import Image

@mcp.tool()
def screenshot_from_file() -> Image:
    return Image(path="/tmp/capture.webp")

@mcp.tool()
def screenshot_from_memory() -> Image:
    image_bytes = capture_website_screenshot()
    return Image(data=image_bytes, format="webp")

With path=, the SDK infers the MIME type from the file suffix. Documented suffix examples include PNG, JPG/JPEG, GIF, and WebP. With data=, provide the format explicitly. A mismatch between the bytes and declared format can prevent correct decoding or display.

The SDK helper is a convenience: it produces the protocol-level ImageContent representation. For a different language or SDK, construct the equivalent image content block according to that implementation’s MCP support.

Return an embedded resource for attachment-like semantics

Choose an embedded resource when a screenshot should have a URI and MIME type as a resource, while its binary content travels in the tool result. The binary resource representation described by the Python SDK uses BlobResourceContents with the image bytes base64-encoded in blob.

# Protocol shape, shown as pseudocode because SDK constructors vary by version:
{
  "type": "resource",
  "resource": {
    "uri": "screenshot://captures/request-123.png",
    "mimeType": "image/png",
    "blob": "<base64-encoded PNG bytes>"
  }
}

Use a stable or request-specific URI that meaningfully identifies the capture. Set mimeType to the actual format. This illustrates the data shape rather than a drop-in constructor; consult the SDK version in use for its concrete embedded-resource API.

An embedded resource is a protocol-level representation, not a promise about the host’s visual treatment. A host may show it as an attachment, render it another way, or expose it through its own resource interface.

A resource link carries a URI pointer rather than the screenshot payload. Use it when the client should retrieve the screenshot separately through an MCP resource flow supported by that client.

# Protocol shape, shown as pseudocode:
{
  "type": "resource_link",
  "uri": "screenshot://captures/request-123.png",
  "name": "Website screenshot",
  "mimeType": "image/png"
}

Expose the resource through a retrieval mechanism the client can resolve, and return that URI as a resource link. A server-local filesystem path is not automatically reachable from a remote client. Confirm that the client supports resource links and can access the URI scheme or resource server you use.

Check client presentation separately

MCP tool results can contain multiple unstructured content items, including text, image, resource-link, and embedded-resource blocks. They can also contain structuredContent for machine-readable JSON. That JSON channel does not replace image or binary resource content.

The protocol defines the data the server returns; it does not mandate one user-interface pattern. A client may render an image inline, expose an embedded resource as an attachment, or provide its own retrieval flow for a resource link. If users specifically need a downloadable attachment, verify the result in the target MCP host and client rather than assuming server-side conformance guarantees a download button.

Build a reliable screenshot tool

  1. Capture the page. Use your chosen browser automation or screenshot service and obtain the encoded image bytes. Handle navigation failure and capture timeout before constructing a successful image result.
  2. Choose the content shape. Return an image for immediate visual consumption, an embedded resource for a URI-bearing binary result, or a resource link for separate retrieval.
  3. Set the true MIME type. Match the metadata to the encoded bytes, such as image/png or image/webp. Do not infer format from the page URL; it is the screenshot output format that matters.
  4. Keep resource URIs resolvable. For resource patterns, use an identifier the intended client can retrieve. Avoid exposing a path that exists only on the server’s local disk.
  5. Test in the target host. Check image rendering, resource display or retrieval, and behavior for a failed capture. Client behavior is part of the user’s outcome.

Common errors and fixes

Symptom Likely cause Fix
The model sees text or a path instead of an image The tool returned a text block containing a path or base64 string Return an MCP image content block, or an embedded resource if resource semantics are required.
The image fails to decode or is mislabeled The declared format or MIME type does not match the bytes Pass the actual format to Image(data=..., format=...); for embedded resources, set the matching MIME type.
The returned file path cannot be opened by the client The path is local to the server and inaccessible to the client Send image bytes inline, embed a binary resource, or expose a resolvable resource URI.
A resource link shows no image The client cannot resolve the URI or does not support that retrieval flow Use a client-accessible resource mechanism, verify host support, or return the image inline.
The result is not presented as a download The host chooses a different presentation for that content type Verify the exact MCP host; use its supported attachment flow or offer a user-accessible retrieval resource.
The screenshot is unexpectedly large Full-page dimensions or image encoding create a large payload Consider a smaller viewport capture, a suitable compressed format, or a resource-link flow when separately supported.
The SDK rejects the helper arguments The installed SDK version has a different API surface Check the installed version’s media documentation and use its matching import and signature.

Performance, reliability, and cost considerations

Inline image content and embedded binary resources place the screenshot bytes in the immediate tool result, so larger captures increase the data the client must receive and process at once. A resource link avoids carrying those bytes in that result, but adds retrieval work and depends on client support and URI accessibility. Choose based on payload size and the user’s workflow.

For reliability, report capture failures clearly rather than returning a success-shaped image with empty or invalid bytes. Validate that bytes exist and that output format metadata agrees with the encoding. Test both successful captures and failure paths in the target client, since server correctness alone cannot establish client presentation.

MCP itself does not set a screenshot capture price. Costs depend on the browser infrastructure or screenshot service behind your tool, along with image storage and transfer if you provide separately retrievable resources. Account for those components in your own deployment.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. Its MCP tools include take_screenshot, get_page_info, and capture_pdf. For your own MCP server, the direct API call below returns screenshot bytes; the screenshot response is distinct from deciding how your MCP tool packages those bytes for its client.

See the ScreenshotNeo API documentation for request details.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off.
  • Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are never billed; response headers identify the page verdict and billing status.
  • An MCP server lets AI agents, including Claude, Cursor, and other MCP clients, take screenshots.
  • 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.

FAQ

Does returning an MCP image guarantee a downloadable file?

No. It supplies image content for the host to consume. Whether the host shows a download control or attachment view is client behavior.

Should I return both image content and an embedded resource?

Only if your client workflow benefits from both representations. Each adds payload or handling complexity; select the shapes the target host actually uses.

Can I put image metadata in structured content instead?

You can return structured JSON for machine-readable fields, but it is a separate channel and does not substitute for image bytes or embedded binary resource content.

Which format should I use if the model needs to inspect the screenshot?

Return image content with the screenshot bytes and matching MIME type. That is the direct inline image pattern.