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.
pip install toaCan be used after installation
toa -h
toa-mcp -helpIt 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]
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 paddleocrUse in command line
toa md.md
toa md.md -o md.html --wrapper default --mermaid --highlight --formulaNote
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/wssfetch from ftp server
toa ftp://ftp.example.com/readme.txtUse it in Python coding
from toa import Toa
toa = Toa()
result = toa.convert('md.md')
print(result.content) # <p>md.md</p>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.
| 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). |
MCP protocol supports
{
"jsonrpc": "2.0",
"method": "tools/list",
"params": {},
"id": 1
}There are multiple ways to integrate.
Microservice architecture
prepare docker-compose.yml
# docker-compose.yml
services:
toa-cli:
image: sunshmo/toa:latest
entrypoint: ["toa"]
command: ["--help"]
stdin_open: true
tty: true
toa-mcp:
image: sunshmo/toa:latest
entrypoint: ["toa-mcp", "--transport", "http"]
ports:
- "56156:56156"
# other servicesBuild and start service
docker-compose up toa-cli
docker-compose up -d toa-mcpAI 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
"sunshmo/toa:latest",
"toa-mcp",
"--transport",
"stdio"
]
}
}
}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/inspectorThe accessible UI address after startup is http://localhost:6274. You can switch the Transport Type to STDIO, SSE, or Streamable HTTP.
STDIO: Enter toa-mcp in the command field and click Connect.
SSE:
- Start service:
toa-mcp --transport http, ortoa-mcp --transport sse, ortoa-mcp --transport wscan all start the web service. - Enter
URL:http://localhost:56156/sse - click
Connect
Streamable HTTP:
- Enter
URL:http://localhost:56156/mcp - click
Connect
Running MCP Web Server Using Docker to build eliminates the hassle of OCR installation. Running toa-mcp service in Docker.
docker run -d \
-p 56156:56156 \
--name toa \
-v /the/host/dir:/data \
sunshmo/toa:latest \
toa-mcp --transport httpVerify 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.
STDIO
docker run --rm -i sunshmo/toa:latest toa < your.emlIn 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:
- Implement a class that inherits from BaseConverter
- then register the converter with Toa
Below is one full example scenario.
plugin example
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.