# Doc To Markdown MCP Server (`abotapi/doc-to-markdown-mcp`) Actor

An MCP server that converts documents to clean Markdown. Convert PDFs, Word docs, Excel spreadsheets, PowerPoints, HTML, images, and more to AI-friendly Markdown format.

- **URL**: https://apify.com/abotapi/doc-to-markdown-mcp.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** MCP servers, Developer tools, Automation
- **Features:** Limited permissions
- **Stats:** 4 total users, 1 monthly users, 0.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Document to Markdown MCP Server: Convert Files and Web Pages into Clean Markdown

This actor is a Model Context Protocol (MCP) server that turns documents into clean Markdown text so an AI agent, chatbot, or script can read them directly. Point it at a PDF, Word file, PowerPoint deck, Excel sheet, HTML page, image, audio file, or one of a dozen other formats, and it hands back Markdown, plus an optional metadata header and table of contents. Start a run, connect an MCP client to that run's own web server, and it answers requests over HTTP and MCP for as long as the run stays active. It can convert one document at a time or up to 20 at once in a single batch call.

### Why This Scraper?

- **Nine MCP tools, one server.** Convert a single URL, a single file already stored in the actor's Key-Value Store, raw HTML, or base64 content, plus batch tools that convert up to 20 URLs or files at once.
- **15+ formats.** PDF, Word, PowerPoint, Excel, RTF, HTML, XML, JPEG, PNG, GIF, BMP, WAV, MP3 (with speech transcription), CSV, JSON, YAML, and ZIP archives with recursive extraction.
- **Runs as a normal Actor run.** Each run serves HTTP and MCP requests on its own container for as long as it stays active (up to the run's timeout), rather than starting a fresh run per document.
- **Formatting controls.** Turn the metadata header and table of contents on or off per call, and choose ATX or Setext (levels 1 and 2 only) heading style.
- **Batch calls don't stop at the first bad file.** A batch call reports which documents converted and which failed, with a per-document error message, instead of aborting the whole batch.
- **No dataset, no export step.** Every tool hands the Markdown straight back in its own response; there is nothing to download afterward.
- **Works with any MCP client.** Point an AI agent, a chatbot, or the Apify MCP proxy at the run's own URL and call the tools directly.

### Use Cases

- **AI agent builders:** turn a retrieved PDF, slide deck, or spreadsheet into Markdown context an LLM can read directly, without a separate parsing step.
- **Documentation and migration teams:** batch-convert a folder of legacy Word and PowerPoint files into Markdown for a docs site or wiki.
- **Research and analysis workflows:** strip a report or web page down to clean text before summarizing or indexing it.
- **RAG pipeline builders:** normalize mixed-format source documents, PDF, HTML, Excel, into one consistent Markdown shape before embedding.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `filename` | `"sample-report.pdf"` |
| `format` | `"pdf"` |
| `size` | `"20480 bytes"` |
| `conversion_time` | `"340ms"` |
| `extracted_at` | `"2024-01-01T00:00:00"` |
| `modified_time` | `"2024-01-01T00:00:00"` |
| `content_type` | `"application/pdf"` |
| `last_modified` | `"Mon, 01 Jan 2024 00:00:00 GMT"` |
| `etag` | `"\"sample-etag\""` |
| `format_description` | `"Portable Document Format"` |
| `total` | `3` |
| `successful` | `2` |
| `failed` | `1` |
| `url` | `"https://example.com/sample-report.pdf"` |
| `content_length` | `"20480"` |
| `status_code` | `200` |
| `key` | `"report.pdf"` |
| `size_bytes` | `20480` |
| `type` | `"str"` |
| `error` | `"File not found: report.pdf"` |

None of these come back as separate JSON fields from a conversion call. A single conversion tool returns one Markdown-formatted text block: a metadata header line for each field above that it managed to fill in (`filename`, `format`, `size`, `conversion_time`, `extracted_at`, `modified_time`, and, for a URL source, `content_type`, `last_modified`, `etag`; `format` and `format_description` both appear again from a second, overlapping metadata pass), then the converted content. A failed single conversion returns plain text (`Error converting <name>: <reason>`) instead. A batch tool wraps several of those blocks inside one summary block (`# Batch Conversion Results`, then `Total`/`Successful`/`Failed` counts, then one `Document N: <name>` section per input with its own `Status` line, and either its metadata block and markdown or its own error). `extract_metadata` is the only tool that returns a plain JSON object instead of text: `url`/`content_type`/`content_length`/`last_modified`/`etag`/`status_code` for a URL, or `key`/`format`/`size_bytes`/`type` for a Key-Value Store key, or just `error` if that key doesn't exist.

### How to Use

1. Start the actor. No Run input is required, you can leave every field empty, since document conversion is not driven by this input form.
2. Open the run's own **Container URL** (shown on the run's Overview tab in the Apify Console once it is running) and add `/mcp` to it.
3. Connect an MCP-compatible client, an AI agent, a chatbot, or the Apify MCP proxy, to that URL with your Apify token, then call one of the tools listed under Input Parameters, for example `convert_url_to_markdown`, with the document you want converted.
4. Read the Markdown straight out of the tool's response. Nothing is written to a dataset, so there is no separate export or download step. The run keeps serving requests until you abort it or it reaches its own timeout, so start a fresh run once that happens.

