tool-convert
Provides document conversion to and from Markdown: converts supported document formats to structured Markdown, converts Markdown to DOCX/ODT/PDF, and renders Markdown to sanitised HTML fragments.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@tool-convertconvert report.docx to markdown"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
@awacloud/tool-convert
Document conversion (@awacloud/oconv) at the command line and over MCP.
The CLI runs on bun, node and deno, from a repository checkout or from
an installed copy (see "Running it" below); the MCP server runs on
bun.
bun src/index.ts to-md <file> [--format <fmt>] [--at <iso>] [--out <file>]
bun src/index.ts from-md <file.md> --target <fmt> [--out <file>]
bun src/index.ts convert <file> --target <fmt> [--format <fmt>] [--out <file>]
bun src/index.ts to-html <file.md> [--out <file>]
bun src/index.ts --help | -h
bun src/index.ts --version | -vDocumentation: the package guide is docs/README.md,
the full API reference (core functions, types, CLI verbs, MCP tools) is
docs/api/, and changes are recorded in
CHANGELOG.md.
Installation
npm install @awacloud/tool-convertIt has no bin field: run its entry file
by path with bun, node or deno (see "Running it" below and "Exposed
surface" at the end).
Related MCP server: GroupDocs.Conversion MCP Server
Quick Start
From the package directory, convert a document to Markdown, then the
Markdown to PDF. report.docx below is a file you supply — the published
package ships no sample document; from a repository checkout, the committed
tests/fixtures/sample.docx works the same way and is what produced the
output shown:
bun src/index.ts to-md report.docx --at 2026-01-01T00:00:00.000Z --out report.md
bun src/index.ts from-md report.md --target pdf --out report.pdf
# stderr: convert: 1 loss recorded (the YAML front matter: frontmatter/stripped)Or from code (the package's only import path is @awacloud/tool-convert,
which resolves to src/core.ts; the snippet is TypeScript source consumed
as is, so run it with bun):
import { readFileSync } from 'node:fs';
import { isCoreError, toMd } from '@awacloud/tool-convert';
const result = await toMd({
name: 'report.docx',
bytes: new Uint8Array(readFileSync('report.docx')),
convertedAt: new Date().toISOString(),
});
if (isCoreError(result)) throw new Error(result.error);
console.log(result.lossy, result.markdown.split('\n').find((l) => l.startsWith('# ')));
// false # Sovereign RAG ingestionRunning it
The CLI is one file, src/index.ts, runnable directly by each runtime from
a checkout with no build step and no adapter flags — everything
runtime-specific (file I/O, argv, stdout/stderr, exit codes, SIGINT/SIGTERM)
is consolidated in src/runtime.ts. Each command line below was actually
run, not inferred:
# bun
bun src/index.ts to-md file.docx --out file.md
# node — no flags needed (node:fs/node:path/process/import.meta.main
# already give this file everything it uses)
node src/index.ts to-md file.docx --out file.md
# deno — two permissions, each because of a concrete call this CLI makes:
# --allow-read readFileSync() on the input file and on ../package.json
# --allow-write writeFileSync() when --out is given
# (measured: all four verbs run under --no-prompt with only these two)
deno run --allow-read --allow-write src/index.ts to-md file.docx --out file.mdNo broader permission (-A/--allow-all, --allow-net) is needed or
requested: this CLI never opens a socket.
Installed copy: the published package carries the TypeScript sources
(src/) and a plain-JavaScript transpile of them (dist/, one file per
source, produced when the package is exported and never committed, so a
checkout has no dist/). Node 24.16.0 and Deno 2.8.3 refuse to execute
TypeScript located under a node_modules directory
(ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING), so an installed copy runs
through dist/ on those two and through src/ on bun:
node node_modules/@awacloud/tool-convert/dist/index.js to-md file.docx --out file.md
deno run --allow-read --allow-write node_modules/@awacloud/tool-convert/dist/index.js to-md file.docx --out file.md
bun node_modules/@awacloud/tool-convert/src/index.ts to-md file.docx --out file.mdimport '@awacloud/tool-convert' picks the right file by itself: the
exports map sends bun to src/core.ts and every other runtime to
dist/core.js. Measured on an offline install of the packed tarball (and
its packed dependencies) with bun 1.3.13, node 24.16.0 and deno 2.8.3.
Versions measured: bun 1.3.13, node 24.16.0, deno 2.8.3 (re-run
2026-10-03, Windows station). tests/runtime-matrix.integration.test.ts
runs the same fixture through every runtime installed on the machine and
self-skips the ones that are not (never reports a skipped runtime as
passing) — see that file's module doc for exactly what byte-identity means
for zip-container (docx/odt) targets, where @awacloud/oconv's zip
writer stamps a wall-clock timestamp per entry (a pre-existing upstream
non-determinism, not a runtime difference: two bun runs back to back
already differ the same way). to-md, to-html and any pdf target are
byte-identical outright; docx/odt targets are identical once that one
known timestamp field is masked out.
SIGINT/SIGTERM: src/runtime.ts installs both handlers unconditionally
on every runtime. The matrix test's signal-delivery checks self-skip on
Windows (this station) because POSIX signal delivery to a child process via
child_process.kill('SIGINT'|'SIGTERM') is documented as unreliable there
for bun, node and deno alike — they were not exercised on this station; a
POSIX host runs them for real.
Verbs
to-md <file>— any supported input format to structured Markdown (@awacloud/oconv'stoMd). Supported input formats:docx,odt,xlsx,ods,pptx,odp,pdf.from-md <file.md> --target <fmt>— Markdown to a target format (oconv.fromMd). Supported targets:docx,odt,pdf.convert <file> --target <fmt>— a cross-format pair, direct (oconv.convert). Supported pairs:docx>odt,odt>docx,docx>pdf,odt>pdf— no other source/target combination is accepted, including anyxlsx/ods/pptx/odp/pdfsource and any same-format pair.to-html <file.md>— Markdown to HTML through@awacloud/md'srenderHtmlMod(via themdfacade's.renderHtml) — a plain HTML fragment, no embedded CSS, no table of contents. Safe and sanitised: raw HTML is dropped (a raw block with its content, inline tags keeping their text),javascript:,vbscript:,file:anddata:URLs (images included) become an emptyhref/src, anything outside the fw sanitiser allowlist is removed, and task-list checkboxes stay as disabled inputs keeping their checked state.
Format/target detection is by file extension, with an explicit --format
override (to-md/convert) always winning. convert's target is never
derived from <file>'s own name — it names the SOURCE, not the target —
and is REQUIRED. Format/target/pair validation is @awacloud/oconv's own:
this tool never duplicates it, it forwards the request and turns a thrown
Error into the CLI's exit code + message.
--at <iso> (to-md only) pins the provenance timestamp that
oconv.toMd's convertedAt requires — @awacloud/oconv never defaults it
itself (reproducibility of the markdown output is caller-owned). Omit it
for the current instant. It must be a strict RFC 3339 date-time (e.g.
2026-01-01T00:00:00.000Z, uppercase T/Z, calendar-valid, no leap
second); anything else is a usage error (exit 2), checked before any
file is opened. A valid value is kept verbatim, never normalised.
stdout / stderr discipline
With no --out, the converted bytes go to stdout so the tool composes
in a shell pipeline. Every diagnostic, warning and the loss count go to
stderr. A conversion that records fidelity losses still exits 0 —
losses are output, not failure. With --out, the bytes are written to that
file instead (never duplicated to stdout); the file's bytes are
byte-identical to what stdout would have carried for the same input and
--at. No ANSI colour escape is ever written to stdout.
Exit codes
Code | Meaning |
0 | success (including a lossy conversion); |
1 | generic error (unknown command, a non-usage runtime failure such as an unreadable input file) |
2 | usage/config error (missing command, missing file argument, missing |
130 | SIGINT |
143 | SIGTERM |
Supported-format source of truth
The oconv facade of @awacloud/oconv is the single source of truth for
which formats/targets/pairs are supported — this tool never hard-codes a
second copy of the validation. It forwards the caller's file name and
lets oconv detect the format/target itself, never pre-validating, and
turns oconv's own unsupported format / unsupported target /
unsupported pair errors into the CLI's exit code 2.
One caveat, stated plainly: oconv exposes no public constant
listing those formats/targets/pairs — its SUPPORTED_FORMATS /
SUPPORTED_TARGETS / SUPPORTED_PAIRS sets are declared inside its
facade's factory closure and are not part of its published exports (the
package root and its worker module only). The lists this tool's --help and
error messages print (docx, odt, xlsx, ods, pptx, odp, pdf /
docx, odt, pdf / the four cross-format pairs) are a maintained mirror
of oconv's own sets and of its loss matrix and convert guide (linked
under "Bounded claims" below) — for the human-facing hint only, never for
gating a request. The mirror is exported as TO_MD_FORMATS,
FROM_MD_TARGETS and CONVERT_PAIRS.
Bounded claims
This tool advertises no conversion pair @awacloud/oconv does not
measurably deliver, and lossy pairs say so (via the stderr loss count) —
see @awacloud/oconv's
loss matrix
for the fidelity table per pair, and its
convert guide
for the cross-format pairs.
MCP server
src/mcp.ts exposes the same four operations as a stdio, zero-dependency
MCP server (newline-delimited JSON-RPC 2.0) — same transport discipline,
same error shape, same "one tool call, one measured answer" contract as any
other stdio MCP server, and fully self-contained.
bun src/mcp.tsThe server runs on bun only: it reads stdin through Bun.stdin, and
under node 24.16.0 and deno 2.8.3 it starts, prints its stderr banner and
exits 1 with convert-mcp: fatal: Bun is not defined (measured). It
answers initialize, tools/list, tools/call, ping and the two
notification methods; every other method returns METHOD_NOT_FOUND. It
never throws across the transport: a bad argument or a failed conversion
both come back as a tools/call result with isError: true and a
structured { "error": "...", "usage": true|false } JSON payload — the
same distinction src/core.ts and the CLI make between a usage/config
error and a generic one.
Tools
Tool | Needs | Loses |
|
| presentation/layout formatting and most inline styling beyond basic emphasis/headings — see the result's |
|
| nothing this step introduces on its own — only as rich as the input Markdown; see |
|
| whatever the docx/odt/pdf loss matrix records for that pair — see |
|
| raw HTML (a block with its content; inline tags keep their text); |
The tool names are the MCP tool registry of src/mcp.ts (TOOLS), not
functions of src/core.ts: each tool calls the matching core function
(toMd, fromMd, convert, toHtml).
Each tool's full description (returned by tools/list) restates its needs
and losses in prose, and its inputSchema lists every property with a
one-line description — a model can choose between the four without reading
src/mcp.ts.
Loss data
to_md, from_md and convert are backed by @awacloud/oconv, which
tracks fidelity losses per conversion: every successful result of these
three carries losses (an array) and lossy (a boolean) from src/core.ts
— inline with the content, or alongside the file-mode payload described
below, never as a side channel a caller has to opt into. to_html carries
no losses field: @awacloud/md's renderer drops content (raw HTML,
javascript:, vbscript:, file: and data: URLs, anything outside the
sanitiser allowlist) but does not itemise it, so there is no list to
return — the absence is a limit of the renderer, not a lossless render.
Large-output rule
A conversion result can be far larger than a tool response should carry.
Every tool accepts an optional outPath: when given, the output is
always written there instead of returned inline (mirrors the CLI's
--out). When outPath is omitted:
Text output (
to_md's markdown,to_html's HTML) underMAX_INLINE_TEXT_CHARS(100,000 characters) comes back inline ("mode": "inline"). At or above that size, the full content is written to a fresh temp file and the result carries"mode": "file",outPath,lengthand an explicitpreview(first 2,000 characters) plus anotestating the redirection — the preview is a clearly labelled excerpt, never a silent cut.Binary output (
from_md's andconvert's bytes) underMAX_INLINE_BYTES(75,000 bytes) is base64-encoded inline ("mode": "inline",bytesBase64). At or above that size, the full bytes are written to a temp file and the result carries"mode": "file",outPath,bytesLengthand anote— binary output is never truncated in place of redirection (a truncated document is a corrupt one), only redirected, in full.
Temp files created this way are not deleted by the server — the caller may not have read the path yet when the tool call returns; cleanup is the caller's or the OS's, same as any other tool that hands back a path.
Tests
The tests live in the repository only (the published package ships no test file). From the package directory:
bun test src/ tests/ # everythingSuite | Covers |
| each core operation, the error-as-data contract, the |
| the descriptor adapter: real oconv/md arrays pass, malformed entries fail by name |
| the strict RFC 3339 check behind |
| MCP dispatch, schemas, large-output helpers |
| the CLI as a spawned process: streams, |
| a real stdio JSON-RPC session |
| bun, node and deno give the same bytes and exit codes |
| importing the package has no side effect ( |
|
|
| the shipped files carry no internal reference; their links resolve; this README's Installation, Licence and Project sections quote the manifest |
Limitations
No Bun-compiled single executable — deferred to a future program for licence reasons. Node and deno run the CLI directly from source in a checkout, same as bun, and from
dist/in an installed copy (see "Running it" above).The MCP server runs on bun only (see "MCP server" above).
The package declares no
binfield, so installing it puts no command on thePATH.
Exposed surface
The package declares exactly one exports key, so its import surface is the
single row below; the CLI and the MCP server are entry files you run by
path, not exports.
Sub-path | Target | Usage |
|
| the programmatic API: |
Command entry points (files you run, never imported through the package name):
CLI —
src/index.ts(dist/index.jsfor node and deno in an installed copy): the four verbsto-md,from-md,convertandto-html, plus--helpand--version; invoked as "Running it" documents. Importing it runs nothing: the CLI body starts only when the file is the process entry point.MCP server —
src/mcp.ts: the stdio server described under "MCP server", started withbun src/mcp.ts.
The package's package.json declares no bin field: npm install
creates no convert command, and the two files above are the whole command
surface. Its main field names src/index.ts, but exports takes
precedence, so import '@awacloud/tool-convert' resolves to src/core.ts
on bun and to dist/core.js elsewhere. src/runtime.ts,
src/descriptors.ts and src/timestamp.ts (and their dist/ transpiles)
are internal modules of the two entry points, not part of the surface.
Maturity
L3, as declared in package.json awa.maturity. The package's own
sources are held to a line-coverage floor of 0.92 by the repository coverage
gate (awa.coverageFloor); the package itself is private and not yet
published.
Licence
AGPL-3.0-only — see LICENSE in this package.
Copyright (c) 2026 AwaCloud SAS
Project
Website: https://awaforge.eu
Source:
tools/convertIssues: https://github.com/awacloud/awa/issues (the package declares no
bugsURL of its own)Security policy and release verification: https://github.com/awacloud/awa/blob/@awacloud/tool-convert@1.0.0/SECURITY.md
This server cannot be deployed
Maintenance
Related MCP Connectors
Document conversion MCP server: PDF to Markdown, image OCR, spreadsheet parsing.
Generate PDF, Word (.docx) and PowerPoint (.pptx) documents from Markdown over MCP.
Document-to-Markdown MCP server — convert PDF, Office and HTML into LLM-ready Markdown.
Normalize and convert more than 400 file types via TweekIT's hosted MCP streamable HTTP endpoint.
Related MCP Servers
- AlicenseAqualityDmaintenanceEnables document format conversion between Word, Markdown, PDF, HTML, and plain text, supporting batch operations and format validation via the AI MCP protocol.411 npm1MIT
- AlicenseNot gradedqualityAmaintenanceEnables local document conversion between 70+ formats (PDF, Word, Excel, etc.) via MCP, keeping files private.MIT
- FlicenseAqualityBmaintenanceEnables broad media and document format conversion (audio, video, images, Office, data, ebooks, PDF, subtitles) through an MCP server with smart routing, batch processing, and multiple conversion engines.145 npm-
- AlicenseNot gradedqualityAmaintenanceEnables document conversion and processing through MCP, including Office/PDF/Markdown conversions, OCR, and PDF operations like split, rotate, encrypt, and extract.MIT