`: it becomes a native InDesign table with a paragraph style per cell.
- Paragraph styles are named from the tag and the element's **first** class (`` gives "Body · caption"). To name one outright, add `data-idml-style="Cover Dates"`.
- Boxes that hold one continuous text can be linked: give each the same `data-idml-thread="cv"` and they convert as one threaded story, frame to frame, in document order (or `data-idml-thread-order="1"`, `"2"`, ...). Every frame but the last is fixed at the size of its text, so an added line pushes text on to the next frame. If a box starts mid-paragraph, add `data-idml-thread-continues` and its first paragraph is joined to the last one of the box before. InDesign recomposes a threaded story, so a line can move across a frame boundary compared with the HTML.
- An inline `display: inline-block` element inside a paragraph is a box of its own and becomes its own frame. Use a plain `` for a run that only changes font or colour.
## When it fails
| result | meaning | what to do |
|---|---|---|
| 402 without a key | the free trial is used up for this design or this hour | the result carries the price and checkout link |
| 402 with a key | the key was not accepted | check the key; if it worked before, delete `~/.idmly/instances.json` so the next call activates again |
| 413 | over the size cap (15 MB trial, 50 MB licensed) | downscale or re-encode images, or host them and use absolute URLs |
| 422 | nothing rendered, or a script never finished | inline the missing assets; remove the runaway script |
| 429 / 503 | rate limit, or the license service is unreachable | retry after `retry_after_seconds` |
The engine sleeps when idle. The tool waits for it to wake (up to ~25 s) before uploading, and if that first upload after a quiet spell is dropped it is sent once more on its own, so a cold start costs a pause, not an error.
Large designs can take a couple of minutes to render. Some clients cut tool calls at 60 seconds by default; raise your client's per-tool timeout (for example `MCP_TOOL_TIMEOUT` in Claude Code) for big documents.
## Environment
| variable | default | purpose |
|---|---|---|
| `IDMLY_LICENSE_KEY` | none (free trial) | your license key from idmly.com |
| `IDMLY_OUT_DIR` | next to the source | default output directory |
| `IDMLY_ENGINE_URL` | the hosted engine | override for a self-hosted engine |
The key is activated on first use. The activation id is kept in `~/.idmly/instances.json` under a hash of the key; the key itself is never written to disk by this tool. Each machine that runs the server uses one activation from the license's allowance.
## Hosted endpoint
If you would rather not run a local process, the engine also speaks MCP over Streamable HTTP:
```
https://idmly-production.up.railway.app/mcp
Authorization: Bearer (omit the header for the free trial)
```
In claude.ai (or the Claude desktop app): **Customize → Connectors → Add custom connector**, paste the URL, choose **No sign-in**, then under **Request headers** pick `authorization` and enter the value `Bearer `, with the word `Bearer` and a space in front of the key. Claude sends the value exactly as typed and adds no scheme of its own; the engine also reads a bare key, so a value of just `` works too. Any other shape (`Basic …`, `Token …`) still connects, and the first conversion answers with what to change instead of running as the trial. If the dialog has no **Request headers** section, your Claude organization does not have that beta yet; use the local server above (`npx idmly-mcp` with `IDMLY_LICENSE_KEY`) instead.
Because a remote tool cannot write to your disk, that endpoint returns a one-time download link instead of a file path: the file is deleted as soon as it is fetched, or after ten minutes unfetched. The same endpoint works with the OpenAI Responses API `mcp` tool and the Agents SDK.
Plain REST is `POST /convert` with a multipart `file` (or `url`), plus `license_key` and `instance_id`. The first licensed response carries an `X-Idmly-Instance` header; send that value back as `instance_id` on every later call, or each call activates a new device against the license. The response is the `.idml` itself, or `application/zip` (containing `converted.idml` and `Links/`) when the design has images.
## Privacy
Uploaded designs are deleted from the engine as soon as the converted file has been returned. On the hosted MCP endpoint the converted file waits behind a one-time, unguessable link for up to ten minutes so the agent can fetch it, then it is deleted. No accounts. The engine keeps a first-party usage ledger (counts, outcome, country), never your file or your key.
## Development
```bash
npm install
npm test # type-checks, bundles, then runs the client against a stub engine (no network)
```
The published package has no runtime dependencies: `npm run build` bundles the
MCP SDK and zod into `dist/index.js`, so `npx` users get exactly the file the
tests ran against. Dependency updates arrive as a weekly Dependabot pull request;
merging it publishes a new patch version from GitHub Actions with provenance.
## License
MIT for this client. The idmly engine is a hosted service, see [idmly.com/legal](https://www.idmly.com/legal.html).