Skip to content

Instantly share code, notes, and snippets.

@kornysietsma
Created February 3, 2026 08:27
Show Gist options
  • Select an option

  • Save kornysietsma/e849865af8a10741b41b0a9a90c471a0 to your computer and use it in GitHub Desktop.

Select an option

Save kornysietsma/e849865af8a10741b41b0a9a90c471a0 to your computer and use it in GitHub Desktop.
Using a trivial MCP server to bypass Github Copilot's inability to view images
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = [
# "mcp>=1.0.0",
# "pillow>=10.0.0",
# ]
# requires-python = ">=3.12"
# ///
"""
View Image MCP Server
Stdio-based MCP server that provides image viewing capabilities.
Accepts images via file path, converts to PNG, and returns with metadata.
"""
import sys
import base64
import json
import asyncio
from io import BytesIO
from PIL import Image
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, ImageContent
# Supported image formats (Pillow format names)
SUPPORTED_FORMATS = {"PNG", "JPEG", "WEBP", "GIF"}
TOOL_DESCRIPTION = """USE THIS TOOL TO VIEW AND ANALYZE IMAGES. When you need to see the contents of an image file or analyze image data, you MUST use this tool. Do not attempt to describe images without first viewing them through this tool.
Accepts filePath: Absolute path to an image file (PNG, JPEG, WebP, GIF)
Returns the image in PNG format with metadata (dimensions, format). Always use this tool before describing or analyzing any image content."""
def load_image_from_path(file_path: str) -> tuple[Image.Image, str]:
with Image.open(file_path) as img:
if img.format not in SUPPORTED_FORMATS:
raise ValueError(f"Unsupported format: {img.format}. Supported: {', '.join(SUPPORTED_FORMATS)}")
return img.copy(), img.format
def convert_to_png(image: Image.Image) -> bytes:
buffer = BytesIO()
image.save(buffer, format="PNG")
return buffer.getvalue()
def create_app() -> Server:
server = Server("view-image")
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="view_image",
description=TOOL_DESCRIPTION,
inputSchema={
"type": "object",
"properties": {
"filePath": {
"type": "string",
"description": "Absolute path to an image file. Supports PNG, JPEG, WebP, GIF.",
}
},
"required": ["filePath"],
},
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent | ImageContent]:
if name != "view_image":
raise ValueError(f"Unknown tool: {name}")
file_path = arguments["filePath"]
image, original_format = load_image_from_path(file_path)
width, height = image.size
png_bytes = convert_to_png(image)
png_base64 = base64.b64encode(png_bytes).decode("utf-8")
metadata = {
"width": width,
"height": height,
"originalFormat": original_format.lower(),
"filePath": file_path,
}
return [
TextContent(
type="text",
text=json.dumps(metadata),
),
ImageContent(
type="image",
data=png_base64,
mimeType="image/png",
),
]
return server
async def main():
server = create_app()
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options(),
)
if __name__ == "__main__":
asyncio.run(main())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment