Skip to main content

Convert documents (txt, markdown, pdf, etc.) to HTML

Project description

Toa is a library for converting documents to HTML.

[!tip] By default, it returns a string-type HTML fragment. You can decide how to render it yourself, such as beautifying the page display.

The built-in HTML templates can return fully structured HTML content, supporting Mermaid stylesheets, formulas, and syntax highlighting, which can be enabled selectively.

Toa supports the following conversions:

  • Ebooks: EPUB
  • Emails: EML, MSG(outlook)
  • Documents: DOCX, PDF, TXT, MD(Markdown)
  • Spreadsheets: XLSX, XLS, CSV
  • Presentations: PPTX
  • Data Exchange: JSON, JSONL
  • Markup Languages: XML, HTML
  • Multimedia: audio files, video files, supports audio-to-text conversion and video subtitles extraction.
  • Resources from the Internet
  • ...

Toa provides convenience for online previewing of documents, emails, data reports, e-books, etc.

Of course, Toa can also convert HTML to Markdown.

Install

Install code dependencies

Install via code, suitable for developers

uv add toa
# or
pip install toa

It may only support following content can be converted to HTML.

  • such as json, jsonl, txt, md, etc.
  • file streams
  • external links (such as web crawlers, FTP, and WebSockets)

Extras: [mcp], [csv], [docx], [epub], [excel], [outlook], [pdf], [pptx], [rss], [audio], [video]

# for convenience, `uv` as the primary tool here
uv add 'toa[mcp, csv, excel]'
  • [lite] supports capabilities other than [video].
  • [all] will install all dependencies. If you don't want to make a choice, this might be the best option, and an additional OCR engine needs to be installed.

with OCR If OCR image recognition is required, you can choose an OCR engine. Such as paddleocr, easyocr, pytesseract, for example:

uv add paddlepaddle paddleocr

Install with docker

Using Docker to build eliminates the hassle of OCR installation.

# from source
# docker build -t toa:latest .

# from the built image
docker pull toa:latest

Using toa CLI capabilities

docker run --rm toa:latest toa md.md

Using toa-mcp server capabilities

Usage

Use in command line

toa md.md

toa md.md -o md.html --wrapper default --mermaid --highlight --formula

[!note] If the file does not exist, it may be treated as a string and ultimately returned as an HTML fragment.

  • --wrapper: HTML fragment wrapper
  • -o: output filename
  • --mermaid: mermaid rendering is required.
  • --highlight: highlight rendering is required.
  • --formula: Requires support for rendering mathematical formulas.

standard input

cat pdf.pdf | toa

cat pdf.pdf | toa --wrapper default -o pdf.html --source-url https://example.com/pdf.pdf --engine default
  • --source-url: An accessible file address ensures the page displays correctly.
  • --engine: rendering engine name

[!important] Some content can only be displayed correctly with server support.

fetch from websocket endpoint

toa ws://examples.com/ws
toa wss://examples.com/wss

fetch from ftp server

ftp://ftp.example.com/readme.txt

Use it in Python coding

from toa import Toa

toa = Toa()
result = toa.convert('md.md')
print(result.content)  # <p>md.md</p>

Plugin

In reality, there may be files that Toa currently does not support for conversion, or you may want to override Toa's default capabilities. In such cases, you can achieve this by using an extension plugin.

You can do like this:

  1. Implement a class that inherits from BaseConverter
  2. then register the converter with Toa

Below is one full example scenario.

from typing import BinaryIO

from toa import Toa, BaseConverter, InputInfo, OutputInfo, ConversionResult


class MyToaConverter(BaseConverter):
    def __init__(self) -> None:
        super().__init__()

    @property
    def check_dependencies(self) -> bool:
        """
        Returns a boolean value. Returns True if there are no external dependencies;
        otherwise, the conversion will throw an error.
        """
        return True

    @property
    def suffixes(self) -> list[str]:
        """Define extension (Format: ['.toa'])"""
        return [".toa"]

    @property
    def mime_types(self) -> list[str]:
        return []

    def convert(
        self,
        input_stream: BinaryIO,
        input_info: InputInfo,
        output_info: OutputInfo | None = None,
        **kwargs,
    ) -> ConversionResult:
        return ConversionResult(content="Result from plugin conversion")


toa = Toa()

# register
toa.add_converter(MyToaConverter())

result = toa.convert("abc.toa")
print(result.content)

[!note] Either suffixes or mime_types must be a value that is not of type NoneType.

[!tip] If you don't want to use the built-in templates, you can implement one or more methods that return HTML strings, and then set wrapper as the method name.

toa-mcp

Toa has built-in MCP capabilities. The corresponding separate command is toa-mcp. toa-mcp is a server based on the MCP (Model Context Protocol) protocol, providing a standardized API interface for the toa command-line tool.

Multi-mode Operation:

  • HTTP: Provides a RESTful API interface, suitable for web integration.
  • SSE: Server sends events, supports real-time push notifications.
  • WebSocket: Bidirectional persistent connection for real-time interaction.
  • Stdio: Standard input/output communication, suitable for integration with AI assistants.

MCP protocol supports

{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "params": {},
  "id": 1
}

toa-mcp Integrations

There are multiple ways to integrate.

As a Docker Service

Build the toa-mcp service using Docker command.

