Skip to main content
Glama
Yike-Ye
by Yike-Ye
README.md
# OriginLab-MCP

An MCP server for OriginLab Origin. It reads and writes worksheets, builds and
styles multi-panel graphs, and exports figures — and it is careful about the
difference between what Origin accepted and what Origin did.

> **1.0.0.** Every tool here has been run against a real Origin 2024, the Origin
> App installs and starts the bridge, and
> [`examples/five_panel_figure.py`](examples/five_panel_figure.py) builds a
> five-panel figure end to end.
>
> Known gaps, so they are not a surprise: a legend placed in a corner is
> checked for being inside its panel, not for sitting on top of the data; a log
> axis gets no minor ticks unless you ask; and the 213-entry option table
> records what Origin documents, not what each option was measured to do —
> sixteen of them have been watched change a picture.

## Requirements

- Origin or OriginPro 2021b or newer (embedded Python 3.9+)
- Python 3.10+ for the server
- Windows, either natively or in a VM

## Install

```
pip install originlab-mcp
originlab-mcp --build-app .
```

That prints the three steps Origin needs, which are worth stating here because
the obvious one does nothing: dragging the folder onto the Apps gallery is
accepted in silence and adds no button. Copy the generated `OriginLab MCP
Bridge` folder into `%LOCALAPPDATA%\OriginLab\Apps`, pack it from Origin's
Command Window with

```labtalk
mkOPX app:="OriginLab MCP Bridge" opx:="C:\path\to\OriginLab MCP Bridge.opx";
```

— backslashes, because forward slashes hang `mkOPX` — and drag the resulting
`.opx` into Origin. A button appears; clicking it brings the Script Window
forward, where the bridge reports `starting`, `loaded`, `serving`.

Point your MCP client at `originlab-mcp`.

`originlab-mcp --status` says whether a bridge is listening, and exits non-zero
when one is not. `originlab-mcp --stop` ends it.

Stopping is a command rather than a second button because a button must not do
the work itself: while the bridge serves it holds Origin's UI thread, so
anything the button ran would run from inside the message pump. A button that
only spawns an external process is possible -- that is how the upstream project
does it -- but the instruction still has to reach the bridge over the socket,
and `--stop` is that instruction without the indirection.

## What's different

**Origin accepts a great deal that it then ignores, without ever saying so.**

- `set <plot> -zzzz 9` — an option Origin has never heard of — returns true,
  reads back as 9.0, and leaves the graph pixel-for-pixel identical.
- `plot:=2003` is not a plot type. Origin makes the window, draws the axes,
  writes the legend, and plots nothing.
- A style written to a plot inside a group reads back exactly as requested,
  while Origin goes on drawing the group's style.

None of these is catchable from the reply, so the checking happens before the
write and after the figure:

**Before.** Options are checked against Origin's own documented list, and an
invented one is refused with the options it was probably meant to be. This
matters because the real names are unguessable — line width is `-w`, bar fill
pattern is `-pfp` — so a plausible guess is the likely failure, not a typo.
Writing to a grouped plot member is refused outright. Plot types come from
`oPlotIDs.h` in the Origin installation rather than from documentation, and
every name in the table was drawn and looked at.

**After.** A per-call check cannot reach the question that matters, which is
whether the figure is right. So the operations that build one report the thing
that could contradict them:

| operation | what it reports back |
|---|---|
| `origin_plot` | the layer used, and how many plots Origin says are in it |
| `origin_layers_arrange` | every layer's real geometry, and the box they all occupy |
| `origin_legend_set` | where the legend is, and whether that is inside its layer |
| `origin_axis_set` | which side of the frame the title landed on |

That list exists because of a measured failure: a five-panel figure where all
twenty-three steps returned OK and the result was unusable. One legend had been
placed at `left = 24953` on a page 6432 wide — `legend.x` is in data
coordinates, not the percentages it was being given — and Origin's export grew
the canvas to contain it, squeezing the figure into a corner. Nothing in any
reply said anything was wrong.

**A read-back is reported as a read-back**, never as proof:

```json
{
  "requested": 60,
  "readback": {"readable": true, "value": 60.0, "matches_request": true},
  "readback_means": "Origin stored this value where 'get' can find it. It is not
                     proof that the graph changed: Origin also stores options it
                     does not recognise."
}
```

Exporting the graph is the only thing that settles it, which is why
`origin_graph_export` is a first-class tool rather than a way to save pictures.

**Arguments this server does not read are refused**, with the nearest name it
does. The same failure it guards against in Origin turned up here first:
`worksheet_read` takes `max_rows`, and a call passing `rows=3` used to get every
row in the sheet and no indication its argument had been dropped.

The tool surface is small on purpose. One `plot` tool with a `plot_type`
parameter rather than one per chart type; one `plot_option_get`/`set` pair
rather than a wrapper per property.

## Tools

