DEV Community

Cover image for Integrating Autype with LLM Agents (MCP): Templates, Schema Validation and Feedback Loops
Kevin
Kevin

Posted on • Originally published at autype.com

Integrating Autype with LLM Agents (MCP): Templates, Schema Validation and Feedback Loops

When you connect an LLM agent to a document generation system, you quickly hit a wall. The agent produces output, but the output needs to fit a strict schema. One missing field, one wrong type, and the document fails to render. Then there's the feedback problem. How does the agent know what went wrong? How does it fix errors without human intervention?

Autype solves this through the Model Context Protocol (MCP). Instead of hoping your LLM generates valid documents, you give it tools that enforce validation, provide structured error messages, and enable iterative refinement. This article shows how to build robust LLM-powered document workflows with Autype's MCP server.

LLM Agent MCP Integration Flow

The MCP Architecture

Autype's MCP server lives at https://mcp.autype.com/mcp/. It implements MCP 2025-03-26 over Streamable HTTP, which means no installation. Point any MCP-compatible client at the URL and you're connected.

Supported clients: Claude Desktop, Claude Code (CLI), Cursor, Windsurf, VS Code with GitHub Copilot, and the MCP Inspector.

The server exposes two categories of resources:

  1. Tools — Actions the LLM can take (render documents, create sessions, manage sections)
  2. Schema Resources — Machine-readable schemas the LLM reads to understand valid document structure

Schema resources are particularly important. Instead of hardcoding document templates, your LLM fetches the current schema from:

  • autype://schemas/document — Complete document JSON schema
  • autype://schemas/defaults — Styling defaults
  • autype://schemas/variables — Template variable definitions

The LLM reads these schemas and constructs valid documents from first principles.

Building a Template with Schema-First Validation

The key to reliable LLM document generation is validating before rendering. Autype provides a validation endpoint that returns structured errors the LLM can parse and fix.

Here's how the flow works:

import httpx
import json

AUTYPE_API_KEY = "your-api-key"
BASE_URL = "https://api.autype.com/api/v1/dev"

# Step 1: LLM generates document JSON based on schema
document_json = {
    "document": {
        "type": "pdf",
        "size": "A4",
        "title": "Q4 Financial Report"
    },
    "variables": {
        "companyName": "Acme Industries",
        "reportDate": "2024-12-15",
        "totalRevenue": {"type": "number", "value": 2847500}
    },
    "defaults": {
        "fontFamily": "Helvetica",
        "fontSize": 11,
        "color": "#1a1a1a"
    },
    "sections": [
        {
            "id": "cover",
            "type": "page",
            "align": "center",
            "content": [
                {"type": "h1", "text": "{{companyName}}"},
                {"type": "text", "text": "Quarterly Financial Report"},
                {"type": "text", "text": "{{reportDate}}", "fontSize": 14}
            ]
        },
        {
            "id": "summary",
            "type": "flow",
            "content": [
                {"type": "h2", "text": "Executive Summary"},
                {"type": "text", "text": "Total revenue for Q4: ${{totalRevenue}}"}
            ]
        }
    ]
}

# Step 2: Validate without consuming credits
validate_response = httpx.post(
    f"{BASE_URL}/render/validate?strict=true",
    headers={"X-API-Key": AUTYPE_API_KEY, "Content-Type": "application/json"},
    json={"config": document_json}
)

result = validate_response.json()

if not result["valid"]:
    # LLM parses errors and fixes them
    for error in result.get("errors", []):
        print(f"Error at {error['path']}: {error['message']}")
    # Feed errors back to LLM for correction...
else:
    print("Document is valid, ready to render")
Enter fullscreen mode Exit fullscreen mode

The validation endpoint returns structured error objects with a path field pointing to the exact location in the JSON. This makes it trivial for an LLM to understand what went wrong and apply a fix.

Example validation error response:

{
  "valid": false,
  "errors": [
    {
      "path": "sections.0.content.2.type",
      "message": "Invalid element type 'paragraph'. Did you mean 'text'?",
      "code": "INVALID_ELEMENT_TYPE"
    },
    {
      "path": "variables.totalRevenue",
      "message": "Number variables require 'type' and 'value' properties",
      "code": "SCHEMA_VIOLATION"
    }
  ],
  "warnings": [
    {
      "path": "defaults.fontFamily",
      "message": "Font 'Helvetica' not found, falling back to 'Roboto'",
      "code": "FONT_FALLBACK"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The Interactive Document Builder Pattern

For complex documents, the MCP server provides an iterative builder workflow. Instead of generating the entire document at once, the LLM creates a session and adds sections incrementally.

This pattern works well for:

  • Long documents where you want to validate each section
  • Documents that require multiple rounds of human feedback
  • Templates where structure is fixed but content varies

Here's the MCP tool sequence:

  1. Create session: builder_create_session returns a session ID
  2. Set defaults: builder_update_defaults defines fonts, colors, spacing
  3. Set variables: builder_set_variables defines template placeholders
  4. Add sections: builder_add_section adds content section by section
  5. Render: builder_render produces the final output
  6. Cleanup: builder_delete_session removes temporary state

The LLM can call builder_update_section at any point to fix issues without starting over.

Agent Prompt Template for Document Generation

Here's a prompt template you can use with Claude or other LLMs connected to Autype's MCP server:

You are a document generation agent connected to Autype via MCP.

WORKFLOW:
1. Fetch the document schema from autype://schemas/document
2. Create a builder session using builder_create_session
3. Set document defaults (fonts, colors) using builder_update_defaults
4. Define template variables using builder_set_variables
5. Add sections one at a time using builder_add_section
6. After each section, validate the session state
7. If validation fails, fix errors and retry
8. When complete, render using builder_render

VALIDATION RULES:
- Every section must have: id, type ("flow" or "page"), content array
- Every content element must have a valid "type": text, h1-h6, image, table, list, code, math, chart
- Variable names must match pattern: ^[a-zA-Z][a-zA-Z0-9_]*$
- Variable placeholders in text: {{varName}} or ${varName}

ERROR HANDLING:
- Parse validation errors carefully
- Fix the specific path mentioned in the error
- Re-validate before attempting to render
- Never guess schema structure — always reference the schema

OUTPUT:
- Return the download URL from the render response
- Summarize what was generated
Enter fullscreen mode Exit fullscreen mode

Variable Types and Data Binding

Autype supports multiple variable types beyond simple text. This matters when your LLM agent needs to inject structured data like tables or images.

Variable type schema:

{
  "companyName": "Acme Industries",
  "logo": {
    "type": "image",
    "src": "https://cdn.example.com/logo.png",
    "width": 200,
    "align": "center"
  },
  "features": {
    "type": "list",
    "items": ["Real-time collaboration", "Version control", "API access"],
    "ordered": false
  },
  "pricingTable": {
    "type": "table",
    "columns": ["Plan", "Price", "Credits"],
    "data": [
      ["Free", "€0", "50/month"],
      ["Writer", "€9", "400/month"],
      ["Pro", "€25", "1,500/month"]
    ]
  },
  "annualRevenue": {
    "type": "number",
    "value": 1250000
  }
}
Enter fullscreen mode Exit fullscreen mode

When the LLM generates a document with these variables, placeholders like {{companyName}} get replaced, while structured types (image, list, table) render as full elements.

Human-in-the-Loop Feedback

The real power of MCP integration emerges when you add human feedback to the loop. Here's a typical workflow:

  1. LLM generates initial document
  2. Human reviews in Autype's web editor or downloads the PDF
  3. Human provides feedback: "Add a third column to the pricing table" or "Change the accent color to blue"
  4. LLM receives feedback and calls builder_update_section or builder_update_defaults
  5. Document re-renders with changes applied

Because MCP maintains session state, the LLM doesn't need to regenerate the entire document. It modifies specific sections or defaults and re-renders.

Example: Iterative Style Refinement

Let's say the human wants to change the document's visual style after reviewing the first draft:

# LLM receives feedback: "Make headings dark blue and increase font size"

style_update = {
    "defaults": {
        "fontFamily": "Roboto",
        "fontSize": 12,
        "styles": {
            "h1": {
                "fontSize": 28,
                "fontWeight": "bold",
                "color": "#1e3a5f"
            },
            "h2": {
                "fontSize": 22,
                "fontWeight": "bold",
                "color": "#2c5282"
            }
        }
    }
}

# MCP call: builder_update_defaults
# Then: builder_render to get updated PDF
Enter fullscreen mode Exit fullscreen mode

The document structure stays the same, but styling updates cascade through all sections.

Bulk Generation for Scale

Once your LLM agent has a validated template, you can scale to hundreds of documents via the bulk render API. This is useful for personalized reports, contracts, or invoices.

# Bulk render with variable sets
bulk_job = {
    "documentId": "template-doc-uuid",  # Saved template from builder
    "format": "PDF",
    "items": [
        {"companyName": "Acme Corp", "totalRevenue": {"type": "number", "value": 1200000}},
        {"companyName": "Beta LLC", "totalRevenue": {"type": "number", "value": 850000}},
        {"companyName": "Gamma Inc", "totalRevenue": {"type": "number", "value": 2100000}}
    ],
    "webhook": {
        "url": "https://your-app.com/webhook/autype-complete",
        "headers": {"X-Webhook-Secret": "your-secret"}
    }
}

response = httpx.post(
    f"{BASE_URL}/bulk-render",
    headers={"X-API-Key": AUTYPE_API_KEY},
    json=bulk_job
)

# Returns: {"bulkJobId": "job-uuid", "status": "PENDING", "totalItems": 3}
Enter fullscreen mode Exit fullscreen mode

Each item in the array produces one document. The webhook fires when all documents are ready, or you can poll the status endpoint.

Error Recovery Patterns

Robust LLM workflows need error recovery. Here's a pattern for handling common failure modes:

async def render_with_retry(document_json, max_attempts=3):
    for attempt in range(max_attempts):
        # Validate first
        validation = await validate_document(document_json)

        if not validation["valid"]:
            # Feed errors back to LLM for correction
            corrected = await llm_fix_errors(document_json, validation["errors"])
            document_json = corrected
            continue

        # Attempt render
        render_result = await render_document(document_json)

        if render_result["status"] == "FAILED":
            # Parse error and determine if retryable
            if "insufficient credits" in render_result.get("error", ""):
                raise CreditsExhaustedError()

            # Re-validate in case of runtime errors
            continue

        return render_result

    raise MaxRetriesExceededError()

async def llm_fix_errors(document_json, errors):
    # Construct prompt with error details
    error_context = "\n".join([
        f"- {e['path']}: {e['message']} (code: {e['code']})"
        for e in errors
    ])

    prompt = f"""
    The document JSON failed validation with these errors:
    {error_context}

    Return corrected JSON that fixes all issues.
    Reference the schema at autype://schemas/document for valid structure.
    """

    # LLM call to fix the document
    corrected = await call_llm(prompt)
    return json.loads(corrected)
Enter fullscreen mode Exit fullscreen mode

This pattern creates a self-healing loop where validation errors become context for the LLM to apply fixes.

Practical Integration with Claude Code

If you're using Claude Code (the CLI), connecting to Autype is straightforward:

# In your Claude Code config (claude_desktop_config.json or .claude/config.json)
{
  "mcpServers": {
    "autype": {
      "url": "https://mcp.autype.com/mcp/"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Once connected, Claude has access to all Autype tools. You can say things like:

  • "Create a quarterly report template with variables for company name, date, and revenue"
  • "Validate this document JSON before rendering"
  • "Add a two-column layout section to the existing document"
  • "Generate 50 personalized contracts from this template using the data in contracts.csv"

Claude reads the schemas, constructs valid JSON, validates, and renders.

Cost Considerations

Each operation has a credit cost. Understanding this helps you design efficient workflows:

Operation Credits
Validate JSON Free
Single render 5
Bulk render (per document) 4
AI document generation 80-100

Validation is free, so always validate before rendering. The bulk render discount (4 vs 5 credits) makes it more economical for batch operations.

Summary

Integrating LLM agents with document generation requires three things: a schema the LLM can read, validation that returns structured errors, and tools for iterative refinement. Autype's MCP server provides all three.

The key patterns:

  • Schema-first generation: LLM fetches schemas, generates valid JSON from the start
  • Validation loops: Free validation endpoint catches errors before credits are consumed
  • Builder sessions: Incremental document construction with mid-process corrections
  • Human feedback integration: MCP sessions persist state, enabling real-time refinement
  • Bulk scaling: Validated templates feed bulk render for personalized documents at scale

The combination of MCP tools and REST API gives you flexibility. Use MCP for interactive agent workflows, REST for programmatic automation. Both produce the same output formats: PDF, DOCX, ODT.

For production deployments, wrap the MCP calls in error recovery logic that feeds validation errors back to the LLM. This turns brittle document generation into a robust, self-correcting process.


Resources:

Top comments (0)