Skip to content

feat: add support for structured content for MCP#318

Open
hallvictoria wants to merge 9 commits intodevfrom
hallvictoria/structured-content
Open

feat: add support for structured content for MCP#318
hallvictoria wants to merge 9 commits intodevfrom
hallvictoria/structured-content

Conversation

@hallvictoria
Copy link
Copy Markdown
Contributor

@hallvictoria hallvictoria commented Mar 17, 2026

fixes: Azure/azure-functions-python-worker#1825, Azure/azure-functions-python-worker#1836, Azure-Samples/remote-mcp-functions-python#38

image

Examples:

# ============================================================================
# Scenario 1: Single ResourceLinkBlock return
# Matches: GetFunctionsLogo in .NET
# ============================================================================
@app.mcp_tool()
def get_functions_logo() -> func.ResourceLinkBlock:
    """Returns the Azure Functions logo as a resource link."""
    return func.ResourceLinkBlock(
        uri="file://logo.png",
        name="Azure Functions Logo",
        mime_type="image/png"
    )

# ============================================================================
# Scenario 2: Multiple content blocks (List[ContentBlock])
# Matches: MultiContentTypeFunction in .NET
# ============================================================================
@app.mcp_tool()
def multi_content_type_function(
    data: str,
    mime_type: Optional[str] = None
) -> List[func.ContentBlock]:
    """Responds to user with multiple content blocks."""
    return [
        func.TextContentBlock(text="Here is an image for you!"),
        func.ResourceLinkBlock(
            name="example",
            uri="https://www.google.com/",
            description="Image Information"
        ),
        func.ImageContentBlock(
            data=data,
            mime_type=mime_type or "image/jpeg"
        )
    ]


# ============================================================================
# Scenario 3: Single ImageContentBlock return
# Matches: RenderImage in .NET
# ============================================================================
@app.mcp_tool()
def render_image(
    data: str,
    mime_type: Optional[str] = None
) -> func.ImageContentBlock:
    """Responds to user with an image."""
    return func.ImageContentBlock(
        data=data,
        mime_type=mime_type or "image/jpeg"
    )

# ============================================================================
# Scenario 4: @mcp_content decorated dataclass
# Matches: GetSnippet returning [McpContent] Snippet in .NET
# ============================================================================
@func.mcp_content
class Snippet:
    """Snippet class that will be serialized as structured content."""
    def __init__(self, name: str, content: Optional[str] = None):
        self.name = name
        self.content = content

@app.mcp_tool()
def get_snippet(name: str) -> Optional[Snippet]:
    """Gets a code snippet by name."""
    if name in snippets_cache:
        return Snippet(name=name, content=snippets_cache[name])
    return None


# ============================================================================
# Scenario 5: CallToolResult with explicit content and structured content
# Matches: GetImageWithMetadata in .NET
# ============================================================================
@app.mcp_tool()
def get_image_with_metadata() -> func.CallToolResult:
    """Returns an image with metadata as structured content."""
    # Metadata object
    metadata = {
        "ImageId": "logo",
        "Format": "png",
        "CreatedAt": datetime.utcnow().isoformat(),
        "Tags": ["functions", "azure", "serverless"]
    }
    
    # Read image file (or use a tiny test image)
    # For demo, using a 10x10 red pixel PNG (larger version of the 1x1)
    import zlib
    import struct
    
    # PNG signature
    png_data = b'\x89PNG\r\n\x1a\n'
    
    def make_chunk(chunk_type, data):
        """Create a PNG chunk with proper CRC."""
        chunk_data = chunk_type + data
        crc = zlib.crc32(chunk_data) & 0xffffffff
        return struct.pack('>I', len(data)) + chunk_data + struct.pack('>I', crc)
    
    # IHDR: 10x10, 8-bit RGB
    width, height = 10, 10
    ihdr_data = struct.pack('>IIBBBBB', width, height, 8, 2, 0, 0, 0)
    png_data += make_chunk(b'IHDR', ihdr_data)
    
    # IDAT: image data (10x10 red pixels)
    raw_data = b''
    for y in range(height):
        raw_data += b'\x00'  # Filter type: None
        for x in range(width):
            raw_data += b'\xff\x00\x00'  # RGB: Red
    
    compressed = zlib.compress(raw_data, 9)
    png_data += make_chunk(b'IDAT', compressed)
    
    # IEND
    png_data += make_chunk(b'IEND', b'')
    
    base64_image = base64.b64encode(png_data).decode('utf-8')
    
    # Construct CallToolResult with both content blocks and structured content
    return func.CallToolResult(
        content=[
            func.TextContentBlock(text=json.dumps(metadata)),  # JSON representation
            func.ImageContentBlock(
                data=base64_image,
                mime_type="image/png"
            )
        ],
        structured_content=metadata  # Structured data for parsing
    )


# ============================================================================
# Additional scenario: use_result_schema parameter
# ============================================================================
@app.mcp_tool(use_result_schema=True)
def get_structured_data(query: str) -> Snippet:
    """Returns structured data using custom class."""
    return MyCustomClass(
        name="result",
        content=f"Query result for: {query}"
    )

@hallvictoria hallvictoria marked this pull request as ready for review April 3, 2026 18:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[MCP Tool] Implement support for structured content

3 participants