| | |
|---|---|
| `origin_ping` | Is the bridge alive — answers even when originpro is broken |
| `origin_bridge_status` | Where the bridge is, and whether it is answering |
| `origin_worksheet_info` | Size, column names, formats |
| `origin_worksheet_read` | Columns as lists, refusing rather than truncating |
| `origin_worksheet_write` | Columns in, with the format stated not guessed |
| `origin_plot` | Plot columns into a new page or a new layer of an existing one |
| `origin_graph_info` | Layers, plots, addresses, group membership |
| `origin_graph_export` | PNG out — and the only witness that a style took |
| `origin_layer_add` | Add a panel, and say what its index is |
| `origin_layers_arrange` | Grid the panels, and report where they really went |
| `origin_legend_set` | Rebuild a legend, place it, check it is inside its panel |
| `origin_axis_set` | Scale, range and title, with the title's side reported |
| `origin_axis_get` | Read an axis back |
| `origin_plot_option_get` | Read one LabTalk `set` option |
| `origin_plot_option_set` | Write one, and report what that established |
| `origin_set_options` | Search Origin's 213 documented `set` options |
| `origin_labtalk` | The escape hatch, with Origin's output captured |

[`examples/five_panel_figure.py`](examples/five_panel_figure.py) builds a
five-panel figure end to end and prints, at every step, the number that could
contradict it.

## Setups

**Windows, everything local** — the usual case. Server and Origin on the same
machine, talking over loopback TCP.

**Origin in a Windows VM, working from macOS or Linux** — also supported. The
server runs inside the VM over ssh; only the MCP client is on the host. This
works because the server reaches Origin through a loopback socket rather than
COM, which cannot cross a Windows session boundary.

```
MCP client  ──ssh──▶  server (Windows)  ──TCP 127.0.0.1──▶  Origin
```

[`docs/remote-origin.md`](docs/remote-origin.md) walks through that one:
the ssh server, the key (Windows puts an administrator's in a different file
and ignores the usual one without saying so), and pointing an MCP client at it.

## Notes on Origin's behaviour

Things measured here that no documentation mentions:

- **originpro is single-threaded, absolutely.** Called from any thread but
  Origin's UI thread it blocks while holding the GIL and deadlocks the whole
  embedded interpreter. Origin's window stays responsive; its Python is gone.
  The bridge therefore serves on that thread and pumps Origin's message queue
  between requests.
- **A modal dialog stops the bridge**, because the dialog holds the thread the
  bridge serves on — while the handshake file goes on saying it is up.
  Exporting into a directory that does not exist opens one, so that is checked
  before the export is sent.
- **A legend has two pairs of coordinates in different spaces.**
  `legend.x`/`legend.y` are the layer's data coordinates; `legend.left`/`.top`
  are page units, the same ones `page.width` reports.
- **`plotxy ogl:=<new>` makes a new layer, not a new page**, and does not make
  it active. `[<new>]` is the new page.
- **Origin exchanges the axes for bar charts** and reports it through no layer
  property. It moves the title objects to match and keeps their rotation, so
  the value-axis title ends up under the frame reading bottom to top.
- **`-w` is points × 500**; `-wp` takes points. Passing a point value to `-w`
  gives a hairline rather than an error.
- **A fill pattern set with `-pfp` alone is invisible**: Origin draws it in the
  fill colour, so `-pfc` is needed too, and neither changes anything on its own.
- **LabTalk stops at the first statement it rejects**, so a script that reports
  failure has run an unknown prefix of itself.
- Origin refuses to start Python while a command is in flight, and every bridge
  request is one. Scripts containing `run -pyf` or `py.exec` are refused up
  front rather than failing without explanation.

## References

- [Ge-Shun/origin-mcp](https://github.com/Ge-Shun/origin-mcp) — the
  embedded-bridge transport approach this project's architecture follows.
- [garethbeaumo/originlab-mcp](https://github.com/garethbeaumo/originlab-mcp) —
  a COM-attached server; simpler to run when Origin is on the same desktop
  session.
- [Origin LabTalk reference](https://docs.originlab.com/labtalk/ref/) ·
  [originpro](https://docs.originlab.com/originpro/)

Origin and OriginPro are products of OriginLab Corporation. This project is not
affiliated with OriginLab.

## Licence

[MIT](LICENSE).

TDQS

A3.6/5.0

Scored across 17 tools

Disambiguation4/5

Most tools target a distinct resource (worksheet, axis, plot option, layer, legend) with clear boundaries, and the descriptions actively warn where confusion could arise (e.g., plot_option vs axis vs set_options). The only mild overlap is origin_ping vs origin_bridge_status, and origin_worksheet_info vs origin_worksheet_read, both of which are carefully delineated but require reading the details.

Naming Consistency3/5

The dominant pattern is origin_<noun>_<verb> (axis_get, layer_add, worksheet_read), but there are deviations: origin_set_options puts the verb first, origin_graph_info and origin_worksheet_info use 'info' where 'get' would match axis_get, and several tools (ping, labtalk, plot, bridge_status) are bare nouns. The get/set pairs are consistent where they exist, but the mixed conventions prevent a higher score.

Tool Count4/5

Seventeen tools is at the boundary where the set starts to feel heavy, but for a bridge to a full desktop application with worksheet, plotting, and styling concerns, each tool has a discernible role and the count is justified. The escape-hatch design (origin_labtalk) keeps the surface from being even larger.

Completeness4/5

The surface covers the full lifecycle from data reading/writing to plotting, styling, arranging, and exporting, with a clear read/modify/verify pattern throughout. The only notable gap is the lack of any delete/clear operation (no way to remove a plot, layer, or worksheet), though origin_labtalk serves as a viable fallback for these rarer operations.

Maintenance

ActivityMaintained
ResponsivenessNo issues