Skip to main content
Glama

markdown2pdf

Convert Markdown text into high-quality PDFs with LaTeX rendering. Include a title and optional date for professional document creation. Payments are processed via the Lightning Network, eliminating the need for sign-ups or credit cards.

Instructions

Convert markdown to PDF, and pay with Lightning

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
dateNoDocument date (YYYY-MM-DD)
text_bodyYesMarkdown text to convert
titleYesDocument title

Implementation Reference

  • Core handler function that implements the 'markdown2pdf' tool logic: validates input (text_body, title), sends markdown to external API, handles Lightning payment (402 status), polls for PDF generation, and returns the download URL or payment details.
    async function handleRequest(req: McpRequest): Promise<McpResponse> {
      if (!req.method) {
        return createErrorResponse(req.id, RPC_ERRORS.INVALID_REQUEST);
      }
    
      if (req.method !== 'markdown2pdf') {
        return createErrorResponse(req.id, RPC_ERRORS.METHOD_NOT_FOUND);
      }
    
      const { text_body, title } = req.params ?? {};
      if (!text_body || !title) {
        return createErrorResponse(req.id, {
          code: RPC_ERRORS.INVALID_PARAMS.code,
          message: "Invalid params: text_body and title are required"
        });
      }
    
      const today = req.params?.date || new Date().toISOString().split('T')[0];
      const payload = {
        data: {
          text_body,
          meta: { title, date: today }
        },
        options: { document_name: title }
      };
    
      let response;
      let pollUrl = 'https://api.markdown2pdf.ai/markdown';
      let headers = { 'Content-Type': 'application/json' };
    
      // Payment loop
      while (true) {
        try {
          response = await fetch(pollUrl, {
            method: 'POST',
            headers,
            body: JSON.stringify(payload)
          });
          
          if (response.status === 402) {
            const resJson = await response.json();
    
            const payment_request_url = resJson.payment_request_url
            const payment_context_token = resJson.payment_context_token;
            const offers = resJson.offers || [];
            const offer_id = offers.length > 0 ? offers[0].id : null;
            const offer_amount = offers.length > 0 ? offers[0].amount : null;
            const offer_currency = offers.length > 0 ? offers[0].currency : null;
            const description = offers.length > 0 ? offers[0].description : "Markdown to PDF conversion";
            
            const payment_request_payload = {
              payment_context_token: payment_context_token,
              offer_id: offer_id
            }
            
            let payment_request_response = await fetch(payment_request_url, {
              method: 'POST',
              headers,
              body: JSON.stringify(payment_request_payload)
            });
    
            const payment_request_json = await payment_request_response.json();
            debug("Payment request response: `${payment_request_json}`");
            debug("Payment request status: `${payment_request_response.status}`");
            debug("Payment request headers: `${payment_request_response.status}`");
            debug("Payment request URL: `${payment_request_url}`");
            debug("Payment request payload: `${payment_request_payload}`");
            const lightning_invoice = payment_request_json.payment_request.payment_request;
            const lightning_invoice_qr = payment_request_json.payment_request.payment_qr_svg;
    
            return createResponse(req.id, {
              content: [{
                type: "text",
                text: JSON.stringify({
                  status: "Payment required. Please pay the invoice and try the same request again to continue.",
                  qr_svg_url: lightning_invoice_qr,
                  payment_request: lightning_invoice,
                  detail: description
                })
              }]
              
            });
          } else if (response.status === 200) {
            const resJson = await response.json();
            if (resJson.path) {
              pollUrl = resJson.path;
              break;
            }
          } else {
            throw new Error(`Unexpected response: ${response.status}`);
          }
        } catch (err) {
          return createErrorResponse(req.id, {
            code: RPC_ERRORS.INTERNAL_ERROR.code,
            message: `Request failed: ${err instanceof Error ? err.message : String(err)}`
          });
        }
      }
    
      // Poll status
      while (true) {
        try {
          response = await fetch(pollUrl, { method: 'GET', headers });
          const resJson = await response.json();
          if (resJson.status === 'Done' && resJson.path) {
            pollUrl = resJson.path;
            break;
          }
          await new Promise(res => setTimeout(res, 3000));
        } catch (err) {
          return createErrorResponse(req.id, {
            code: RPC_ERRORS.INTERNAL_ERROR.code,
            message: `Polling failed: ${err instanceof Error ? err.message : String(err)}`
          });
        }
      }
    
      // Fetch final output
      try {
        response = await fetch(pollUrl, { method: 'GET', headers });
        const resJson = await response.json();
        if (resJson.url) {
          return createResponse(req.id, {
            content: [{
              type: "text",
              text: JSON.stringify({
                status: "complete",
                url: resJson.url
              })
            }]
          });
        } else {
          return createErrorResponse(req.id, {
            code: RPC_ERRORS.INTERNAL_ERROR.code,
            message: "PDF URL not found in response"
          });
        }
      } catch (err) {
        return createErrorResponse(req.id, {
          code: RPC_ERRORS.INTERNAL_ERROR.code,
          message: `Failed to fetch PDF: ${err instanceof Error ? err.message : String(err)}`
        });
      }
    }
  • Input schema for the 'markdown2pdf' tool defining required parameters text_body (markdown content) and title, with optional date.
    inputSchema: {
      type: 'object',
      required: ['text_body', 'title'],
      properties: {
        text_body: {
          type: 'string',
          description: 'Markdown text to convert'
        },
        title: {
          type: 'string',
          description: 'Document title'
        },
        date: {
          type: 'string',
          description: 'Document date (YYYY-MM-DD)'
        }
      }
    }
  • MCP 'tools/list' handler that registers the 'markdown2pdf' tool with its name, description, and input schema.
    if (req.method === 'tools/list') {
      process.stdout.write(JSON.stringify(createResponse(req.id, {
        tools: [{
          name: 'markdown2pdf',
          description: 'Convert markdown to PDF, and pay with Lightning',
          inputSchema: {
            type: 'object',
            required: ['text_body', 'title'],
            properties: {
              text_body: {
                type: 'string',
                description: 'Markdown text to convert'
              },
              title: {
                type: 'string',
                description: 'Document title'
              },
              date: {
                type: 'string',
                description: 'Document date (YYYY-MM-DD)'
              }
            }
          }
        }]
      })) + '\n');
      continue;
    }
  • MCP 'tools/call' handler that validates the tool name 'markdown2pdf' and forwards the call to the core handleRequest function.
    if (req.method === 'tools/call') {
      if (!req.params?.name || req.params.name !== 'markdown2pdf') {
        process.stdout.write(JSON.stringify(createErrorResponse(req.id, {
          code: RPC_ERRORS.INVALID_PARAMS.code,
          message: "Invalid tool name"
        })) + '\n');
        continue;
      }
    
      // Forward the parameters to our existing handler
      const toolRequest: McpRequest = {
        jsonrpc: "2.0",
        method: 'markdown2pdf',
        params: req.params.arguments,
        id: req.id
      };
      const result = await handleRequest(toolRequest);
    
      process.stdout.write(JSON.stringify(result) + '\n');
      continue;
    }
  • Server information in 'initialize' response identifies the server as 'markdown2pdf'.
        name: "markdown2pdf",
        version: "0.1.0"
      }
    });
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/Serendipity-AI/markdown2pdf-mcp'

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