The Run input below is optional and this build never reads it, every conversion happens through the MCP tool calls in step 3, so most users leave it empty.

**Leave it empty (recommended for MCP usage):**

```json
{}
```

**Pre-fill a URL, kept for schema completeness only, this build does not read it:**

```json
{
  "urls": ["https://example.com/sample-report.pdf"]
}
```

**Pre-fill the default output formatting, also kept for schema completeness only:**

```json
{
  "outputOptions": {
    "include_metadata": true,
    "include_toc": false,
    "heading_style": "atx"
  }
}
```

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/doc-to-markdown-mcp").start()
print(run["containerUrl"])
## The run never finishes on its own, it just keeps serving requests until its
## timeout, so do not call() and wait on it. Point an MCP client at
## run["containerUrl"] + "/mcp" with your Apify token instead.
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/doc-to-markdown-mcp').start();
console.log(run.containerUrl);
// The run never finishes on its own, it just keeps serving requests until its
// timeout, so do not call() and wait on it. Point an MCP client at
// `${run.containerUrl}/mcp` with your Apify token instead.
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `files` | array | none | Optional file uploads, stored in the actor's Key-Value Store. This build does not read this field to trigger a conversion, it is a placeholder for a possible future non-MCP batch mode. |
| `urls` | array | none | Optional list of document URLs. Like `files`, this build does not read this field either, conversions happen through the MCP tool calls below, not this input form. |
| `outputOptions` | object | `{"include_metadata": true, "include_toc": false, "heading_style": "atx"}` | Optional. Mirrors the `include_metadata`, `include_toc`, and `heading_style` arguments the MCP tools accept, kept here for schema completeness; it is not applied automatically to a run. |

> Note: the Apify Console's own input form for this actor currently shows three different fields (default include-metadata, default include-TOC, default heading style) rather than the ones above. Neither that form nor the fields documented here are read by this build; every conversion happens only through the MCP tool calls below, and the Run input can always be left empty.

#### Calling the MCP tools directly

Once connected, call any of these 9 tools with their own arguments, separate from the Run input above:

- `convert_url_to_markdown(url, include_metadata, include_toc, heading_style, table_format, image_handling)`: download one document from a URL and convert it. `table_format` and `image_handling` are accepted but currently have no effect on the output.
- `convert_file_to_markdown(key, store_name, include_metadata, include_toc)`: convert one file already stored in an Apify Key-Value Store.
- `convert_base64_to_markdown(content, filename, include_metadata, include_toc)`: convert a document sent as base64 text.
- `convert_html_to_markdown(html, include_metadata, include_toc)`: convert a raw HTML string.
- `batch_convert_urls(urls, include_metadata, include_toc)`: convert up to 20 URLs in parallel; anything beyond the first 20 is silently dropped, not reported as failed.
- `batch_convert_files(keys, store_name, include_metadata, include_toc)`: convert up to 20 Key-Value Store files in parallel; anything beyond the first 20 is silently dropped, not reported as failed.
- `extract_metadata(url, key)`: return size, content type, and last-modified date without converting. Unlike the conversion tools above, an unreachable URL here can surface as a protocol-level tool error rather than a normal response.
- `get_supported_formats()`: list every supported file format.
- `get_output_options()`: list the formatting options, including `table_format` and `image_handling`, which are listed here but currently do nothing.

Example call to `convert_url_to_markdown`:

