@sheetrender/mcp
OfficialThis server renders spreadsheet rows into PDFs with SheetRender — from your own HTML, from saved templates, or as background batch jobs — and manages the templates and datasets behind them.
Render one PDF from HTML you write (
render_pdf): pass a full HTML document with inline CSS plus optional Jinjadatavariables and page settings; get back a saved file path, size, and an inline PDF under 512 KB.Render one PDF from a saved template (
render_template): fill an existing design with a single row of values, optionally overriding page setup for that render only.Find templates (
list_templates): no arguments; returns each saved template's name, id, and last-updated date so a mentioned name can be mapped to an id.Design a new template (
design_template/get_design): supply data (dataset id, inline rows, or a CSV/XLSX file) plus an example or written brief/style, and wait up to 3 minutes for a generated design with preview URL; then poll bydesign_id.Create datasets from JSON rows (
create_dataset): up to 50,000 rows / 500,000 cells, scalars only, attached to a template's project; returns the dataset id and the sanitized column keys.Upload spreadsheets as datasets (
upload_dataset): same result from a local.csvor.xlsxfile (up to 20 MB) on the machine running the server.List existing datasets (
list_datasets): ids, row counts, and column keys for a template's project, newest first.Run batch jobs (
create_batch_job): render one PDF per dataset row in the background, optionally naming files withfilename_templateor merging rows per value withgroup_by(both persist as new defaults).Track and collect batch output (
get_jobthenget_document): poll status, rows done/failed, and document ids once finished, then download individual PDFs.
Notes: only rendering consumes plan volume (datasets and uploads are free); free-plan PDFs carry a "Made with SheetRender" footer; requires a SheetRender API key, and the public API rate-limits at 120 requests/minute per key.
Renders sheet rows and inline data into HTML templates written with Jinja syntax: each key of the supplied data object becomes a Jinja variable (e.g. {{ total }}), and Jinja placeholders, loops and conditionals are evaluated when producing a single PDF (render_pdf, render_template) or one PDF per row in a batch job.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@@sheetrender/mcpRender this HTML to PDF: Test"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@sheetrender/mcp
MCP server for SheetRender: turn rows of a spreadsheet into one PDF each, such as certificates, letters, donation receipts and offer letters.
The quickest start needs no account and no install. Add the hosted server
https://mcp.sheetrender.com/mcp as a connector in ChatGPT, Claude or any
client that takes a Streamable HTTP URL, then paste or describe your rows. It
fills SheetRender's built-in templates and returns a preview and a PDF per row
(details).
With a SheetRender API key you get the full tool set: your own saved templates, HTML templates, datasets and batch jobs, either through the npm package below or the same hosted URL.
Setup
Get an API key from sheetrender.com under Settings → API keys, then add this to your MCP client config:
{
"mcpServers": {
"sheetrender": {
"command": "npx",
"args": ["-y", "@sheetrender/mcp"],
"env": { "SHEETRENDER_API_KEY": "sr_live_..." }
}
}
}SHEETRENDER_API_URL is also read, and defaults to https://sheetrender.com.
Set it only if you're pointing at a self-hosted or staging instance.
Related MCP server: rendoc
Hosted endpoint
The same server runs at https://mcp.sheetrender.com/mcp over Streamable
HTTP, so clients that can't spawn a local process can use it too. Nothing is
installed; each request carries your API key:
Authorization: Bearer sr_live_...Requests without that header get the smaller no-key tool set described below. The key goes straight through to the SheetRender API for that one request and is never stored — the server keeps no sessions, so every request stands alone.
Where the key goes depends on the client:
Claude Code:
claude mcp add --transport http sheetrender https://mcp.sheetrender.com/mcp --header "Authorization: Bearer sr_live_..."Cursor, Windsurf, VS Code and other clients with an
mcp.json:{ "mcpServers": { "sheetrender": { "url": "https://mcp.sheetrender.com/mcp", "headers": { "Authorization": "Bearer sr_live_..." } } } }Claude API (the Messages API's MCP connector): add
{"type": "url", "url": "https://mcp.sheetrender.com/mcp", "name": "sheetrender", "authorization_token": "sr_live_..."}tomcp_servers.claude.ai, Claude Desktop and ChatGPT custom connectors take a server URL and an OAuth client, not a static header. Until the endpoint speaks OAuth, use the stdio package above there — it's the same tools, with the key in
env— or bridge withnpx mcp-remote https://mcp.sheetrender.com/mcp --header "Authorization: Bearer sr_live_..."as the command.
Two differences from the stdio server follow from the process not running on
your machine. Rendered PDFs come back inline as a base64 resource (up to 8 MB;
larger ones are reported with their size and left for the web app) instead of
as a temp-file path, and upload_dataset is not offered because there is no
local file to read — send rows with create_dataset instead.
GET /healthz answers 200 without credentials. Request bodies are capped at
25 MB; anything larger is a 413.
A key that merely starts with sr_ proves nothing, so the server waits for
the SheetRender API to accept it. Until a call made with the key has
succeeded (normally the first tool call, e.g. list_templates), its requests
get the same bounds as requests without a key, described below: bodies up to
2 MB, at most 4 JSON-RPC messages per batch, and every message counted toward
the per-network budgets. Once the API has accepted the key, the server
remembers a hash of it (never the key) for an hour after its latest
successful call, and its requests get the 25 MB body cap and batches of up to
20 messages. MCP clients send one message per request, so the batch caps only
matter to hand-written clients. If the very first call is a body over 2 MB
(a large create_dataset), it gets a 413 that says to make a small call
first and retry. Every request, with a key or without, counts toward the
server-wide in-flight cap.
Bodies over 2 MB also take one of a few server-wide large-body slots, 1 by
default (LARGE_BODY_MAX_IN_FLIGHT). A slot is taken before reading when
Content-Length declares such a body, or as soon as a streamed body passes
2 MB, and is held until the response ends. When all are taken, the request
gets a 503 with Retry-After: 5 and the rest of its body is not read. One 25 MB
body can take around 100 MB of heap while it is decoded, parsed and passed
on, so raise this only with the heap (the hosted container runs a 192 MB
heap).
Without a key (ChatGPT and Claude directory listings)
When the server runs with SHEETRENDER_DEMO_API_KEY set, a request that
sends no Authorization header gets a different, smaller tool set
instead of a 401. These tools fill SheetRender's built-in templates
(certificate of completion, letter, donation receipt, job offer letter) from
rows in the chat, through a dedicated rendering account:
list_document_templates: the templates, their fields (key, label, required, example, character and line limits).render_documents: up to 25 rows per call, one PDF per row; returns a PNG preview and a PDF link per document (both expire after an hour), the rows that missed a required field, and how many of the month's 50 documents per user remain. When a limit shared with other users is what cut or refused the call, it says the limit is shared instead of showing a count. A value over its field's character or line limit is refused before the API is called, and the API's own reason for refusing rows (row number, field and rule, never the value) is passed on.create_continue_link: up to 100 rows, stored for 7 days, and a link to the template's page on the website with those rows loaded. Called only when the user wants to continue there.
render_documents comes with an MCP Apps
view, ui://sheetrender/documents.html (text/html;profile=mcp-app): a strip
of previews with PDF links and a "Continue in SheetRender with these rows"
button. Its script, src/widget/documents.ts, is bundled by esbuild into
dist/widget/documents.js during deno task build and inlined into the page
when the resource is read.
Calls are counted per user: ChatGPT's anonymised openai/subject, else the
client IP. Any caller can send openai/subject, so it is believed only from
OpenAI's published egress ranges (chatgpt-connectors.json,
overridable with OPENAI_EGRESS_CIDRS); from anywhere else the call is
counted by IP. The hosted server fetches that list at startup and every 24
hours, keeping the built-in copy (then the last good fetch) whenever a fetch
fails or returns anything malformed or empty; it logs one line per refresh. It
also logs, at most once an hour, how many calls sent an openai/subject from
outside the list, so a stale list shows up.
The server allows 30 render or continue calls per subject or IP per hour
(ANON_CALLS_PER_HOUR). Callers counted by IP also share a second bucket per
IPv4 /24 or IPv6 /48, 300 calls an hour (ANON_NETWORK_CALLS_PER_HOUR), so a
block of cheap addresses counts as one caller. All traffic from Claude's
160.79.104.0/21 network (Anthropic's published outbound range, which the
Claude API's MCP connector also uses) shares a separate 3,000-call hourly
bucket (CLAUDE_CALLS_PER_HOUR); each Claude conversation that sends its
Mcp-Session-Id also gets the 30-call per-subject bucket inside it. Neither
User-Agent text nor _meta can claim or leave these buckets.
The SheetRender API still applies the monthly document volume against the
hashed subject or IP. The subject and IP are sent to the API only as SHA-256
hashes, and the request log carries a fingerprint, the detected client (chatgpt,
claude or other) and the row count, never the rows.
Anonymous HTTP bodies are capped at 2 MB, enough for 25 rows at every
field's limit; a continue link's rows are capped at 256 KB. A private socket peer is
treated as a proxy and only the final valid IP in X-Forwarded-For is
trusted. Keep this listener behind Caddy, with no publicly exposed container
port, as in the deployment's Compose topology.
Anonymous requests have three more bounds, so a small request cannot make
the server generate a lot of output. A JSON-RPC batch may carry at most 4
messages (MCP dropped batching in protocol version 2025-06-18, and ChatGPT
and Claude send one message per request); a larger one gets a 400 before
anything in it runs. Messages other than tool calls (initialize,
tools/list, resources/read, ...) count 600 an hour per IPv4 /24 or IPv6
/48 (ANON_RPC_PER_HOUR); traffic from OpenAI's and Claude's egress ranges
is not counted there, since each of those addresses carries many users. And
at most 64 requests, with a key or without, are answered at once (ANON_MAX_IN_FLIGHT),
8 per network outside those ranges (ANON_NETWORK_MAX_IN_FLIGHT); past
either, the answer is a 503 or 429 with Retry-After: 5. Requests with an
API key the SheetRender API has not yet accepted get all three bounds, with
tool calls counted toward the 600 as well; once the key is accepted they
count only toward the overall in-flight cap, and their batches may carry 20
messages.
A request carrying a SheetRender bearer key uses the API-key tools above.
A malformed Authorization header is rejected. The stdio server never
offers the anonymous tools.
Running it yourself
sheetrender-mcp-http is a second bin in the package. It reads PORT
(default 8080), HOST (default 0.0.0.0), SHEETRENDER_API_URL,
MAX_BODY_BYTES and IDLE_TIMEOUT_MS (default 60000), and logs one JSON
line per request to stdout — method, path, status, duration, the JSON-RPC
method and tool name, a fingerprint of the key (never the key), and
key_verified, whether the API had accepted that key yet.
For the anonymous tools, and the request bounds above (which also apply to keyed requests when no demo key is set), it also reads:
Variable | |
| The dedicated rendering account's |
| The URL users paste, byte for byte, e.g. |
| OpenAI's domain-verification token, served as plain text at |
| Render and continue calls per subject or IP per hour, default 30. |
| Render and continue calls per hour per IPv4 /24 or IPv6 /48, for callers counted by IP, default 300. |
| Shared render and continue calls per hour for all traffic from |
| Anonymous messages other than tool calls, plus every message sent with a key the API has not yet accepted, per hour per IPv4 /24 or IPv6 /48, outside the OpenAI and Claude ranges, default 600. |
| Requests answered at once, all callers together (with a key or without), default 64. |
| Anonymous requests, and requests with a key the API has not yet accepted, answered at once per IPv4 /24 or IPv6 /48, outside the OpenAI and Claude ranges, default 8. |
| Requests with a body over 2 MB handled at once, server-wide, default 1. Only keys the API has accepted can send such a body. |
| Comma- or space-separated CIDRs whose |
With SHEETRENDER_DEMO_API_KEY set, the server refuses to start if the view
bundle (dist/widget/documents.js) is missing. The Dockerfile in this repo
builds a non-root runtime image for it:
docker build -t sheetrender-mcp .
docker run --rm -p 8080:8080 sheetrender-mcpTools
design_template: Design from exactly one ofdataset_id, inlinerows,data_base64plusdata_filename, or stdio-onlydata_path, and an example, brief or style. Data files must be CSV or XLSX, up to 10 MB. Waits up to 3 minutes and returns status, template details and an authenticated preview URL. Examples useexample_base64plusexample_filename, or stdio-onlyexample_path.get_design: Poll adesign_idfor its status, template id, name, column mapping and preview URL.
render_pdf
Renders one PDF from HTML you supply.
Argument | Type | |
| string | Required. A full HTML document. CSS has to be inline in a |
| object | Optional. Keys become Jinja variables, so |
| object | Optional, see below. |
Returns the path of the saved PDF and its size. Under 512 KB it's also attached inline as a base64 resource, so clients that display attachments show the document itself.
HTML over 2 MB is rejected, and that's measured both on
what you send and on the document after data is substituted in, so a template
that expands a long dataset can cross the line even when the markup you wrote
doesn't. A "Made with SheetRender" footer is added when required by the
rendering account's settings. That applies to render_pdf and
render_template alike.
list_templates
No arguments. Returns each saved template's name, id and last-updated date. Call it to turn a template name the user mentioned into the id the other tools want.
render_template
Renders one PDF from a template already saved in the account.
Argument | Type | |
| string | Required, from |
| object | Required. One row's values, as Jinja variables. |
| object | Optional. Omit it to keep the template's own saved page setup; passing it overrides that for this render. |
Same return as render_pdf.
create_dataset
Turns JSON rows into a dataset a batch job can render. This is the usual way to
start a batch: assemble the rows, send them, get back a dataset_id.
Argument | Type | |
| string | Required, from |
| array of objects | Required. One flat object per document. |
| string | Optional label, used as the stored filename. |
The header is the union of every row's keys in first-seen order, so rows don't have to agree on their keys — a missing one is a blank cell rather than a shifted row. Values have to be scalars: strings, numbers, booleans or null. Nested objects and arrays are rejected, and so are NaN, Infinity and whole numbers past 2^53 (send those as strings to keep them exact). The caps are 50,000 rows and 500,000 cells per call.
Returns the dataset id, row count and, for each column, the sanitized key.
That key is what template placeholders, filename_template and group_by
address, and it's often not the header verbatim — Invoice No becomes
invoice_no. Read it off this result instead of guessing.
Creating a dataset does not consume document volume; only rendering does.
upload_dataset
The same thing from a file that already exists.
Argument | Type | |
| string | Required, from |
| string | Required. A |
The first row has to be the header. Files over 20 MB, the wrong extension and
empty files are refused locally, before anything is uploaded. Same return as
create_dataset.
list_datasets
Takes template_id and lists every dataset in that template's project, newest
first, with ids, row counts and column keys. Use it to find data the user
already loaded, or to read a dataset's column keys before writing a
filename_template or picking group_by.
create_batch_job
Queues a background job that renders one PDF per row of a dataset. The whole
loop runs from here — list_templates → create_dataset or upload_dataset →
create_batch_job → get_job → get_document.
Argument | Type | |
| string | Required, from |
| string | Required, from |
| string | Optional. Output naming pattern, e.g. |
| string | Optional. Column key to group rows by, giving one multi-page PDF per distinct value. |
Returns the job id to poll with get_job. Worth knowing: filename_template
and group_by are persisted to the template and the project respectively, so
they change the defaults for later runs too.
If the server predates the public batch endpoint, the tool reports that batch jobs are unavailable rather than failing obscurely. The three dataset tools do the same for a server that predates the dataset endpoints.
get_job
Takes job_id. Returns the status, rows done and failed, and the id and
filename of every rendered document. That document list stays empty while the
job is queued, retry_queued or running, and fills in once the job reaches
succeeded, partial, failed or cancelled. Those document ids are what
get_document takes.
get_document
Takes document_id and downloads that single rendered PDF. The ids come from
get_job on a finished batch, and there's no other way to get one. Same return
as render_pdf: path, size, and an inline blob under 512 KB.
If you want a whole batch, the merged PDF and ZIP in the web app beat fetching each document in turn.
page_settings
Shared by both render tools. Every field is optional:
{
"page_size": "a4",
"orientation": "portrait",
"margins": { "top": 15, "right": 15, "bottom": 15, "left": 15 }
}page_size is lowercase: a3, a4, a5, letter, legal or tabloid.
Margins are plain numbers in millimetres, not CSS lengths.
Rendered PDFs are written to the system temp directory. API errors like a bad key, a missing template or a rate limit come back as tool errors carrying the server's own message.
The public API allows 120 requests per minute per API key. Past that it returns 429, and the tool reports that you're rate limited and should retry shortly. Batch job creation is metered separately and more tightly.
Development
You don't need node or npm on the host: scripts/dev.sh install, then
scripts/dev.sh deno task build and scripts/dev.sh deno task test.
scripts/dev.sh node dist/http.js runs the HTTP server on the host network.
MIT licensed.
Available Tools
11 toolscreate_batch_jobStart a batch PDF jobAInspect
SheetRender turns HTML templates plus spreadsheet rows into rendered PDFs. This tool queues a batch job that renders one PDF per row of a dataset, and returns the job id.
Use it when the user wants many documents at once — "an invoice for every row", "one letter per employee" — rather than calling render_template in a loop.
The full sequence, all of it available here:
list_templates — the user's designs, and the template_id for the rest.
create_dataset (rows you hold as JSON) or upload_dataset (a local .csv/.xlsx) — returns the dataset_id. list_datasets finds one that already exists.
create_batch_job — this tool, returning a job id.
get_job — poll until the status is finished; it then lists the document ids.
get_document — download any of those PDFs.
The dataset must belong to the same template's project, which is where create_dataset or upload_dataset put it. Rendering happens in the background, so the job id comes back long before the PDFs do.
filename_template and group_by name columns by their sanitized key, which create_dataset, upload_dataset and list_datasets all report — it is often not the header text verbatim ("Invoice No" becomes invoice_no). Both are saved onto the template/project, so they change the defaults for later runs, not just this one. Only pass them when the user asked to change how output is named or grouped.
| Name | Required | Description | Default |
|---|---|---|---|
| group_by | No | Column name to group rows by, producing one multi-page PDF per distinct value instead of one per row. Saved to the project. | |
| dataset_id | Yes | Dataset id from create_dataset, upload_dataset or list_datasets. It must belong to the same template's project. | |
| template_id | Yes | Template id from list_templates. | |
| filename_template | No | Naming pattern for output files, with column placeholders, e.g. "invoice-{{ invoice_no }}". Saved to the template. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and meets it: it discloses that the dataset must belong to the same template's project, that rendering is asynchronous so the job id returns before the PDFs exist, and — critically for a mutation-style side effect — that filename_template and group_by are 'saved onto the template/project, so they change the defaults for later runs, not just this one.' That persistence warning is exactly the kind of hidden consequence annotations would otherwise supply.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose and the sibling contrast are front-loaded, and the numbered workflow is scannable. It is on the long side and the 'SheetRender turns HTML templates plus spreadsheet rows into rendered PDFs' framing sentence is partly scene-setting, but the remaining sentences each carry routing or constraint information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite no output schema, the description tells the agent what comes back (a job id), how to get the results (get_job polling, get_document downloads), and what preconditions must hold (dataset in the same project). For a 4-param tool with no annotations, nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description goes beyond it by explaining that column names must be sanitized keys ('Invoice No' becomes invoice_no) reported by create_dataset/upload_dataset/list_datasets, and that these two params persist as project defaults — details found only in prose, not in the schema strings.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening states a specific verb and resource: 'queues a batch job that renders one PDF per row of a dataset, and returns the job id.' It explicitly contrasts with the sibling render_template ('rather than calling render_template in a loop'), so an agent can distinguish batch from single-render without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives a concrete usage condition ('when the user wants many documents at once — an invoice for every row, one letter per employee'), names the alternative to avoid, and lays out the full 5-step workflow with which sibling produces each required id. It also states when NOT to pass the optional params: 'Only pass them when the user asked to change how output is named or grouped.'
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_datasetCreate a dataset from JSON rowsAInspect
SheetRender turns HTML templates plus spreadsheet rows into rendered PDFs. This tool turns rows you already hold — as JSON — into a dataset a batch job can render, and returns the dataset id plus the column keys.
This is the normal way to start a batch: the user asks for "an invoice for each of these clients" or "a letter per employee", you assemble the rows, and this uploads them. Use upload_dataset instead when the data is already a file on disk, and list_datasets when the user is referring to a dataset that already exists.
rows is a flat array of flat objects, one per document: [{"client": "Acme", "total": 42}, {"client": "Globex", "total": 17}]. The header is the union of every row's keys in first-seen order, so rows need not agree on their keys — a missing one is a blank cell, not a shifted row. Values must be strings, numbers, booleans or null; nested objects and arrays are rejected, so flatten or stringify them first. So are NaN, Infinity and whole numbers past 2^53 (send those as strings to keep them exact).
The dataset is attached to the template's project, which means every template in that project can render it and it stays available to later jobs.
Limits: 50,000 rows and 500,000 cells (rows x columns) per call. The row cap applies to JSON rows only — a bigger sheet can still go through upload_dataset as a file, which is bounded by size and cells rather than rows. Creating a dataset is free; only rendering counts against the account's plan.
Returns the dataset id and, for each column, the sanitized key. That key — not the original header — is what the template's placeholders, filename_template and group_by address, so read it off this result rather than guessing from the header text.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Optional label for the dataset, used as its stored filename so the user recognises it later, e.g. "march-invoices". | |
| rows | Yes | One flat object per document. Keys become spreadsheet columns; values must be scalars (string, number, boolean or null). | |
| template_id | Yes | Template id from list_templates. The dataset lands in that template's project. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden and does so richly: it discloses the flat-array/flat-object shape requirement, union-of-keys header semantics with missing keys becoming blanks, scalar-only value rules, rejection of NaN/Infinity/values past 2^53, the 50,000-row and 500,000-cell caps, that creation is free (only rendering is billed), and that the dataset is attached to the template's project and reusable by later jobs.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, then constraints, then limits, then return semantics in a logical order. It is on the long side and the opening 'SheetRender turns HTML templates...' line is arguably ambient context, but every paragraph earns its place by resolving a real ambiguity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, and the description compensates by stating what is returned (dataset id plus per-column sanitized keys) and warning that the sanitized key is what the template must reference. Combined with the limits, cost model, and placement rules, an agent has everything needed to call this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description goes beyond the schema by explaining the union-of-keys header derivation, the exact-number caveat (send large integers as strings), and that the returned sanitized `key` — not the original header — is what placeholders, filename_template and group_by address. That is meaningful semantics the schema does not carry.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('turns rows ... into a dataset') and immediately distinguishes itself from siblings by naming upload_dataset and list_datasets with the conditions that select them. An agent can tell exactly what this does without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use ('the normal way to start a batch', with the user-intent example 'an invoice for each of these clients'), plus explicit when-not with named alternatives: upload_dataset for data already on disk, list_datasets for an existing dataset. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
design_templateDesign a templateAInspect
Design a saved template from exactly one of dataset_id, inline rows, or data_base64 with data_filename (CSV or XLSX, up to 10 MB), and an example, brief, or style_id. Use an example alone, or a brief with optional style_id. Examples accept PDF, PNG, JPG, WebP or DOCX as base64 with a filename. Local data_path can supply the data; example_path can supply the example. Waits up to 3 minutes; returns the design status, template id, mapping and preview URL.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Template name. | |
| rows | No | Flat data rows; use exactly one data source. | |
| brief | No | Written design instructions. | |
| style_id | No | Saved style id. | |
| data_path | No | Local CSV or XLSX file path; at most 10 MB; ~ is expanded. | |
| dataset_id | No | Existing dataset id; use exactly one data source. | |
| data_base64 | No | Base64 CSV or XLSX contents; at most 10 MB decoded. | |
| example_path | No | Local example file path; ~ is expanded. | |
| fit_one_page | No | Fit the design onto one page. | |
| data_filename | No | Data filename including .csv or .xlsx extension. | |
| example_base64 | No | Base64 example file contents. | |
| example_filename | No | Example filename including its extension. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations the description carries the full load, and it delivers real behavioral detail: a 3-minute wait, 10 MB size cap, accepted formats (CSV/XLSX for data; PDF/PNG/JPG/WebP/DOCX for examples), and the returned fields (status, template id, mapping, preview URL). It does not mention permission/auth requirements, nor explicitly flag that this persists a new artifact, which is the main gap for a mutation tool with zero annotation coverage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Key constraints (the exclusive-or data source and the example/brief rule) are front-loaded, and every sentence carries a constraint, limit, or return value. It is dense to the point of being run-on in places, but there is little wasted text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 12-parameter tool with no annotations and no output schema, the description is unusually complete: it covers input combinations, file types, size limits, latency, and enumerates the return values. Remaining gaps are auth/permission expectations and the meaning of the returned 'design status'.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3, but the description adds semantics the schema does not: the exclusive-or relationship among data sources and the 'example alone' vs 'brief with optional style_id' rule. data_path/example_path are also framed as local-file alternatives to their base64 counterparts, which the schema treats as independent fields.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ('Design a saved template') and immediately scopes exactly what inputs produce that template, so an agent knows this creates a persisted template rather than rendering one. It never names or distinguishes itself from nearby siblings such as render_template or get_design, so sibling differentiation is left to inference.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit composition rules: data must come from exactly one of dataset_id / inline rows / data_base64+data_filename, and the design source is 'an example alone, or a brief with optional style_id.' That is clear when-to-use guidance for the parameter combinations. It stops short of saying when NOT to use this tool or when to prefer render_template instead.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_designCheck a template designAInspect
Get a design's status and, when complete, its template id, name, mapping and preview URL.
| Name | Required | Description | Default |
|---|---|---|---|
| design_id | Yes | Design id returned by design_template. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It usefully discloses conditional return behavior — that template id, name, mapping and preview URL only appear once the design is complete — but says nothing about the not-yet-complete case (pending status, errors), auth requirements, or rate limits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence that leads with the primary output (status) and then qualifies the conditional extras. No filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema and no annotations, so the description is the only source of return-shape information. It names the returned fields but does not enumerate possible status values or explain the incomplete-state response, leaving a meaningful gap for a status tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the schema already states design_id is 'Design id returned by design_template.' The description adds no further meaning about the id's format or provenance, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb (Get) and resource (a design), and even enumerates the payload it returns (status, template id, name, mapping, preview URL). It does not distinguish itself from siblings like design_template or get_job, so an agent must infer that design_template is the producer and this is the status poller.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'when complete' implies this is a polling/status-check tool rather than a fetch of a finished artifact, which gives implied usage. However, it never states when to call it versus design_template, render_template, or get_job, and gives no polling cadence or terminal-condition guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_documentDownload a rendered documentAInspect
SheetRender turns HTML templates plus spreadsheet rows into rendered PDFs. This tool downloads one PDF produced by a batch job and returns the path to the saved file.
document_id comes from get_job on a finished batch — that is the only place these ids appear, so call get_job first and take an id from its document list. A document id is not a template id or a job id.
Use it to fetch a specific output the user asked about, or to spot-check a batch. Fetching every document of a large batch one at a time is slow; point the user at the SheetRender web app for the merged PDF or ZIP instead.
Returns the temp-file path and size; PDFs under 512 KB are also attached inline.
| Name | Required | Description | Default |
|---|---|---|---|
| document_id | Yes | Document id from a finished job's document list in get_job. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the behavioral burden. It discloses that the return includes a temp-file path and size, that PDFs under 512 KB are attached inline, and warns about performance when fetching many documents. It also notes the provenance of document_id. While it doesn't mention error handling or permissions, it covers the essential behavior for a download tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured: the first sentence states the core purpose, followed by id provenance, usage guidance, and return behavior. Every sentence adds value with no fluff, and the most critical information (spoiler: the id source) is front-loaded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple tool with one parameter and no output schema, the description covers all necessary aspects: what it does, how to obtain the id, when to use it vs alternatives, and what the return includes. Nothing essential is missing for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema already describes document_id with high coverage, but the description adds crucial disambiguation: it explains the id comes specifically from get_job's document list and clarifies it is not a template or job id. This goes beyond the schema's baseline, making parameter meaning clearer for the agent.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states the specific verb (download), resource (PDF from a batch job), and distinguishes it from siblings like render_pdf and get_job. It also clarifies that document_id comes from get_job, making the tool's role unambiguous.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use the tool (fetch a specific document or spot-check a batch) and when to instead direct the user to the SheetRender web app for bulk downloads. It also references get_job for obtaining valid IDs, providing clear usage context.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_jobCheck a batch PDF jobAInspect
SheetRender turns HTML templates plus spreadsheet rows into rendered PDFs. This tool reports the progress of a batch job started by create_batch_job.
Returns the status, rows done/failed, and — once the job reaches a finished state ("succeeded", "partial", "failed" or "cancelled") — the id and filename of every rendered document. The document list is empty while the job is still in "queued", "retry_queued" or "running", so poll again after a short wait rather than assuming zero documents.
Pass those document ids to get_document to download the individual PDFs.
| Name | Required | Description | Default |
|---|---|---|---|
| job_id | Yes | Job id returned by create_batch_job. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With zero annotations, the description carries the full behavioral burden and succeeds. It discloses the asynchronous polling nature, enumerates the complete state machine (queued, retry_queued, running, succeeded, partial, failed, cancelled), explains that the document list is empty during in-progress states, and specifies what becomes available once finished. This is exactly the behavioral context an agent needs to avoid misinterpreting empty results.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short paragraphs, zero filler. The purpose and polling caveat are front-loaded; the SheetRender context sentence and the get_document routing both earn their place. The state enumeration is necessary, not redundant, given the target states versus document-list-availability states must be distinguished.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite having no output schema and no annotations, the description fully compensates: it explains the returned fields (status, rows done/failed, document ids/filenames), the conditional availability of results, and the follow-up action. For a single-parameter polling tool, nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% — job_id is documented as 'Job id returned by create_batch_job.' The description reinforces but does not extend this: it references 'the id' of every rendered document but adds no new formatting or validation details beyond the schema. Baseline 3 is appropriate since the schema already carries the semantic weight.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb+resource: 'reports the progress of a batch job started by create_batch_job' — it monitors a job rather than producing content. It distinguishes itself from siblings by explaining the workflow position (reports on create_batch_job output, feeds get_document). The title 'Check a batch PDF job' reinforces the purpose without ambiguity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description names the prerequisite sibling (create_batch_job), the successor (get_document for downloading), and gives explicit call-timing guidance: poll again while status is in 'queued', 'retry_queued' or 'running' rather than treating an empty document list as zero results. This is concrete, actionable when-to-use guidance that maps the tool into a larger workflow.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_datasetsList datasets for a templateAInspect
SheetRender turns HTML templates plus spreadsheet rows into rendered PDFs. This tool lists the datasets a batch job can render with a given template — everything in that template's project, newest first — with each one's id, row count and column keys.
Call it when the user refers to data they have already loaded ("use the customer list I uploaded") so you can find its id, or to re-run a batch over an existing dataset instead of creating a duplicate. When there is nothing suitable, create one with create_dataset or upload_dataset.
It is also the quickest way to see a dataset's sanitized column keys before writing a filename_template or choosing group_by.
| Name | Required | Description | Default |
|---|---|---|---|
| template_id | Yes | Template id from list_templates. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full disclosure burden, and it delivers non-obvious behavior: results are project-wide rather than restricted to the template, ordered newest first, and include id, row count and column keys. It stops short of stating pagination/result limits or whether the operation is purely read-only, so the safety and volume profile is still partially inferred.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with what the tool is and what it returns before moving to usage and the column-key tip; every sentence carries information. It runs to three paragraphs where two would suffice, but nothing is redundant.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description must describe returns itself — it does so explicitly (id, row count, column keys) plus ordering and scope. For a single-parameter list tool, an agent has everything needed to call and interpret it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There is one parameter with 100% schema description coverage, so the schema already documents template_id and its provenance (from list_templates). The description adds domain context about what the id scopes but no format or syntax detail beyond the schema, making the baseline 3 appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb and resource ('lists the datasets a batch job can render with a given template') and pins down scope: everything in that template's project, newest first, with id, row count and column keys. That is enough to distinguish it from create_dataset, upload_dataset and list_templates without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit triggering condition ('when the user refers to data they have already loaded') plus a second concrete use case (re-running a batch over an existing dataset instead of duplicating). It also names the alternatives to use when nothing fits — create_dataset or upload_dataset — so routing decisions are fully determined.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_templatesList SheetRender templatesAInspect
SheetRender turns HTML templates plus spreadsheet rows into rendered PDFs. This tool lists the templates saved in the user's account, with the id each one needs.
Call it first whenever the user refers to a template by name ("render the invoice template") so you can map that name to an id — every other tool here takes a template_id, including create_dataset and list_datasets. Takes no arguments.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden; it discloses that the operation is a read ('lists'), is scoped to the user's account, and returns templates together with their ids. It is silent on ordering, result size/pagination, and account/permission requirements, which keeps it short of a 5 for an un-annotated tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Effectively front-loaded: the domain context comes first, then the action, then the routing guidance. The closing 'Takes no arguments' is mildly redundant against the empty schema, and the middle sentence is long, but no sentence is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given a no-arg, annotation-free, no-output-schema listing tool, the description supplies the essential context: what it returns, why it exists, and when to call it. Some return-value detail (ordering, size limits) is absent, which is the only real gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the schema baseline is 4; the description reinforces this with 'Takes no arguments.' No additional parameter meaning is needed or missing.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a precise verb+resource ('lists the templates saved in the user's account, with the id each one needs') and separates itself from siblings by noting that create_dataset and list_datasets take a template_id instead. The opening sentence even establishes the SheetRender domain so the agent understands what a template is here.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit trigger condition ('Call it first whenever the user refers to a template by name') plus the concrete alternative behavior it enables: mapping a name to an id that 'every other tool here takes'. This is a clear when-to-use rule with the tool's role in the workflow spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
render_pdfRender HTML to PDFAInspect
SheetRender turns HTML templates plus spreadsheet rows into rendered PDFs. This tool renders a single PDF from HTML you supply and returns the path to the saved file.
Use it for one-off documents — an invoice, a report, a certificate — where you are writing the markup yourself. Use render_template instead when the user already has a saved template.
html must be a complete HTML document (, , ) with all CSS inline in a tag: external stylesheets, fonts and scripts are not fetched. Use @page and mm/cm units for print layout.
data keys become Jinja template variables, so passing {"total": "42.00"} lets the HTML say {{ total }}. Jinja loops and conditionals work too. Omit data if the HTML has no placeholders.
Two server limits to plan for: HTML over 2 MB is rejected, measured both on what you send and on the result after data is substituted in, so keep large tables paginated rather than emitting one enormous document; and accounts on the free plan get a "Made with SheetRender" footer added to every PDF, which is expected, not a bug — mention it if the user seems surprised.
Returns the temp-file path and size; PDFs under 512 KB are also attached inline.
| Name | Required | Description | Default |
|---|---|---|---|
| data | No | Template variables as a flat JSON object. Each key becomes a Jinja variable, so {"customer": "Acme"} makes {{ customer }} available in the HTML. | |
| html | Yes | A complete HTML document with inline CSS. May contain Jinja placeholders filled from `data`. | |
| page_settings | No | Optional page setup for the PDF. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden. It transparently discloses that external resources are not fetched, that HTML over 2MB is rejected (including after substitution), that free accounts get a footer, and that return includes path/size plus inline attachment for small PDFs. This exceeds expectations for behavioral disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured with clear paragraphs. Each sentence earns its place: purpose, usage (with alternative), HTML requirements, data behavior, server limits, and return format. It is detailed but not repetitive; complexity is handled without bloat.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers all necessary aspects for correct use: input requirements, template variable mechanics, output behavior, error conditions (2MB limit), and an environmental quirk (free-plan footer). It is fully self-sufficient even without annotations or output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Despite 100% schema coverage, the description adds substantial meaning beyond the schema: it explains Jinja variable substitution with a concrete example, confirms loops/conditionals work, clarifies that data can be omitted, and details page_settings usage in plain terms. This significantly aids correct invocation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's function: rendering a single PDF from user-supplied HTML and returning the saved file path. It also distinguishes itself from render_template by explicitly saying when to use which, making it easy for an agent to select correctly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description provides explicit guidance on when to use this tool ('one-off documents... writing the markup yourself') and when to use the alternative (render_template when a saved template exists). It also warns about size limits and the free-plan footer, covering both usage context and potential pitfalls.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
render_templateRender a saved template to PDFAInspect
SheetRender turns HTML templates plus spreadsheet rows into rendered PDFs. This tool renders one PDF from a template already saved in the user's account and returns the path to the saved file.
Use it when the user wants a document in their existing design. Get template_id from list_templates. Use render_pdf instead when you are writing the HTML yourself.
data supplies one row's worth of values: each key becomes a Jinja variable in the template's HTML. To render a PDF for every row of a spreadsheet, load the rows with create_dataset or upload_dataset and run create_batch_job rather than calling this repeatedly.
Omit page_settings to keep the template's own saved page setup — passing it overrides that for this render only.
Free-plan accounts get a "Made with SheetRender" footer on the PDF, same as render_pdf — expected, not a bug.
Returns the temp-file path and size; PDFs under 512 KB are also attached inline.
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | Template variables as a flat JSON object. Each key becomes a Jinja variable, so {"customer": "Acme"} makes {{ customer }} available in the HTML. | |
| template_id | Yes | Template id from list_templates. | |
| page_settings | No | Optional page setup for the PDF. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so: it discloses the free-plan watermark ('expected, not a bug'), the return payload (temp-file path and size, inline attachment under 512 KB), and the override semantics of page_settings. These are behaviors an agent cannot infer from the schema and would otherwise be surprised by.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four short paragraphs, each doing distinct work: what it does, when to use it vs alternatives, what data means, and the page_settings override plus return/watermark notes. Front-loaded with the core purpose and no filler sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with three parameters, nested objects, and no output schema, the description covers selection, sibling routing, the single-row constraint, override behavior, and the return value including the free-plan watermark. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents data as Jinja variables and page_settings fields, making 3 the baseline. The description adds genuine meaning beyond the schema by stating that `data` supplies exactly one row's worth of values and that omitting page_settings preserves the template's saved setup while passing it overrides only for this render.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('renders one PDF from a template already saved in the user's account') and explicitly contrasts with the sibling render_pdf ('Use render_pdf instead when you are writing the HTML yourself'). An agent can distinguish this from render_pdf, design_template, and create_batch_job without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Names the conditions for use ('when the user wants a document in their existing design'), the source of the required id ('Get `template_id` from list_templates'), the alternative tool for a different case (render_pdf), and the escalation path for multi-row output (create_batch_job after create_dataset/upload_dataset). This is explicit when/when-not/alternatives coverage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
upload_datasetUpload a spreadsheet as a datasetAInspect
SheetRender turns HTML templates plus spreadsheet rows into rendered PDFs. This tool uploads a local .csv or .xlsx file as a dataset a batch job can render, and returns the dataset id plus the column keys.
Use it when the user points at a file they already have — an export, a spreadsheet they attached, something you just wrote to disk. Use create_dataset instead when you are holding the rows as JSON: it avoids writing a file only to read it straight back.
file_path is a path on the machine running this MCP server, which is the user's machine, not SheetRender's. The first row must be the header. Other spreadsheet formats (.xls, .ods, .numbers) and .pdf are not parsed — convert to .csv or .xlsx first.
Limits: 20 MB per file and 500,000 cells; larger data has to be split across several datasets and jobs. Uploading is free; only rendering counts against the account's plan.
Returns the dataset id and each column's sanitized key — the name the template's placeholders, filename_template and group_by use, which is often not the header text verbatim ("Invoice No" becomes invoice_no).
| Name | Required | Description | Default |
|---|---|---|---|
| file_path | Yes | Path to a .csv or .xlsx file on the user's machine. Absolute is safest; `~` is expanded. | |
| template_id | Yes | Template id from list_templates. The dataset lands in that template's project. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden and meets it: it discloses that file_path is on the user's machine rather than SheetRender's, that the first row must be the header, which formats are rejected, the 20 MB / 500,000-cell limits, and the billing nuance (upload free, rendering counts against the plan).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then separated paragraphs for usage, parameter semantics, limits, and return values. Length is justified by the amount of genuinely useful constraint information, and no sentence is filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Though no output schema exists, the description describes the return values (dataset id plus each column's sanitized key) and covers the limits, format restrictions, and machine context an agent needs to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents both parameters. The description still adds real meaning beyond it: file_path lives on the user's machine, its format constraints and limits, and that the returned key is the sanitized name used by placeholders, filename_template, and group_by.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("uploads a local .csv or .xlsx file as a dataset a batch job can render") and explicitly contrasts with the sibling create_dataset. An agent can tell this apart from create_dataset without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit when-to-use trigger ("when the user points at a file they already have") plus a named alternative and its selecting condition ("Use create_dataset instead when you are holding the rows as JSON"). Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
6 tool updates
v0.1.4- Changed
create_batch_job1 field changed- changed
Input schema / properties / dataset_id / descriptionPrevious value: -"Id of a dataset already uploaded to the same SheetRender project."New value: +"Dataset id from create_dataset, upload_dataset or list_datasets. It must belong to the same template's project."
- Added
create_dataset - Added
design_template - Added
get_design - Added
list_datasets - Added
upload_dataset
6 tool updates
v0.1.2- First observed
create_batch_job - First observed
get_document - First observed
get_job - First observed
list_templates - First observed
render_pdf - First observed
render_template
TDQS
Scored across 6 tools
Each tool has a distinct purpose: listing templates, rendering from raw HTML, rendering from a saved template, creating a batch job, checking job progress, and downloading a specific document. The descriptions explicitly clarify when to use render_pdf vs render_template and how get_job and get_document relate, leaving no ambiguity.
All tool names follow the consistent verb_noun pattern in snake_case (list_templates, render_pdf, render_template, create_batch_job, get_job, get_document). The verbs are clear and the pattern is uniform, making it easy to predict tool behavior from the name.
Six tools is well-scoped for a PDF rendering MCP server. It covers the essential operations without bloat: listing templates, two rendering modes, batch job management, and document retrieval. Each tool earns its place in the workflow.
The core rendering lifecycle is covered: list templates, render single or batch, check job status, and download outputs. Minor gaps exist, such as no tool for uploading templates or datasets (stated as handled outside the MCP), and no cancellation or template management, but these are reasonable omissions for an integration-focused server.
Maintenance
Related MCP Connectors
Generate HTML to PDF documents in bulk or single — raw replacements or based on conditions, loops
JSON in, PDF out. Render invoices, certificates, reports and cards from a template and a payload.
Cloud PDF generation from HTML, CSS and XSL-FO, with PDF/A and PDF/UA support.
Render PDFs from templates you design once: list templates, check the data shape, preview, render.
Related MCP Servers
AlicenseBqualityBmaintenanceProvides PDF.co API functionality through the Model Context Protocol, enabling AI assistants to perform various PDF processing tasks like conversion, editing, searching, and security operations.3819 PyPI9MIT- AlicenseAqualityCmaintenanceGenerate professional PDFs from Claude, Cursor, and other AI tools. Create invoices, contracts, reports, and certificates from templates or inline HTML markup.755 npm1MIT

Dokmatiq DocGenofficial
AlicenseAqualityCmaintenancePDF/DOCX/Excel generation from HTML/Markdown with stationery overlay, ZUGFeRD/XRechnung e-invoicing, digital signing, form filling, and AI receipt OCR with DATEV/SKR03 export.40MIT
mcp-server-pdfnoodleofficial
AlicenseNot gradedqualityDmaintenanceEnables AI assistants to generate PDF documents from templates or raw HTML using natural language, with tools for template creation, PDF generation, and utility operations.31 npm1MIT