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

# Unstructured Transform MCP server installation for Microsoft Agent Framework

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

[Microsoft Agent Framework](https://learn.microsoft.com/agent-framework/) is Microsoft's open-source SDK for building
AI agents in Python and .NET. Unlike end-user AI tools that add the Transform MCP server through a settings screen,
Microsoft Agent Framework connects to it *from code*: you load the Transform MCP server's tools as agent tools and hand
them to any Microsoft Agent Framework agent. 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.

## Requirements

Before you begin, you must have the following:

* For Python: Python 3.10 or later on your local development machine. To check, in your terminal, run `python --version` or `python3 --version`. [Install Python](https://www.python.org/downloads/).
* For .NET: the .NET 8.0 SDK or later on your local development machine. To check, in your terminal, run `dotnet --version`. [Install .NET](https://dotnet.microsoft.com/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).
* An OpenAI API key, or an Azure OpenAI resource, for the agent's underlying chat model.

## Install the packages

In your terminal, install the Microsoft Agent Framework packages and, for .NET, the Model Context Protocol SDK:

<CodeGroup>
  ```bash Python theme={null}
  pip install "agent-framework==1.11.0"
  ```

  ```bash .NET theme={null}
  dotnet add package Microsoft.Agents.AI.OpenAI --version 1.13.0
  dotnet add package ModelContextProtocol --version 2.0.0-preview.2
  ```
</CodeGroup>

<Note>
  These SDKs are in active development, and their APIs change between releases. The versions shown here are the
  versions this guide was verified against. If you install different versions, check the
  [Microsoft Agent Framework release notes](https://github.com/microsoft/agent-framework/releases) for API changes.
</Note>

## Connect to the Transform MCP server

The Transform MCP server speaks the streamable HTTP transport and authenticates with your Unstructured API key sent as
a bearer token. Store your key in an environment variable rather than hard-coding it:

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

The server requires the `Authorization` header on every request, including the initial `initialize` handshake. Set the
header on the HTTP client or transport itself, as follows:

<CodeGroup>
  ```python Python theme={null}
  import os

  import httpx
  from agent_framework import MCPStreamableHTTPTool

  http_client = httpx.AsyncClient(
      headers={"Authorization": f"Bearer {os.environ['UNSTRUCTURED_API_KEY']}"},
      timeout=httpx.Timeout(30.0, read=300.0),
  )

  transform = MCPStreamableHTTPTool(
      name="unstructured-transform",
      url="https://mcp.transform.unstructured.io",
      http_client=http_client,
  )

  # Connecting loads the Transform tools: request_file_upload_url,
  # transform_files, check_transform_status, and get_transform_results.
  async with transform:
      print(sorted(f.name for f in transform.functions))
  ```

  ```csharp .NET theme={null}
  using ModelContextProtocol.Client;

  var transport = new HttpClientTransport(new HttpClientTransportOptions
  {
      Endpoint = new Uri("https://mcp.transform.unstructured.io"),
      TransportMode = HttpTransportMode.StreamableHttp,
      AdditionalHeaders = new Dictionary<string, string>
      {
          ["Authorization"] = $"Bearer {Environment.GetEnvironmentVariable("UNSTRUCTURED_API_KEY")}",
      },
  });

  await using var mcpClient = await McpClient.CreateAsync(transport);

  // Lists the Transform tools: request_file_upload_url, transform_files,
  // check_transform_status, and get_transform_results.
  var transformTools = await mcpClient.ListToolsAsync();
  Console.WriteLine(string.Join(", ", transformTools.Select(t => t.Name)));
  ```
</CodeGroup>

<Warning>
  In Python, do not use the `header_provider` parameter as the only way to send the bearer token. `header_provider`
  injects headers only during tool calls, so the connection's `initialize` handshake goes out unauthenticated and the
  server rejects it. Pass an `httpx.AsyncClient` with default headers through `http_client` instead, as shown above.
</Warning>

### Add local file helpers for uploads and downloads

Transform's job lifecycle mixes MCP tool calls with two plain HTTP requests that are not MCP calls: an HTTP `PUT` of
the raw file bytes to a pre-signed `upload_url`, and an HTTP `GET` of the finished output from a pre-signed
`download_url`. An agent cannot perform raw HTTP requests through MCP tools alone. To close that gap, the full examples
in the next section register three small local functions as additional agent tools:

* `describe_local_file`: Returns a file's filename, content type, and exact size in bytes, which
  `request_file_upload_url` requires.
* `upload_local_file`: Sends the file bytes to the pre-signed `upload_url` with an HTTP `PUT`.
* `download_text`: Retrieves the finished Markdown from the pre-signed `download_url` with an HTTP `GET`.

Pre-signed URLs carry their own credentials in the URL itself. The helpers must not send the `Authorization` header on
these two requests, or the storage service rejects them.

### Use the tools in an agent

The following example builds an agent that can drive the full Transform job lifecycle. The agent requests an upload
URL, uploads the file through the local helper, starts the transform, polls for status, and downloads the Markdown
output:

<CodeGroup>
  ```python Python theme={null}
  import asyncio
  import mimetypes
  import os
  from pathlib import Path
  from typing import Annotated

  import httpx
  from agent_framework import MCPStreamableHTTPTool
  from agent_framework.openai import OpenAIChatClient

  TRANSFORM_MCP_URL = "https://mcp.transform.unstructured.io"


  def describe_local_file(
      path: Annotated[str, "The path to a local file."],
  ) -> dict:
      """Get the filename, content type, and exact size in bytes of a local file."""
      file = Path(path).expanduser()
      content_type = mimetypes.guess_type(file.name)[0] or "application/octet-stream"
      return {
          "filename": file.name,
          "content_type": content_type,
          "size_bytes": file.stat().st_size,
      }


  def upload_local_file(
      path: Annotated[str, "The path to the local file to upload."],
      upload_url: Annotated[str, "The pre-signed upload_url from request_file_upload_url."],
      content_type: Annotated[str, "The Content-Type header value to send."],
  ) -> str:
      """Upload a local file's raw bytes to a pre-signed URL with an HTTP PUT."""
      # The URL is pre-signed: do not send an Authorization header here.
      file = Path(path).expanduser()
      response = httpx.put(
          upload_url,
          content=file.read_bytes(),
          headers={"Content-Type": content_type},
          timeout=120.0,
      )
      response.raise_for_status()
      return f"Uploaded {file.name}, status {response.status_code}."


  def download_text(
      url: Annotated[str, "The pre-signed download_url from get_transform_results."],
  ) -> str:
      """Download the transformed output from a pre-signed URL with an HTTP GET."""
      # The URL is pre-signed: do not send an Authorization header here.
      response = httpx.get(url, timeout=120.0)
      response.raise_for_status()
      return response.text


  INSTRUCTIONS = """
  You transform local documents into structured Markdown with the Unstructured
  Transform tools. Follow this exact sequence:

  1. Call describe_local_file to get the file's filename, content_type, and
     size_bytes. Report files larger than 52428800 bytes (50 MB) to the user
     instead of uploading them.
  2. Call request_file_upload_url with that filename, content_type, and
     size_bytes.
  3. Call upload_local_file with the file path and the returned upload_url and
     Content-Type header value.
  4. Call transform_files with file_refs set to a one-item list containing the
     returned file_ref.
  5. Poll check_transform_status with the returned job_id until status is
     COMPLETED. Transforms take from about 10 seconds to several minutes.
  6. Call get_transform_results with the job_id, then call download_text with
     the first file's download_url to retrieve the Markdown output.
  """


  async def main() -> None:
      # The Transform MCP server requires the Authorization header on every
      # request, including the initialize handshake, so configure it on the
      # HTTP client itself.
      http_client = httpx.AsyncClient(
          headers={"Authorization": f"Bearer {os.environ['UNSTRUCTURED_API_KEY']}"},
          timeout=httpx.Timeout(30.0, read=300.0),
      )

      transform = MCPStreamableHTTPTool(
          name="unstructured-transform",
          url=TRANSFORM_MCP_URL,
          http_client=http_client,
      )

      async with transform:
          agent = OpenAIChatClient().as_agent(
              name="transform-agent",
              instructions=INSTRUCTIONS,
              tools=[transform, describe_local_file, upload_local_file, download_text],
          )

          result = await agent.run(
              "Transform the document at ./sample.pdf and show me the first "
              "500 characters of the Markdown output, plus the element count."
          )
          print(result.text)


  if __name__ == "__main__":
      asyncio.run(main())
  ```

  ```csharp .NET theme={null}
  using System.ComponentModel;
  using Microsoft.Agents.AI;
  using Microsoft.Extensions.AI;
  using ModelContextProtocol.Client;
  using OpenAI;
  using OpenAI.Chat;

  const string TransformMcpUrl = "https://mcp.transform.unstructured.io";

  var apiKey = Environment.GetEnvironmentVariable("UNSTRUCTURED_API_KEY")
      ?? throw new InvalidOperationException("Set the UNSTRUCTURED_API_KEY environment variable.");

  // The Transform MCP server requires the Authorization header on every
  // request, including the initialize handshake.
  var transport = new HttpClientTransport(new HttpClientTransportOptions
  {
      Endpoint = new Uri(TransformMcpUrl),
      TransportMode = HttpTransportMode.StreamableHttp,
      AdditionalHeaders = new Dictionary<string, string>
      {
          ["Authorization"] = $"Bearer {apiKey}",
      },
  });

  await using var mcpClient = await McpClient.CreateAsync(transport);

  var transformTools = await mcpClient.ListToolsAsync();
  Console.WriteLine($"Discovered tools: {string.Join(", ", transformTools.Select(t => t.Name))}");

  // Local helper tools. The byte upload and result download use pre-signed
  // URLs, which are plain HTTP calls rather than MCP tool calls.
  using var httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(120) };

  [Description("Get the filename, content type, and exact size in bytes of a local file.")]
  static object DescribeLocalFile([Description("The path to a local file.")] string path)
  {
      var file = new FileInfo(path);
      var contentType = file.Extension.ToLowerInvariant() switch
      {
          ".pdf" => "application/pdf",
          ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
          ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
          ".html" or ".htm" => "text/html",
          ".txt" => "text/plain",
          ".png" => "image/png",
          ".jpg" or ".jpeg" => "image/jpeg",
          _ => "application/octet-stream",
      };
      return new { filename = file.Name, content_type = contentType, size_bytes = file.Length };
  }

  [Description("Upload a local file's raw bytes to a pre-signed URL with an HTTP PUT.")]
  async Task<string> UploadLocalFile(
      [Description("The path to the local file to upload.")] string path,
      [Description("The pre-signed upload_url from request_file_upload_url.")] string uploadUrl,
      [Description("The Content-Type header value to send.")] string contentType)
  {
      // The URL is pre-signed: do not send an Authorization header here.
      using var content = new ByteArrayContent(await File.ReadAllBytesAsync(path));
      content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(contentType);
      using var response = await httpClient.PutAsync(uploadUrl, content);
      response.EnsureSuccessStatusCode();
      return $"Uploaded {Path.GetFileName(path)}, status {(int)response.StatusCode}.";
  }

  [Description("Download the transformed output from a pre-signed URL with an HTTP GET.")]
  async Task<string> DownloadText(
      [Description("The pre-signed download_url from get_transform_results.")] string url)
  {
      // The URL is pre-signed: do not send an Authorization header here.
      return await httpClient.GetStringAsync(url);
  }

  const string Instructions = """
      You transform local documents into structured Markdown with the
      Unstructured Transform tools. Follow this exact sequence:

      1. Call DescribeLocalFile to get the file's filename, content_type, and
         size_bytes. Report files larger than 52428800 bytes (50 MB) to the
         user instead of uploading them.
      2. Call request_file_upload_url with that filename, content_type, and
         size_bytes.
      3. Call UploadLocalFile with the file path and the returned upload_url
         and Content-Type header value.
      4. Call transform_files with file_refs set to a one-item list containing
         the returned file_ref.
      5. Poll check_transform_status with the returned job_id until status is
         COMPLETED. Transforms take from about 10 seconds to several minutes.
      6. Call get_transform_results with the job_id, then call DownloadText
         with the first file's download_url to retrieve the Markdown output.
      """;

  var openAiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY")
      ?? throw new InvalidOperationException("Set the OPENAI_API_KEY environment variable.");
  var model = Environment.GetEnvironmentVariable("OPENAI_CHAT_MODEL") ?? "gpt-4.1-mini";

  AIAgent agent = new OpenAIClient(openAiKey)
      .GetChatClient(model)
      .AsAIAgent(
          instructions: Instructions,
          tools:
          [
              .. transformTools.Cast<AITool>(),
              AIFunctionFactory.Create(DescribeLocalFile),
              AIFunctionFactory.Create(UploadLocalFile),
              AIFunctionFactory.Create(DownloadText),
          ]);

  var response = await agent.RunAsync(
      "Transform the document at ./sample.pdf and show me the first "
      + "500 characters of the Markdown output, plus the element count.");
  Console.WriteLine(response.Text);
  ```
</CodeGroup>

## Parse your source files

Parsing requests have the following limits:

* Each file must be of a [supported file type](/transform/supported-file-types).
* Each file must be 50 MB or less in size.
* Each request must have 10 files or fewer.
* Only 5 requests can be running at a time.

The Transform MCP server is designed to report these limits back to the agent through its tool responses. Because of
this, your agent should notify you whenever it encounters a file that exceeds 50 MB in size, and it should formulate
strategies to send requests that are 10 files or fewer and not cause more than 5 requests to be running at a time. You
can reinforce this behavior in your agent's system prompt, as the examples above do for the file size limit.

### Run the agent

The examples default to OpenAI as the chat model provider. Set your OpenAI key and model, place any PDF that is 50 MB
or smaller at `./sample.pdf`, and run the program:

<CodeGroup>
  ```bash Python theme={null}
  export OPENAI_API_KEY="<your-openai-api-key>"
  export OPENAI_CHAT_MODEL="gpt-4.1-mini"

  python transform_agent.py
  ```

  ```bash .NET theme={null}
  export OPENAI_API_KEY="<your-openai-api-key>"
  export OPENAI_CHAT_MODEL="gpt-4.1-mini"

  dotnet run
  ```
</CodeGroup>

The agent works through the tool sequence and prints a summary such as the following. Transforms take from about 10
seconds to several minutes, depending on page count:

```text theme={null}
Discovered tools: request_file_upload_url, transform_files, check_transform_status, get_transform_results
The transformed Markdown output from the document contains 1 element.

Here are the first 500 characters of the Markdown content:

# Dummy PDF file
```

### Use Azure OpenAI as the chat model provider

To use Azure OpenAI instead of OpenAI, keep the Transform MCP connection code the same and change only the chat model
configuration:

<CodeGroup>
  ```bash Python theme={null}
  # The Python OpenAIChatClient reads these environment variables and routes
  # to Azure OpenAI automatically. No code changes are needed.
  export AZURE_OPENAI_ENDPOINT="https://<your-resource>.openai.azure.com"
  export AZURE_OPENAI_API_KEY="<your-azure-openai-api-key>"
  export AZURE_OPENAI_CHAT_MODEL="<your-deployment-name>"
  ```

  ```csharp .NET theme={null}
  // Add the Azure OpenAI package first:
  //   dotnet add package Azure.AI.OpenAI
  // Then replace the OpenAIClient lines with the following:
  using Azure;
  using Azure.AI.OpenAI;

  var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")!;
  var azureKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY")!;
  var deployment = Environment.GetEnvironmentVariable("AZURE_OPENAI_CHAT_DEPLOYMENT")!;

  AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureKey))
      .GetChatClient(deployment)
      .AsAIAgent(
          instructions: Instructions,
          tools:
          [
              .. transformTools.Cast<AITool>(),
              AIFunctionFactory.Create(DescribeLocalFile),
              AIFunctionFactory.Create(UploadLocalFile),
              AIFunctionFactory.Create(DownloadText),
          ]);
  ```
</CodeGroup>

## Troubleshooting

* **`401 Unauthorized` or an `invalid_token` error on connect.** Confirm the `UNSTRUCTURED_API_KEY` environment
  variable is set and that the `Authorization` header is formatted as `Bearer <your-unstructured-api-key>`. In Python,
  also confirm the header is set on the `httpx.AsyncClient` passed through `http_client`, not only through
  `header_provider`.
* **`Session terminated` or `404` when connecting.** Verify the server URL is exactly
  `https://mcp.transform.unstructured.io`, with no path appended.
* **The upload or download request is rejected.** The `upload_url` and `download_url` values are pre-signed. Send those
  two requests without the `Authorization` header.
* **The agent reports the job is still running.** Transforms take from about 10 seconds to several minutes by page
  count. The agent keeps polling `check_transform_status` until the status is `COMPLETED`.
* **A compile or import error on the SDK APIs.** These SDKs are in active development. Install the exact package
  versions pinned in this guide, or update the code to the installed versions' APIs.

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