Skip to main content
Glama
README.md
# Assembly MCP

A small [Model Context Protocol](https://modelcontextprotocol.io) server that
gives any MCP client (Claude Code, Claude Desktop, …) document-conversion and assembly-app upload
tools:

| Tool | Direction | Engine |
|------|-----------|--------|
| **`convert_pdftomd`** | PDF → Markdown | assembly-app `pdf2md` CLI (pdf.js) |
| **`convert_mdtopdf`** | Markdown → PDF | [WeasyPrint](https://weasyprint.org) |
| **`upload_issue` / `issue_draft`** | files → draft issue → issued | assembly-app `upload-issues` CLI |
| **`upload_renders`** | images → Renders tab | assembly-app `upload-renders` CLI |
| **`dwg_to_geomap`** | Revit DWG → lines on Site → Revit Maps | assembly-app `upload-dwg-geomap` CLI (DWG converted by civil-map-service) |

`convert_pdftomd` and the upload tools run the assembly-app CLIs from **self-contained bundles in
`cli/bin/*.mjs`** (built by `cli/build.mjs`), so **no assembly-app checkout is needed** to use them —
just Node ≥ 22. The bundles talk straight to the app's server (Supabase at `newapi-next.assembly.nz`)
as the signed-in person. See [Building the CLI bundles](#building-the-cli-bundles). `convert_mdtopdf` renders
Markdown/HTML → PDF with WeasyPrint (pure-Python, no TeX toolchain).

---

## Requirements

- **Python ≥ 3.11**
- **[uv](https://docs.astral.sh/uv/)** (recommended) or `pip`
- **Node ≥ 22** — runs the bundled CLIs in `cli/bin/`. `convert_pdftomd` also needs
  `cd cli && npm ci --omit=dev` once (pdf.js + the native canvas module).
- An **assembly-app checkout is optional**: only used when a bundle is missing (falls back to
  `<ASSEMBLY_APP_DIR>/apps/frontend/scripts/<name>.ts`) and for `env="dev"` config (its
  `apps/frontend/.env.local`).
- **WeasyPrint native libraries** — only needed for `convert_mdtopdf`
  (`convert_pdftomd` works without them):
  - **Linux (Debian/Ubuntu):** `apt install libpango-1.0-0 libpangoft2-1.0-0 libgdk-pixbuf-2.0-0 libffi-dev`
  - **macOS:** `brew install pango gdk-pixbuf libffi`
  - **Windows:** install the
    [GTK3 runtime](https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases).
    Without it, `convert_pdftomd` still works and `convert_mdtopdf` returns a
    clear "install GTK" error.

---

## Install

```bash
git clone https://github.com/AssemblyJustin/assembly-mcp.git
cd assembly-mcp
uv sync            # creates .venv and installs everything
```

Run the server directly to confirm it starts (it speaks MCP over stdio and will
wait for a client — `Ctrl-C` to exit):

```bash
uv run assembly-mcp
```

---

## Add to Claude Code

From anywhere, register the server (adjust the path to your clone):

```bash
claude mcp add assembly -- uv --directory /ABSOLUTE/PATH/TO/assembly-mcp run --no-sync assembly-mcp
```

(`--no-sync`: on Windows, a second Claude session's `uv run` otherwise tries to reinstall the
`assembly-mcp.exe` the first session is running, fails with "file in use", and the server never
connects. Run `uv sync` yourself after pulling.)

Then the tools are available in your session. This repo also ships matching slash commands — run
Claude Code from inside the repo (or copy `.claude/commands/*` into your project) to use:

```
/convert-pdftomd  report.pdf  report.md
/convert-mdtopdf  notes.md     notes.pdf
/upload issues
/upload renders
/dwgtogeomap
```

## Add to Claude Desktop

Edit `claude_desktop_config.json`
(**macOS:** `~/Library/Application Support/Claude/`,
**Windows:** `%APPDATA%\Claude\`) and add:

```json
{
  "mcpServers": {
    "assembly": {
      "command": "uv",
      "args": ["--directory", "C:\\ABSOLUTE\\PATH\\TO\\assembly-mcp", "run", "--no-sync", "assembly-mcp"]
    }
  }
}
```

Restart Claude Desktop. The tools appear under the 🔌 tools menu.

---

## Tools

### `convert_pdftomd`

Convert a PDF file to Markdown. PDF→Markdown runs the assembly-app pdf2md
converter (pdf.js) — headings, lists, ruled tables, images (saved to
`<stem>_images/`). Requires node ≥ 22 and `cli/` installed (`npm ci --omit=dev`).

| Argument | Type | Default | Description |
|----------|------|---------|-------------|
| `pdf_path` | string | — | Path to the source `.pdf`. |
| `output_path` | string | `null` | Optional path to also write the `.md`. |
| `strip_watermarks` | bool | `true` | Kept for compatibility; always on in the unified converter. |
| `front_matter` | bool | `true` | Kept for compatibility; always on in the unified converter. |

Returns the Markdown text (prefixed with a `<!-- pdf2md: … -->` summary
comment giving page/table/image counts and the output path).

> This is a code-only extraction. It does not run vision-based verification,
> so treat the output as a high-quality first pass, not a certified copy.

### `convert_mdtopdf`

Convert Markdown to a PDF file. Provide **either** `md_path` **or**
`markdown_text`.

| Argument | Type | Default | Description |
|----------|------|---------|-------------|
| `output_path` | string | — | Where to write the `.pdf` (required). |
| `md_path` | string | `null` | Path to a source `.md`. |
| `markdown_text` | string | `null` | Raw Markdown (alternative to `md_path`). |
| `title` | string | `null` | Document title (falls back to front-matter `title`). |
| `css` | string | `null` | CSS to replace the built-in print stylesheet. |

Renders Markdown → HTML → PDF with a clean A4 print stylesheet (tables, code
blocks, page numbers) and verifies the PDF magic bytes before writing.

### `upload_issue` / `issue_draft` / `upload_renders` / `dwg_to_geomap`

Upload to an assembly-app project **as the person doing it**. These are thin wrappers over the
assembly-app CLIs `upload-issues`, `upload-renders` and `upload-dwg-geomap`, run from the bundles in
`cli/bin/` (node ≥ 22; no checkout). Each signs in as `uploader_email`, so the database's own
permission rules apply: people can only upload to projects they're on, and viewers and clients can't upload.
The slash command `/upload issues` / `/upload renders` (`.claude/commands/upload.md`) drives them;
`/dwgtogeomap` (`.claude/commands/dwgtogeomap.md`) drives `dwg_to_geomap`.

| Tool | Does |
|------|------|
| `upload_issue(project, issue_type, issue_name, files, env="prod", dry_run=False, uploader_email=None)` | Files → a **draft** issue (Transmittal) on the project's Documents tab. Returns `collectionId` + `recipientChoices`. Never issues. |
| `issue_draft(collection_id, issue_type, recipients=[], cover_notes=None, env="prod", uploader_email=None)` | Issues that draft (allocates the issue number, which can't be undone). `recipients` = people and/or organisation names. |
| `upload_renders(project, files, title=None, env="prod", dry_run=False, uploader_email=None)` | Images → the project's Renders tab. Skips non-images and images already there. |
| `dwg_to_geomap(project, file, name=None, env="prod", dry_run=False, uploader_email=None)` | A Revit DWG → stored lines on the project's **Site → Revit Maps** tab. The DWG is converted by civil-map-service (`POST /api/dwg/geojson`, coordinate system + units auto-detected near the project's site). One drawing per `name` (default: file stem); the same name again replaces it. `project` may also be a project name. The dry run converts and reports coordinate system, units, layers, line count and distance to site without writing. Civil-map URL: `https://civilmap.assembly.co.nz` (prod) / `https://civilmap-dev.assembly.co.nz` (dev), override `ASSEMBLY_CIVILMAP_URL`. |
| `save_login(email, password, env="prod", make_default=True)` | Checks the login by signing in, then saves it to `~/.assembly/credentials.json` and (by default) makes them the default uploader. `/upload` calls it the first time only. |
| `saved_login()` | Who `/upload` acts as on this machine (`default` + saved `people`). Never returns passwords. |

`uploader_email` is optional everywhere: omitted, the tools act as the saved default.

`project` is the project number (e.g. `2610`) or UUID. `issue_type` is the issue purpose: For
Information / Review / Approval / Construction / Tender / Coordination / Record (or an org's own).

**Setup (once per machine):**
- Login: nothing to do by hand. The first `/upload` asks for the person's app email + password
  once and `save_login` stores them in `~/.assembly/credentials.json`
  (`{"_default": "<email>", "<email>": "<password>"}`, several people allowed). After that it never
  asks again. `ASSEMBLY_PASSWORD` still works for scripted use.
- Prod: set `ASSEMBLY_SUPABASE_ANON_KEY` to the prod app's public anon key (URL defaults to
  `https://newapi-next.assembly.nz`; override with `ASSEMBLY_SUPABASE_URL`).
- Dev (`env="dev"`): read from the checkout's `apps/frontend/.env.local`.

---

## Develop

```bash
uv run pytest            # smoke tests (PDF→MD skips without node/assembly-app; MD→PDF skips without GTK)
```

Project layout:

```
src/assembly_mcp/
  server.py       FastMCP server — registers the tools (stdio); convert_pdftomd
                  shells out to the assembly-app pdf2md CLI (_app_dir())
  md_to_pdf.py    python-markdown → WeasyPrint rendering
.claude/commands/ /convert-pdftomd, /convert-mdtopdf, /upload and /dwgtogeomap slash commands
tests/            smoke tests
```

## License

MIT — see [LICENSE](LICENSE).

---

## Building the CLI bundles

`cli/bin/*.mjs` are esbuild bundles of the assembly-app operator CLIs (`upload-issues`, `upload-renders`,
`upload-product`, `upload-dwg-geomap`, `upload-artifact`, `upload-panomap`, `pdf2md`). They are built
from a private repo, so they are **gitignored here (this repo is public)** and reach the shared copy on
P: through `push-to-p.sh`. Rebuild after the app's CLIs change (build from `main`, which is what prod runs):

```bash
cd cli
npm ci
ASSEMBLY_APP_DIR=<assembly-app checkout on main> npm run build   # writes bin/*.mjs + bin/SOURCE.json
```

`bin/SOURCE.json` records the app commit the bundles came from.

TDQS

A4.6/5.0

Scored across 2 tools

Disambiguation5/5

The two tools are exact inverses: one converts PDF to Markdown, the other Markdown to PDF. There is zero overlap or ambiguity between them.

Naming Consistency5/5

Both tool names follow the identical convert_<source>to<target> pattern (convert_pdftomd, convert_mdtopdf). The naming is perfectly consistent and predictable.

Tool Count4/5

The server has only 2 tools, which is on the low end, but the scope is tightly focused on bidirectional PDF/Markdown conversion. The count is slightly under the typical 3-15 range yet entirely reasonable for the stated purpose.

Completeness5/5

The tool surface fully covers the domain of PDF-Markdown conversion in both directions. There are no obvious missing operations for a conversion-focused utility.

Maintenance

ActivityMaintained
ResponsivenessNo issues