Skip to content

feat: add support for structured content for MCP#318

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

feat: add support for structured content for MCP#318
hallvictoria wants to merge 8 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:

import base64
import json
from datetime import datetime
from enum import Enum
from typing import List, Optional, Dict
from uuid import UUID

import azure.functions as func


app = func.FunctionApp()


# Enum example (matching .NET JobType)
class JobType(Enum):
    FULL_TIME = "FullTime"
    PART_TIME = "PartTime"
    CONTRACT = "Contract"
    INTERNSHIP = "Internship"
    TEMPORARY = "Temporary"
    FREELANCE = "Freelance"
    UNEMPLOYED = "Unemployed"


# ============================================================================
# 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 3: 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 4: 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 8: @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


# In-memory snippet cache
snippets_cache: Dict[str, str] = {
    "hello": "print('Hello, World!')",
    "loop": "for i in range(10):\n    print(i)"
}


@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 9: Complex object as parameter
# Matches: SaveSnippet in .NET
# ============================================================================
@app.mcp_tool()
def save_snippet(name: str, content: str) -> str:
    """Saves a code snippet."""
    snippets_cache[name] = content
    return f"Snippet '{name}' saved successfully"


# ============================================================================
# Scenario 10: Complex request/response objects
# Matches: SearchSnippets in .NET
# ============================================================================
@func.mcp_content
class SnippetSearchRequest:
    """Search request for snippets."""
    def __init__(self, pattern: str, case_sensitive: bool = False):
        self.pattern = pattern
        self.case_sensitive = case_sensitive


@app.mcp_tool()
def search_snippets(pattern: str, case_sensitive: bool = False) -> Dict[str, str]:
    """Searches for snippets matching a pattern."""
    results = {}
    for key, value in snippets_cache.items():
        if case_sensitive:
            if pattern in key:
                results[key] = value
        else:
            if pattern.lower() in key.lower():
                results[key] = value
    return results


# ============================================================================
# Scenario 11: 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 @mcp_content decorated class."""
    return Snippet(
        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

1 participant