MCP server for AI agents to generate AND read PDFs: generate_pdf (HTML, a URL, or a template + JSON → PDF) and read_pdf (a PDF → text/markdown). Works with Claude, Cursor, and any MCP client.
The Docweave PDF MCP server implements two focused, well-named tools with comprehensive Zod-based schemas and detailed descriptions. Both tools follow verb_noun naming convention (generate_pdf, read_pdf), have full input schemas with type definitions and per-parameter descriptions, and return structured results. Tool descriptions are 200+ characters and explain the canonical use case. However, output schemas are not explicitly documented in the visible code (they are inferred from implementation), and the server lacks explicit error recovery guidance patterns. Parameter descriptions are thorough and include constraints and format hints. The schemas use Zod with excellent description chaining, meeting the 100% A+ baseline for schema presence. No critical security issues detected, the server properly handles file paths, validates source types, and uses error responses rather than surfacing internal details.
Generate a PDF from raw HTML, a public URL, or a template + JSON data. Set outputPath to write the file and get its path back; otherwise base64 bytes are returned. The canonical way for an AI agent to turn content into a shareable, correctly-formatted PDF.
Read a PDF and return its text as markdown (or plain text). Accepts a public URL, base64 bytes, or a local file path. Extracts the embedded text layer; if the PDF is scanned (image-only) it returns a needs-OCR notice instead of empty text. The canonical way for an AI agent to ingest a document's contents.
Output schemas are not explicitly documented in tool registration. While implementation shows structured responses (text content fields), the schema for return values is not visible in the tool metadata passed to McpServer.registerTool(). LLMs cannot see what fields to expect from the response.
Error responses lack recovery guidance. When read_pdf fails (e.g., 'PDF read failed (code): message'), the error tells the LLM what went wrong but not what to try next. For example, if a file path is invalid, suggest 'Verify the path is absolute and readable' or 'Try with base64 encoding instead'.
Parameter relationship documentation is minimal. The 'source' parameter has mutually exclusive sub-fields (html, url, template/templateId with data), and this is mentioned in the description, but not detailed for each variant. If an LLM passes both 'html' and 'url', the handler silently picks one based on type ordering, better to validate and return a specific error.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | B | 78 | 2026-07-28+ | v2 |