Skip to main content
Firebase Genkit 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.
  • An Unstructured API key, which the Transform MCP server uses as a bearer token. Get an API key.
  • A Gemini API key for the model that drives the tools. Get an API key.

Install the packages

In your terminal, install Genkit, the MCP plugin, and the Google GenAI model plugin:
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 for changes.

Connect to the Transform MCP server

Store your API keys in environment variables rather than hard-coding them:
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:
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.

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.
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.
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.

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:
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.

Run the application

From the directory that contains 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:

Next steps

Questions? Need help?