```json
{
  "url": "https://example.com/sample-report.pdf",
  "include_metadata": true,
  "include_toc": false,
  "heading_style": "atx"
}
```

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "content": [
    {
      "type": "text",
      "text": "---\nfilename: sample-report.pdf\nformat: pdf\nsize: 20480 bytes\nconversion_time: 340ms\nextracted_at: 2024-01-01T00:00:00\nmodified_time: 2024-01-01T00:00:00\ncontent_type: application/pdf\nlast_modified: Mon, 01 Jan 2024 00:00:00 GMT\netag: \"sample-etag\"\nformat: PDF\nformat_description: Portable Document Format\n---\n\n# Sample Report\n\nThis is a sample paragraph converted from the source file."
    }
  ],
  "structuredContent": {
    "result": "---\nfilename: sample-report.pdf\nformat: pdf\nsize: 20480 bytes\nconversion_time: 340ms\nextracted_at: 2024-01-01T00:00:00\nmodified_time: 2024-01-01T00:00:00\ncontent_type: application/pdf\nlast_modified: Mon, 01 Jan 2024 00:00:00 GMT\netag: \"sample-etag\"\nformat: PDF\nformat_description: Portable Document Format\n---\n\n# Sample Report\n\nThis is a sample paragraph converted from the source file."
  },
  "isError": false
}
```

This is a URL conversion, so it carries `content_type`, `last_modified`, and `etag`; a file, base64, or HTML conversion leaves those out. `format` appears twice, once from the conversion result itself and once from a second metadata pass; both are shown as-is above rather than de-duplicated. Newer MCP client libraries also add a `structuredContent` field mirroring the same text, shown above; older ones only return `content`.

### Plan Requirement

This actor takes no proxy configuration, there is not one in its input, so there is nothing to pick under Connection. Billing is a charge each time the actor starts (it scales with the memory you give it), plus a small charge for anything written to the dataset (this actor never writes to one, so that charge does not apply in practice). It is not a per-document or per-field charge. Check the **Pricing** tab on the actor page for the current rates.

### FAQ

#### How much does it cost?

You pay a charge each time the actor starts, which scales with how much memory you give it, shown on the **Pricing** tab. There is no per-document charge, and no dataset charge in practice since this actor never writes to a dataset.

#### Is it legal to convert a document I don't own the rights to?

This actor only converts the file or page you give it; it does not search for or discover documents on its own. You are responsible for how you use it: only feed it documents you have the rights or permission to process, and check the source document's own license or copyright terms before you redistribute the converted output.

#### Why doesn't anything happen when I start a run without connecting an MCP client?

The Run input fields (`files`, `urls`, `outputOptions`) are optional and this build does not read them. Nothing converts until an MCP client connects to that run's own URL and calls one of the tools listed under Input Parameters. Starting the actor on its own just brings the server online and waiting, until the run's timeout is reached.

#### What happens if a document fails to convert?

For the four conversion tools and the two batch tools, a failed conversion, an unreachable URL, a missing Key-Value Store key, or an unsupported or corrupted file, is caught and returned in the same tool response as an error message naming the file and the reason, rather than raised as a protocol-level error, and does not fail the run. A batch call keeps going after a failed document and reports per-document success and failure counts instead of stopping. `extract_metadata`, which does not convert anything, does not have the same protection for an unreachable URL and can surface a protocol-level tool error instead. A corrupted file that still looks like a valid one of its type is not always caught; some corrupted input converts without an error into unreadable output rather than failing outright.

#### Can I use this with AI agents or other MCP clients?

Yes. This actor is itself an MCP server: connect any MCP-compatible client, an agent framework, a chatbot, or the Apify MCP proxy, to that run's own URL and call the tools directly.

### 🔗 Want more travel data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>🤖 <a href="https://apify.com/abotapi/doc-to-markdown"><b>Doc To Markdown</b></a><br>Convert documents (PDF, Word, PowerPoint, Excel, HTML, images) to clean Markdown...</td><td>🏨 <a href="https://apify.com/abotapi/airbnb-stays-reviews-scraper"><b>Airbnb Scraper</b></a><br>Scrape Airbnb stays by destination or link: nightly and total price, rating breakdown...</td></tr>
<tr><td>🏨 <a href="https://apify.com/abotapi/yanolja-com-scraper"><b>Yanolja Scraper</b></a><br>Scrape Yanolja (NOL), Korea's largest accommodations marketplace, by region or URL. Every...</td><td>🏨 <a href="https://apify.com/abotapi/klook-activities-scraper"><b>Klook Scraper</b></a><br>Scrape Klook activities, tours, attractions and travel experiences from search pages or...</td></tr>
<tr><td>🏨 <a href="https://apify.com/abotapi/rover-pet-care-scraper"><b>Rover Pet Care Scraper</b></a><br>Scrape Rover.com pet-care providers: sitter search by city, rates per stay type, ratings...</td><td>🏨 <a href="https://apify.com/abotapi/vrbo-vacation-rentals-scraper"><b>Vrbo Scraper</b></a><br>Extract vacation rental data from Vrbo.com by location, region, coordinates, property...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/doc-to-markdown-mcp/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `defaultIncludeMetadata` (type: `boolean`):

Include file metadata header in converted output by default

## `defaultIncludeToc` (type: `boolean`):

Generate table of contents in converted output by default

## `defaultHeadingStyle` (type: `string`):

Default heading style for markdown output

## Actor input object example

```json
{
  "defaultIncludeMetadata": true,
  "defaultIncludeToc": false,
  "defaultHeadingStyle": "atx"
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/doc-to-markdown-mcp").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/doc-to-markdown-mcp").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call abotapi/doc-to-markdown-mcp --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/doc-to-markdown-mcp"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/36uGUyHwMNJ1CVQT1/builds/rIAWCGdOuAcMVrfA6/openapi.json
