> ## Documentation Index
> Fetch the complete documentation index at: https://docs.unstructured.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Get started with Unstructured Transform for Firebase Genkit

> Learn how to connect the Unstructured Transform MCP server to a Firebase Genkit application. Your flows and agents can then point to your files and have Transform start producing partitioned, enriched, chunked, and embedded data based on your files in minutes.

[Firebase Genkit](https://genkit.dev/) is Google's open-source framework for building AI-powered
JavaScript/TypeScript applications. Unlike end-user AI tools that add the Transform MCP server through a settings
screen, Genkit connects to it *from application code*: you create an MCP client and hand its tools to a model call.
Because Transform is a hosted *remote* MCP server, there is nothing to install or run locally. You point the client at
the server URL and authenticate with your Unstructured API key.

Genkit ships the building block that makes this work out of the box, so you do not need a custom transport: the
`@genkit-ai/mcp` package's `createMcpClient` function connects to a remote MCP server over the streamable HTTP
transport, and its `requestInit` setting carries your Unstructured API key as an `Authorization: Bearer` header on
every request, including the initial `initialize` handshake.

## Requirements

Before you begin, you must have the following:

* Node.js 20 or later on your local development machine. To check, in your terminal, run `node -v` or `node --version`. [Install Node.js](https://nodejs.org/en/download).
* An Unstructured API key, which the Transform MCP server uses as a bearer token. [Get an API key](/transform/code#get-your-unstructured-api-key-and-url).
* A Gemini API key for the model that drives the tools. [Get an API key](https://aistudio.google.com/apikey).

## Install the packages

In your terminal, install Genkit, the MCP plugin, and the Google GenAI model plugin:

```bash theme={null}
npm install genkit @genkit-ai/mcp @genkit-ai/google-genai
```

<Note>
  Genkit is in active development and its APIs change between releases. This guide was verified against
  `@genkit-ai/mcp` version 1.39.0. If you install a different version, check the
  [Genkit release notes](https://github.com/firebase/genkit/releases) for changes.
</Note>

## Connect to the Transform MCP server

Store your API keys in environment variables rather than hard-coding them:

```bash theme={null}
export UNSTRUCTURED_API_KEY="<your-unstructured-api-key>"
export GEMINI_API_KEY="<your-gemini-api-key>"
```

In your application code, create an MCP client for the Transform MCP server. The server connection settings go inside
the `mcpServer` object, and the `requestInit` headers attach the `Authorization: Bearer` header to every request, so
the handshake and the tool calls are both authenticated:

```typescript theme={null}
import { createMcpClient } from "@genkit-ai/mcp";

const transformClient = createMcpClient({
  name: "unstructured_transform",
  mcpServer: {
    url: "https://mcp.transform.unstructured.io",
    requestInit: {
      headers: {
        Authorization: `Bearer ${process.env.UNSTRUCTURED_API_KEY}`,
      },
    },
  },
});
```

<Warning>
  Use the server URL exactly as shown. Do not add `/mcp` or `/sse` to the end of the URL.

  Also, the server connection settings must be nested inside the `mcpServer` object as shown. Passing `url` at the
  top level of the `createMcpClient` options throws `TypeError: Cannot read properties of undefined (reading
      'disabled')` in version 1.39.0.
</Warning>

## Understand the transform job lifecycle

Transforming a document is an asynchronous, multi-step flow. Four of the steps are MCP tool calls, and two are plain
HTTP transfers that are *not* MCP calls:

1. `request_file_upload_url`: Returns a pre-signed `upload_url` and a `file_ref` for a local file.
2. An HTTP `PUT` of the raw file bytes to the pre-signed `upload_url`. This is not an MCP call.
3. `start_transform_job`: Starts a transform job for one or more `file_ref` values (or public HTTP(S) URLs) and returns a `job_id`.
4. `check_job_status`: Reports whether the job is `SCHEDULED`, `IN_PROGRESS`, or `COMPLETED`.
5. `get_job_results`: Returns a pre-signed `download_url` for the Markdown output of each transformed file.
6. An HTTP `GET` of the Markdown from the pre-signed `download_url`. This is not an MCP call.

Extracting structured data adds a second, dependent job. The extraction tools read the element JSON that a parse
produces rather than the raw file, so an extraction always follows a transform job, keyed by the durable
`output_ref` that `get_job_results` returns for each file:

* `suggest_extraction_schema_for_file`: Drafts a JSON Schema from one parsed document. Use this only when you do
  not already have a schema.
* `start_extraction_job`: Starts an extraction for up to 10 `output_ref` values against a single JSON Schema, and
  returns a `job_id`.

An extraction job is polled with `check_job_status` and read with `get_job_results`, exactly as a transform job is.
Its results come back inline rather than behind a `download_url`, so there is no equivalent of the final HTTP `GET`.
Each result is wrapped with the source filename, file type, timestamp, and the element JSON reference it was taken
from. For prompt patterns, see [Structured data extraction](/transform/sde).

An agent cannot perform the two raw HTTP transfers through MCP tools alone, and an unpaced polling loop wastes model
calls. Define a small `wait_seconds` tool alongside the MCP tools so the model can pause between status checks.

<Note>
  The pre-signed `upload_url` and `download_url` carry their own credentials in the URL itself. The `PUT` and `GET`
  must not send the `Authorization` header, or the storage service rejects them.
</Note>

## Build the application

The following complete example gives Gemini the Transform tools plus a `wait_seconds` pacing tool, then asks it to
parse a PDF from a public URL. Save it as `index.mjs`:

```typescript theme={null}
import { setTimeout as sleep } from "node:timers/promises";

import { googleAI } from "@genkit-ai/google-genai";
import { createMcpClient } from "@genkit-ai/mcp";
import { genkit, z } from "genkit";

const ai = genkit({ plugins: [googleAI()] });

const transformClient = createMcpClient({
  name: "unstructured_transform",
  mcpServer: {
    url: "https://mcp.transform.unstructured.io", // root URL - do not append /mcp
    requestInit: {
      headers: {
        Authorization: `Bearer ${process.env.UNSTRUCTURED_API_KEY}`,
      },
    },
  },
});

const waitSeconds = ai.defineTool(
  {
    name: "wait_seconds",
    description:
      "Pause before the next status check. Transform jobs take 30 seconds to a few " +
      "minutes; call this between check_job_status calls instead of polling " +
      "in a tight loop. Use 30 seconds unless told otherwise.",
    inputSchema: z.object({ seconds: z.number().int().min(1).max(120) }),
    outputSchema: z.object({ waitedSeconds: z.number() }),
  },
  async ({ seconds }) => {
    await sleep(seconds * 1000);
    return { waitedSeconds: seconds };
  },
);

await transformClient.ready();
const transformTools = await transformClient.getActiveTools(ai);

const { text } = await ai.generate({
  model: googleAI.model("gemini-3.1-flash-lite"),
  system:
    "You parse documents with the Unstructured Transform MCP server. Pass public " +
    "https:// file URLs straight to start_transform_job. It returns a job_id; poll with " +
    "check_job_status, calling wait_seconds(30) between checks - jobs take 30 " +
    "seconds to a few minutes. When the job completes, call get_job_results " +
    "and report the parsed content back to the user.",
  prompt:
    "Parse this PDF with Unstructured Transform: https://arxiv.org/pdf/1706.03762 " +
    "When it completes, tell me the document title and the first three section " +
    "headings from the parsed output.",
  tools: [...transformTools, waitSeconds],
  maxTurns: 20,
});

console.log(text);
await transformClient.disable();
```

<Note>
  On the Gemini API free tier, some models allow as few as 5 requests per minute, and the model spends one request
  per step of the workflow. The `wait_seconds(30)` pacing in the system prompt keeps a polling loop from hitting
  that limit; if you see `429 RESOURCE_EXHAUSTED` or transient `503` errors, increase the wait, retry later, or
  choose a different model from the [Gemini model list](https://ai.google.dev/gemini-api/docs/models).
</Note>

## Run the application

From the directory that contains `index.mjs`:

```bash theme={null}
node index.mjs
```

The model starts the transform job, polls at a measured pace with `wait_seconds`, fetches the results, and prints its
answer. A successful run ends with output like this:

```text theme={null}
The PDF (Attention Is All You Need) has been parsed successfully.

*   Document Title: Attention Is All You Need
*   First Three Section Headings:
    1.  1 Introduction
    2.  2 Background
    3.  3 Model Architecture
```

## Extract structured data

To return named fields as JSON rather than the whole document as text, use the server's two extraction tools.
`getActiveTools` already loads them, so only the system prompt and the request change:

```typescript theme={null}
const { text } = await ai.generate({
  model: googleAI.model("gemini-3.1-flash-lite"),
  system:
    "You extract structured data with the Unstructured Transform MCP server. The " +
    "extraction tools read the element JSON a parse produces, never the raw file, so " +
    "always parse first: call start_transform_job with stages " +
    '{"partition": {"strategy": "vlm"}} for an image, PowerPoint file, or PDF, poll ' +
    "check_job_status with wait_seconds(30) between checks, then call get_job_results " +
    "and keep each file's output_ref. Do not download the parsed output. If the user " +
    "gave no schema, call suggest_extraction_schema_for_file with that output_ref and " +
    "show the draft first. Then call start_extraction_job with element_json_refs set to " +
    "the output_refs and schema_to_extract set to a JSON Schema passed as a JSON string, " +
    "and poll it the same way. Extraction results come back inline, wrapped with the " +
    "source filename; report that filename with each object and keep the wrapper.",
  prompt:
    "Extract each plant's name, light needs, watering instructions, and humidity level " +
    "from https://docs.unstructured.io/img/pipelines/data-extractor/house-plant-care.png " +
    "as JSON. Draft a schema from the image itself and show it to me first.",
  tools: [...transformTools, waitSeconds],
  maxTurns: 30,
});
```

Note the higher `maxTurns`. An extraction runs a second job after the parse, so it needs roughly twice the polling
turns of a parse alone.

Each result is wrapped with the source filename, file type, timestamp, and the element JSON reference it was taken
from, alongside the extracted data. Keep that wrapper when you persist results, since it is what ties each record
back to its document.

<Tip>
  Extraction can only surface what the parse captured, so parse quality sets the ceiling. If a result comes back
  sparse, re-parse with `strategy: "hi_res"` plus the `image_description`, `generative_ocr`, and `table_to_html`
  enrichment steps, then extract from the new `output_ref`. For more prompt patterns, see
  [Structured data extraction](/transform/sde).
</Tip>

## Next steps

* [Control Transform file parsing output](/transform/output): Control how the Unstructured Transform MCP server instructs Transform to partition, enrich, chunk, and embed the data based on your files.
* [Control Transform structured data extraction](/transform/sde): Control how the Unstructured Transform MCP server extracts and formats structured data from your files.
* [Control Transform generated sample code](/transform/code): Control how the Unstructured Transform MCP server generates sample curl or Python code that demonstrates how to use Transform to partition, enrich, chunk, and embed the data based on your files.

## Questions? Need help?

* For technical support, [request support](/support/request).
