Skip to main content
Glama
README.md
# PostScript MCP

An [MCP](https://modelcontextprotocol.io) server that gives AI agents the
tools to write **high-quality PostScript** — grounded in the Adobe manuals and
Kees van der Laan's PSlib rather than guessed from memory.

It exposes four capabilities:

| Capability | Tools | What it does |
|---|---|---|
| **Render & validate** | `validate_postscript`, `render_postscript` | Run a program through Ghostscript, get a PDF/PNG/SVG and structured diagnostics — the write→check→fix loop that lets an agent self-correct. |
| **Operator lookup** | `lookup_operator`, `search_operators` | Exact PLRM operator definitions (stack signature, LanguageLevel, errors). 270 operators shipped, offline. |
| **PSlib fragments** | `get_pslib_fragment`, `search_pslib` | Retrieve van der Laan's vetted procedures by name, with their stack signatures and source, so agents reuse code instead of reinventing it. |
| **Reference search** | `search_reference` | Cited passages from the PLRM, Blue/Green books, DSC/EPS specs and tutorials. |

Plus `postscript_capabilities` for setup/debugging.

---

## Why

LLMs write plausible-looking PostScript that fails on a real interpreter:
wrong operand order, undefined names, unbalanced `gsave`/`grestore`, EPS files
with no `%%BoundingBox`. This server closes that gap. The agent can confirm an
operator's signature before using it, paste in a procedure that is known to
work, quote the spec when behaviour is subtle, and — crucially — **actually run
the program and read the errors back** before handing code to a human.

Pair it with the [skill suite](#skills) for the authoring conventions.

---

## Requirements

* **Python 3.10+**
* **Ghostscript** — the `gs` binary (rendering & validation)
  * macOS: `brew install ghostscript` · Debian/Ubuntu: `apt install ghostscript`
  * Windows: the official installer, then ensure `gswin64c.exe` is on `PATH`
* **pdftotext** (poppler-utils) — only needed to build the reference corpus
  * macOS: `brew install poppler` · Debian/Ubuntu: `apt install poppler-utils`

## Install

```bash
cd postscript-mcp
python -m venv .venv && source .venv/bin/activate      # optional
pip install -e .                                        # installs the `mcp` dep
```

That is enough for **operator lookup** and **render/validate** to work
immediately (the operator dataset ships in `data/operators.json`).

## Build the indexes (PSlib + reference corpus)

Point the ingester at your PostScript reference library — the folder with the
Adobe PDFs and the PSlib sources (this project was built around exactly such a
library; see the accompanying `REFERENCE_INDEX.md`):

```bash
python scripts/ingest.py --library "/path/to/postscript reference"
```

This writes:

* `data/pslib_index.json` — every PSlib procedure, parsed from `PSlib.ps` /
  `PSlib.eps` (auto-discovered; or pass `--pslib PSlib.ps`).
* `corpus/corpus_index.json` — one searchable chunk per PDF page and per block
  of each `.ps`/`.txt` source.

Re-run it whenever your library changes. Useful flags: `--skip-corpus`,
`--skip-pslib`, `--pslib <files…>`, `--library`, `--corpus`, `--data`.

You can also set defaults via environment variables (below) instead of flags.

## Connect it to a client

### Claude Desktop

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

```json
{
  "mcpServers": {
    "postscript": {
      "command": "python",
      "args": ["-m", "postscript_mcp"],
      "env": {
        "PSMCP_LIBRARY_DIR": "/path/to/postscript reference",
        "PSMCP_DATA_DIR": "/path/to/postscript-mcp/data",
        "PSMCP_CORPUS_DIR": "/path/to/postscript-mcp/corpus"
      }
    }
  }
}
```

If you installed with `pip install -e .`, you can use the console script
instead: `"command": "postscript-mcp", "args": []`. Point `command` at the
Python inside your virtualenv if you used one.

### Claude Code

```bash
claude mcp add postscript -- python -m postscript_mcp
```

(run from the project directory, or give the absolute interpreter path).

### Any MCP client

The server speaks MCP over **stdio**: launch `python -m postscript_mcp`.

### Remote / cloud deployment (public HTTPS endpoint)

To expose this server on the network so any client can reach it over HTTP, see
the deployment guides. It serves MCP over **Streamable HTTP** at `/mcp`,
containerised with Docker (Ghostscript included):

* **[`DEPLOY_RENDER.md`](DEPLOY_RENDER.md) — recommended, free, no credit
  card:** deploy to the Render free tier (GitHub sign-in) and point your own
  domain at it.
* **[`DEPLOY.md`](DEPLOY.md) — alternative:** a free **Oracle Cloud Always
  Free** VM running Docker + Caddy (TLS).

```json
{
  "mcpServers": {
    "postscript": {
      "type": "http",
      "url": "https://mcp.example.com/mcp"
    }
  }
}
```


---

## Tool reference

**`validate_postscript(source="", path="")`** → `{ok, diagnostics[], bbox,
hires_bbox, hint, …}`
Interpret without rasterising (fast). Diagnostics carry `error`,
`offending_command` and whether it is a genuine interpreter error. `hint`
gives a plain-language nudge for common failures. Great for filling an EPS
`%%BoundingBox` from `hires_bbox`.

**`render_postscript(source="", path="", output_format="png", resolution=150,
page=None, eps_crop=True, return_base64=False)`** → `{ok, output_path,
page_count, bbox, diagnostics[], …}`
`output_format` ∈ `png`, `png-alpha`, `pdf`, `svg`. Returns the output file
path; set `return_base64=True` to also get PNG bytes inline.

**`lookup_operator(name)`** → the operator's `signature`, `operands`,
`results`, `summary`, `category`, `level`, `errors`. On a miss, returns
`did_you_mean`.

**`search_operators(query, category="", limit=20)`** → ranked operators, or a
whole category listing.

**`get_pslib_fragment(name)`** → `{signature, source, category, attribution,
line}` for one PSlib procedure.

**`search_pslib(query="", category="", limit=20)`** → matching procedures
(names + one-line signatures); follow up with `get_pslib_fragment`.

**`search_reference(query, limit=8, doc="")`** → cited passages
`{doc, page, snippet, score}`. Restrict to a document with `doc` (e.g.
`"PLRM"`, `"Green"`, `"DSC"`).

**`postscript_capabilities()`** → what is available right now (Ghostscript
present? indexes built?) and how to build what is missing.

### Recommended agent workflow

1. **Draft** using `lookup_operator` / `search_pslib` + `get_pslib_fragment`.
2. **Consult** `search_reference` when behaviour is subtle (imaging model,
   `save`/`restore`, DSC rules).
3. **Check** with `validate_postscript` — fix every diagnostic.
4. **Render** with `render_postscript` and, for figures, verify the bounding
   box and that the image looks right.
5. For embeddable output, emit a correct `%%BoundingBox` and DSC comments
   (see `examples/square.eps`).

---

## Configuration (environment variables)

| Variable | Default | Purpose |
|---|---|---|
| `PSMCP_LIBRARY_DIR` | `./library` | Where your reference PDFs / PSlib sources live (for ingest). |
| `PSMCP_DATA_DIR` | `./data` | Location of `operators.json` and `pslib_index.json`. |
| `PSMCP_CORPUS_DIR` | `./corpus` | Location of the search corpus. |
| `PSMCP_GHOSTSCRIPT` | auto-detected | Path to the `gs` binary. |
| `PSMCP_OUTPUT_DIR` | temp dir | Where rendered files are written. |
| `PSMCP_RENDER_TIMEOUT` | `30` | Ghostscript wall-clock limit (seconds). |
| `PSMCP_MAX_SOURCE_BYTES` | `4194304` | Max program size accepted. |

## Safety

Every Ghostscript invocation uses `-dSAFER` (no arbitrary file writes / device
control from the program) and a wall-clock timeout, and source size is capped.
The server never executes PostScript except through Ghostscript.

## Development

```bash
pip install -e ".[dev]"
python scripts/gen_operators.py     # regenerate data/operators.json
python -m pytest -q                 # run the test suite
```

## Layout

```
postscript_mcp/      server + the four capability modules
  server.py          FastMCP wiring (the tools)
  render.py          Ghostscript validate/render
  operators.py       operator reference access
  pslib.py           PSlib tokeniser / parser / index
  reference.py       BM25 corpus search
  config.py          paths & limits from env
data/operators.json  curated PLRM operator reference (shipped)
scripts/ingest.py    build pslib_index.json + corpus from your library
scripts/gen_operators.py   rebuild the operator dataset
skills/              agent skill suite (see below)
examples/            conformant EPS + PSlib usage
tests/               pytest suite (+ a real PSlib fixture)
```

## Skills

The `skills/` folder holds a composable suite an agent loads alongside this
server:

* **postscript-authoring** — stack discipline, structure, the render/verify loop.
* **postscript-pslib** — reusing van der Laan's library correctly.
* **postscript-eps-dsc** — conformant EPS / DSC output for embedding & print.

## Credits & licences

* PSlib © Kees van der Laan (kisa1@xs4all.nl) — parsed and indexed here for
  reference; its own terms apply to the code itself.
* Operator reference curated from the *PostScript Language Reference Manual,
  3rd ed.* (Adobe, 1999).
* This server's code: MIT (see `LICENSE`).

TDQS

A4.3/5.0

Scored across 8 tools

Disambiguation5/5

Each tool targets a distinct job: exact operator definitions vs operator search, validation vs rendering, PSlib retrieval vs searching, and reference/capability lookups. Even the paired search/get tools are clearly separated by role.

Naming Consistency4/5

Most tools follow a consistent verb_noun pattern (lookup_operator, validate_postscript, search_operators). The only deviation is postscript_capabilities, which lacks a verb; otherwise the naming is uniform and predictable.

Tool Count5/5

Eight tools is an appropriate size for a PostScript assistant: each one covers a necessary stage of authoring, checking, rendering, and reference lookup without redundancy. The count feels intentional rather than padded.

Completeness5/5

The surface covers a full workflow: discover operators, validate code, render output, reuse library procedures, and consult authoritative references. There are no dead ends or obvious missing operations for the stated domain.

Maintenance

ActivityMaintained
ResponsivenessNo issues