docker run -d \
  -p 56156:56156 \
  --name toa \
  -v /the/host/dir:/data \
  toa:latest \
  toa-mcp --transport http

Verify if the service is usable, visit http://localhost:56156/ping, Seeing "pong" indicates a normal startup.

[!tip] -v option maps a host directory to a container directory, allowing you to use a directory path within the container as the target file path when calling the function.

Microservice architecture

prepare docker-compose.yml

# docker-compose.yml
services:
  toa-cli:
    image: toa:latest
    command: ["toa", "--help"]
    stdin_open: true
    tty: true
  toa-mcp:
    image: toa:latest
    command: ["toa-mcp"]
    ports:
      - "56156:56156"

  # other services

Build and start service

docker-compose up toa-cli
docker-compose up -d toa-mcp

AI assistant integration Such as, Cursor, openclaw, claude, etc.

{
  "mcpServers": {
    "toa": {
      "command": "toa-mcp",
      "args": ["--transport", "stdio"]
    }
  }
}

You can also use the docker command.

{
  "mcpServers": {
    "toa": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "-v",
        "./path/data:/data", // resource directory mapping
        "toa:latest",
        "toa-mcp",
        "--transport",
        "stdio"
      ]
    }
  }
}

Web Service

endpoint method description
/sse Get/POST Message data is transmitted in a streaming manner (Server-to-Client).
/messages GET Standard message retrieval endpoint (Polling/REST).
/mcp GET Streamable HTTP (Model Context Protocol / Long-polling transport).
/ws GET (Upgrade) Full-duplex bidirectional streaming (WebSocket connection for real-time interaction).
/ping GET Check service availability (Heartbeat).

Integration with coding If toa-mcp is already running, then you can call it in the program. No programming language restrictions. For example, below uses HTTP transport.

async def with_http():
    import httpx

    async with httpx.AsyncClient(follow_redirects=True) as client:
        response = await client.post(
            "http://localhost:56156/mcp",
            headers={"Content-Type": "application/json", "Accept": "application/json"},
            json={"jsonrpc": "2.0", "method": "tools/list", "id": 1},
        )
        print("tools:", response.json())
Below is the capability of Java to call toa-mcp
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public class McpHttpClient {
    private static final ObjectMapper mapper = new ObjectMapper();
    
    public static void withHttp() throws Exception {
        HttpClient client = HttpClient.newBuilder()
            .followRedirects(HttpClient.Redirect.NORMAL)
            .connectTimeout(Duration.ofSeconds(10))
            .build();
        
        // build JSON-RPC request
        ObjectNode request = mapper.createObjectNode();
        request.put("jsonrpc", "2.0");
        request.put("method", "tools/list");
        request.put("id", 1);
        
        HttpRequest httpRequest = HttpRequest.newBuilder()
            .uri(URI.create("http://localhost:56156/mcp"))
            .header("Content-Type", "application/json")
            .header("Accept", "application/json")
            .timeout(Duration.ofSeconds(30))
            .POST(HttpRequest.BodyPublishers.ofString(mapper.writeValueAsString(request)))
            .build();
        
        HttpResponse<String> response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString());
        
        if (response.statusCode() == 200) {
            JsonNode result = mapper.readTree(response.body());
            System.out.println("tools: " + result.toPrettyString());
        } else {
            System.err.println("HTTP Error: " + response.statusCode());
        }
    }
    
    public static void main(String[] args) throws Exception {
        withHttp();
    }
}

with web MCP Inspector debug

npx @modelcontextprotocol/inspector

The accessible UI address after startup is http://localhost:6274. You can switch the Transport Type to STDIO, SSE, or Streamable HTTP.

STDIO mode: Enter toa-mcp in the command field and click Connect.

SSE mode:

  • Activate virtual environment: source .venv/bin/activate
  • Start service: toa-mcp --transport http, or toa-mcp --transport sse, or toa-mcp --transport ws can all start the web service.
  • Enter URL: http://localhost:56156/sse
  • click Connect

Streamable HTTP mode:

  • The virtual environment is already activated and does not need to be activated again; the web service is already started.
  • Enter URL: http://localhost:56156/mcp
  • click Connect

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

toa-0.1.0.tar.gz (61.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

toa-0.1.0-py3-none-any.whl (77.8 kB view details)

Uploaded Python 3

File details

Details for the file toa-0.1.0.tar.gz.

File metadata

  • Download URL: toa-0.1.0.tar.gz
  • Upload date:
  • Size: 61.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.8

File hashes

Hashes for toa-0.1.0.tar.gz
Algorithm Hash digest
SHA256 767fb56e1dc583f6ec072eee726c09b9dc6d158fa144ac5cf4d305b08fb02bab
MD5 f66ce31e5514ae82f90cac69af83ec88
BLAKE2b-256 af1b90b46755e5ea89d6e8dbc051ceb0bd7627094d776e446c8212dbb479d5a5

See more details on using hashes here.

File details

Details for the file toa-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: toa-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 77.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.8

File hashes

Hashes for toa-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 652da9a5971916dd4f54f3a703412e9dbde9bebb485831b6463e0cfd7489ba0a
MD5 fe846f8d955fb489bf3f6749a1670c0b
BLAKE2b-256 90127c3b09f844070195fcf118ceda142d27430e185b3b3a911131b548d3d7bf

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page