Skip to main content
Glama
norman-finance

Norman Finance MCP Server

Official

generate_finanzamt_preview

Generate a PDF preview of a tax report for the Finanzamt. Verify the preview before submission using the provided file link. Includes line items, totals, and a summary based on the report data.

Instructions

Generate a test Finanzamt preview for a tax report.

Args:
    report_id: Public ID of the tax report
    
Returns:
    Generate a PDF preview of the tax report. 
    Always suggest to check the preview before sending it to the Finanzamt.
    Always include the path to the generated PDF file as a link to open the file from local file system.
    Get the report data from @get_tax_report and show line items and totals.
      You could add short summary based on the report data.
    Ask follow up question to file the tax report to the Finanzamt @submit_tax_report. Don't send the report to the Finanzamt without the user confirmation.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
report_idYes

Implementation Reference

  • Main handler function for the generate_finanzamt_preview tool. Calls the Norman API to generate a PDF preview of a tax report, converts the first page to a resized PNG image, and returns it as an MCP Image object.
    @mcp.tool()
    async def generate_finanzamt_preview(
        ctx: Context,
        report_id: str = Field(description="Public ID of the tax report to generate a preview for")
    ) -> Image:
        """
        Generate a test Finanzamt preview for a tax report and return it as an image.
        
        Args:
            report_id: Public ID of the tax report
            
        Returns:
            The tax report preview as an image in PNG format
        """
        api = ctx.request_context.lifespan_context["api"]
        
        # Validate report_id
        if not report_id or not isinstance(report_id, str) or not report_id.strip():
            raise ValueError("Invalid report ID")
        
        preview_url = urljoin(
            config.api_base_url,
            f"api/v1/taxes/reports/{report_id}/generate-preview/"
        )
    
        try:
            response = requests.post(
                preview_url,
                headers={"Authorization": f"Bearer {api.access_token}"},
                timeout=config.NORMAN_API_TIMEOUT
            )
            response.raise_for_status()
            
            # The response contains raw PDF bytes
            if isinstance(response.content, bytes) and len(response.content) > 0:
                # Convert PDF to image
                images = convert_from_bytes(response.content, dpi=150)
                
                # Get the first page as PIL Image
                first_page = images[0]
                
                # Convert to RGB and resize if needed to keep file size manageable
                first_page = first_page.convert('RGB')
                
                # Calculate new dimensions while maintaining aspect ratio
                width, height = first_page.size
                max_dim = 1000
                if width > max_dim or height > max_dim:
                    if width > height:
                        new_width = max_dim
                        new_height = int(height * (max_dim / width))
                    else:
                        new_height = max_dim
                        new_width = int(width * (max_dim / height))
                    first_page = first_page.resize((new_width, new_height), PILImage.LANCZOS)
                
                # Save as PNG to bytes buffer
                buffer = io.BytesIO()
                first_page.save(buffer, format="PNG", optimize=True)
                buffer.seek(0)
                
                # Return as Image
                return Image(data=buffer.getvalue(), format="png")
            else:
                raise ValueError("Preview generation failed or invalid response format")
        except requests.exceptions.RequestException as e:
            logger.error(f"Failed to generate tax report preview: {str(e)}")
            if hasattr(e, 'response') and e.response is not None:
                logger.error(f"Response: {e.response.text}")
            raise ValueError(f"Failed to generate tax report preview: {str(e)}")
        except Exception as e:
            logger.error(f"Error generating tax report preview: {str(e)}")
            raise ValueError(f"Error generating tax report preview: {str(e)}")
  • Calls register_tax_tools(server) as part of the overall tool registration in the MCP server setup within create_app function.
    register_client_tools(server)
    register_invoice_tools(server)
    register_tax_tools(server)
    register_transaction_tools(server)
    register_document_tools(server)
    register_company_tools(server)
    register_prompts(server)
    register_resources(server)
  • Input schema: report_id (str, required, description provided). Output: Image type from mcp.types.
    async def generate_finanzamt_preview(
        ctx: Context,
        report_id: str = Field(description="Public ID of the tax report to generate a preview for")
    ) -> Image:
  • Function that registers all tax tools, including generate_finanzamt_preview, by defining them with @mcp.tool() decorators inside it.
    def register_tax_tools(mcp):
        """Register all tax-related tools with the MCP server."""
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It describes key behaviors: generating a PDF preview, including a file path link, and advising user confirmation before submission. However, it misses critical details like whether this is a read-only or mutating operation, error handling, or any side effects (e.g., if it creates temporary files). For a tool with no annotations, this leaves significant gaps in understanding its operational impact.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is poorly structured and verbose, mixing usage instructions, implementation details, and behavioral notes in a disorganized way. Sentences like 'Always suggest to check the preview...' and 'You could add short summary...' are prescriptive and clutter the core purpose. It's front-loaded with the main action but loses focus with tangential guidance, reducing clarity and efficiency.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of generating a tax report preview with no annotations and no output schema, the description is incomplete. It mentions a PDF output and link but lacks details on return format, error cases, or dependencies. The references to other tools (@get_tax_report, @submit_tax_report) hint at a workflow but don't fully explain integration or prerequisites, leaving the agent with insufficient context for reliable use.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The description adds minimal semantics beyond the input schema: it defines 'report_id' as the 'Public ID of the tax report,' which clarifies the parameter's purpose. However, with schema description coverage at 0% and only one parameter, this provides basic but insufficient detail (e.g., format examples or constraints). The baseline is 4 for zero parameters, but here the single parameter's description is too vague to fully compensate for the lack of schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Generate a test Finanzamt preview for a tax report.' It specifies the verb ('Generate'), resource ('test Finanzamt preview'), and target ('tax report'), making the intent unambiguous. However, it doesn't explicitly differentiate from sibling tools like 'submit_tax_report' or 'get_tax_report' beyond implied usage context.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use this tool by referencing other tools (e.g., 'Get the report data from @get_tax_report' and 'Ask follow up question to file the tax report to the Finanzamt @submit_tax_report'), suggesting it's part of a workflow. However, it lacks explicit guidance on when to choose this tool over alternatives or any prerequisites, leaving usage context somewhat inferred rather than clearly stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Related Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/norman-finance/norman-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server