transform
Transform MCP is a single MCP-native tool that turns any file into agent-ready data, called directly from your agent session with no separate pipeline to wire…
What it can do
- Request File Upload Url: Upload a file to the Unstructured Platform for parsing. Returns a signed PUT URL; the agent PUTs the file bytes there, then passes the returned ``file_ref`` to ``start_transfo
- Start Transform Job: Transform files into structured output by running a configurable pipeline. Submits 1–10 inputs as one async job and returns a ``job_id`` immediately. Drive the wait via ``check_jo
- Check Job Status: Check a job's current status. Returns immediately. Works for any job — a transform job (from ``start_transform_job``) or an extraction job (from ``start_extraction_job``). Non-blocki
What data it sees
Do you need an account
No: the server works without sign-in
Transform MCP is a single MCP-native tool that turns any file into agent-ready data, called directly from your agent session with no separate pipeline to wire up. Drop in a PDF, spreadsheet, scan, or email and get back partitioned, enriched, chunked, and embedded output ready for RAG, vector stores, or agent memory. 60+ formats, one call, 15,000 free pages a month.
How to connect
How to connect transform to Claude
The server address to paste into Claude settings:
https://transform--unstructured.run.tools
- Open Claude (claude.ai or the desktop app).
- Go to Settings → Connectors.
- Click Add custom connector.
- Paste the server address copied below into Remote MCP server URL and click Add.
- In a chat, click + → Connectors and switch the new connector on.
Custom connectors are available on Free, Pro, Max, Team and Enterprise plans (Free is limited to one). On Team and Enterprise an organization Owner adds the connector first under Organization settings → Connectors.
Authorization
After you click Add, a sign-in window for the service opens. Sign in with your own account and approve access. Claude never sees your password.
How to connect transform to ChatGPT
The server address to paste into ChatGPT settings:
https://transform--unstructured.run.tools
- Open ChatGPT in a browser (chatgpt.com). A Plus, Pro, Business, Enterprise or Edu plan is required.
- Turn on developer mode once: Settings → Apps → Advanced settings → Developer mode.
- Open Settings → Connectors and click Create.
- Fill in the form: Name (anything), Description (one line about what the service does), MCP server URL (copy it below).
- Under Authentication choose OAuth if the service requires sign-in, otherwise None. Click Create.
- In a new chat open + → Apps/Connectors and enable the connector.
OpenAI has renamed this section before (Connectors → Apps/Plugins). If the label differs, search settings for "developer mode". On Business/Enterprise workspaces an admin must allow custom connectors first.
Authorization
On first use ChatGPT opens the service's sign-in window. Sign in and approve access.
How to connect transform to Cursor
The server address to paste into Cursor settings:
https://transform--unstructured.run.tools
Fastest: click Open in Cursor below and confirm the prompt.
Manually:
- In Cursor open Settings → Cursor Settings → MCP and click Add new global MCP server.
- Paste the JSON copied below into
~/.cursor/mcp.json(per project:.cursor/mcp.jsonin the repo root). - Save the file. The server appears in the MCP list; authorize it there if asked.
Authorization
If the service needs sign-in, an authorize button appears next to the server in the MCP list.
Server tool list (7)
Raw names from tools/list. Only developers need these.
| request_file_upload_url | Upload a file to the Unstructured Platform for parsing. Returns a signed PUT URL; the agent PUTs the file bytes there, then passes the returned ``file_ref`` to ``start_transform_job``. The URL points at the MCP host itself — no third-party blob-storage hostname needs to be allowlisted. Call this when the user has provided a file you can read by path — either typed/quoted in chat, @-mentioned, drag-dropped (most terminals paste the path as text), or already sitting in your sandbox. Skip this tool entirely if the file is reachable at a public ``https://`` URL (pass the URL straight to ``start_transform_job``). **If the file came inline as a vision attachment with no path in the message:** you cannot save it yourself. Vision attachments reach you through the multimodal encoder, not as text/base64 in your tool-accessible context, so ``Write`` / ``Bash`` have no bytes to write. Most MCP hosts (Claude Code, Cursor, VS Code Copilot, Cline, Continue) do **not** expose a readable path either. Don't guess at temp directories — ask the user once, concisely, to save the file and share the path. Hosts where a path *is* already available without asking: - Anthropic Code Execution / claude.ai (Cowork): attachments mount under ``/mnt/user-data/uploads/`` — check there first. - Zed: ``@``-mentioned files keep their workspace path. - Codex CLI: user-supplied ``--image <path>`` is in the message. Limits: a single file may not exceed 50 MB. Upload-URL minting also has a per-tenant per-minute abuse backstop, set well above any legitimate parsing rate (current value in the server instructions); if a call returns ``rate_limited``, wait for the returned ``retry_after`` before minting more URLs — already-minted URLs stay usable until ``expires_at``. |
| start_transform_job | Transform files into structured output by running a configurable pipeline. Submits 1–10 inputs as one async job and returns a ``job_id`` immediately. Drive the wait via ``check_job_status``; once complete, call ``get_job_results`` to retrieve the rendered output in the format you want. **Inputs.** ``file_refs`` accepts ``https://`` / ``http://`` URLs, ``u10d://file/<id>`` uploads (mint via ``request_file_upload_url`` + PUT), and ``u10d://output/<id>`` refs from a previous ``get_job_results``. An ``output_ref`` resolves to that job's *terminal* stage — the last stage it ran. Feeding it back in re-ingests that Element JSON without re-partitioning, so re-chunking or re-embedding is cheap and needs no re-upload — but the re-feed is only sound when the ref is *upstream* of the stage you're changing: a parse-only ref is a clean seed to re-chunk; an already-embedded ref is not (re-chunking it mangles already-chunked output). See **Staging strategy** below. Limits: 50 MB per file, 10 per call, 5 active jobs per tenant. Free-tier accounts also carry a page allowance; once it's exhausted this tool returns a ``quota_exceeded`` error whose ``message`` and ``upgrade_url`` should be relayed to the user verbatim. **Pipeline.** ``stages`` is a map of stage name → settings; the tool wires the DAG in canonical order (``partition → enrich → chunk → embed``) regardless of key order. Omit ``stages`` (or pass ``{}``) for a plain parse. Stages: - ``partition`` — ``{"strategy": ...}`` (``fast`` / ``hi_res`` / ``vlm`` / ``auto``), plus optional ``languages``, ``page_range``, and an ``options`` passthrough. A partition node always runs (it's a near-passthrough for Element-JSON inputs); this stage only customizes it. **Pick the strategy explicitly from the input type — don't rely on ``auto`` — and note the wrong choice degrades silently rather than erroring:** - **Images, PowerPoint (PPT/PPTX), and PDFs** → start with ``vlm``. If its output isn't optimal, switch to ``hi |
| check_job_status | Check a job's current status. Returns immediately. Works for any job — a transform job (from ``start_transform_job``) or an extraction job (from ``start_extraction_job``). Non-blocking: one status snapshot per call, never a long wait. While the job is running, the response carries ``poll_after`` — wait that many seconds, then call again with the same ``job_id``. Polling faster is pointless: the server caches the status for the same window. The job runs server-side and is never lost between checks, so use the wait to do other work (mint upload URLs, upload files, submit other batches). On ``COMPLETED``, call ``get_job_results`` next. |
| get_job_results | Fetch a completed job's results — works for transform and extraction jobs. Call this after ``check_job_status`` returns ``COMPLETED``. One tool serves both job kinds; it detects which from the job's DAG and shapes the result accordingly. Every response carries a ``job_type`` field (``"transform"`` or ``"extraction"``) so you always know which shape you got — never infer it from element counts. The kinds: - **Transform job** (from ``start_transform_job``): renders the parsed Element JSON as JSON / markdown / HTML / text. ``output_format`` is re-selectable — a job already retrieved as ``json`` can be re-fetched as ``md`` without re-running the engine. Each returned file also carries a durable ``output_ref`` (``u10d://output/<id>``) you can re-fetch or feed back into ``start_transform_job`` (to chunk/embed it) or ``start_extraction_job`` (to extract from it) without re-uploading or re-parsing. Output is delivered **out of band, not inline**: each entry returns a short-TTL signed ``download_url`` (plus counts) instead of the content, so a large document never floods your context. Fetch it e.g. ``curl -o output.<ext> '<download_url>'`` (download host is this MCP server). If your shell and the destination are different filesystems, do **not** read the output into context to re-emit it — hand the user a curl script; each entry's ``next_steps`` spells this out. (When output storage isn't configured — local dev — the entry inlines ``content``.) - **Extraction job** (from ``start_extraction_job``): returns the extracted data per file as a **provenance-wrapped** object — ``filename``, ``filetype``, ``processed_date_utc``, ``source_file_uri`` (the Element JSON ref you extracted from), and ``extracted_data``. Keep that wrapper intact downstream — do not unwrap to bare ``extracted_data`` and discard the provenance, which is what ties each result back to its document (see the response ``next_steps``). ``output_format`` and ``image_base64 |
| start_extraction_job | Extract structured JSON from parsed documents against a JSON schema. Runs the platform's structured data extractor over already-parsed **Unstructured Element JSON** and returns only the extracted data. Submits 1–10 inputs as one async job and returns a ``job_id`` immediately; drive the wait via ``check_job_status``, then call ``get_job_results`` to retrieve the extracted JSON. **Inputs are Element JSON, never raw documents.** Each ref in ``element_json_refs`` must resolve to the Element JSON a parse produces — a ``u10d://output/<id>`` from a previous ``get_job_results``, or a ``u10d://file/<id>`` / ``http(s)://`` pointing at Element JSON. To extract from a raw PDF/DOCX/etc., run ``start_transform_job`` first and pass its ``output_ref`` here. Embed-stage output (Element JSON carrying vectors) is rejected — pass the parse (or chunk) output, i.e. the Element JSON from *before* any embed stage. Limits mirror ``start_transform_job``: 50 MB per file, 10 per call, 5 active jobs per tenant. **Parse quality drives extraction quality.** The extractor can only pull out what the parse captured, and documents to extract from are almost always images / PowerPoint / PDFs, so parse them the same way: (1) **start with** ``start_transform_job`` and ``stages={"partition": {"strategy": "vlm"}}`` (a vision model reads the page); (2) **if the extraction isn't optimal, re-parse with** ``strategy=hi_res`` plus ``enrich`` types ``["image_description", "generative_ocr", "table_to_html"]`` (per-element AI passes — image/OCR are VLM-based, table_to_html may use a VLM or agentic AI), which can massively outperform ``vlm`` when hi_res object detection nails the layout and element classification. A cheap ``fast``/``auto`` parse misses tables, figures, and scanned text and yields sparse extractions — re-parse at high fidelity if the Element JSON you're about to pass came from a low-quality run. **One schema per call.** ``schema_to_extract`` is a single JSON Schema (as a JSON string) applied to |
| suggest_extraction_schema_for_file | Suggest a JSON extraction schema for one parsed document. Given **Unstructured Element JSON** for a single document, drafts a JSON Schema (valid for ``start_extraction_job``) capturing the document's salient fields. Use this when you want to extract structured data but don't yet have a schema. **Input is Element JSON for one document, not a raw file.** ``element_json_ref`` must resolve to the Element JSON a parse produces (a ``u10d://output/<id>``, or a ``u10d://file/<id>`` / ``http(s)://`` pointing at Element JSON). To suggest from a raw PDF/DOCX/etc., run ``start_transform_job`` first and pass its ``output_ref`` here. For the richest schema, parse at high fidelity: for images/PPT/PDF start with ``strategy=vlm``, then if that's not optimal re-parse with ``strategy=hi_res`` plus the ``image_description`` / ``generative_ocr`` / ``table_to_html`` enrichments (which can outperform ``vlm`` when hi_res layout detection is accurate) so tables and figures are captured. Embedded output (Element JSON carrying vectors) is rejected. |
| get_instructions | Return this server's full usage instructions and metadata on demand. **Call this if you don't have the server's usage guidance in context.** A server's instructions are delivered once, during the connection handshake, and what happens next is up to the MCP host: some hosts never relay them to the agent, and others truncate them after a few thousand characters. If you're missing the URI-scheme decision tree, the polling contract, the ``stages`` pipeline config, or the enforced limits — or you're unsure whether what you have is complete — call this tool to recover the full guidance in-band. Returns: ``instructions`` — the complete instructions text, identical to what the connection handshake sends; ``name`` and ``version`` — server/product identity; ``models`` — the provider→model catalogs the registry offered at build (``vlm_partitioner`` as ``{provider: [model, …]}`` for ``partition.strategy: "vlm"``, ``embedder`` as ``{provider: {model: dimension}}`` for the ``embed`` stage), so you can discover selectable models here rather than parsing the ``start_transform_job`` description; a ``null`` catalog means the build-time snapshot was unavailable (registry unreachable), not that no models exist — per-request validation stays the live authority; ``limits`` — every enforced limit on the surface: the aggregate caps (``max_file_mb`` / ``max_files_per_call`` / ``max_active_jobs_per_tenant``), the timing values (``poll_after_seconds`` and the signed-URL TTLs), and ``rate_limits_per_minute`` — the per-tenant per-minute backstop for every tool (keyed by tool name), the shared ``global`` ceiling, and the ``upload_route`` / ``output_download_route`` HTTP-route caps; and ``links`` — the ``dashboard`` (where the user manages their API key and finds billing and usage information) and ``docs`` URLs. |