LibreOffice

Ein Modell steuert LibreOffice 25.2.3.2 über den Hub, um in Calc ohne geöffnetes Dokument eine Verkaufstabelle mit Formeln und einer Kopfzeile zu bauen und als .ods-Datei zu speichern. Das erste Skript funktionierte. Die Seite nennt den Text, das Skript, die Antworten des Hubs und die Aufnahme.

Das LibreOffice-Writer-Fenster mit einem einseitigen Brief auf Deutsch vom Hub an GIMP, Krita, Blender, Inkscape und LibreOffice, mit dem Logo und dem Titel LibreModular-MCP über dem Text.
Ein Brief des Hubs an die fünf Anwendungen, von einem Skript in Writer gesetzt, mit dem Logo über dem Titel. Brief als PDF

LibreOffice und seine Brücke starten

LibreOffice und seine Brücke starten nie auf Wunsch des Modells: Der Bediener startet beide. Ein Starter erledigt das, nach dem Leitfaden, und kehrt sofort zurück:

libremodular-launch libreoffice --bridge-file /usr/share/libremodular-mcp/bridges/uno_bridge.py

Der Lauf hatte keinen Bildschirm, ein virtuelles X-Display ersetzte ihn, und er setzte in config.toml die Ports 2102 und 9314, nicht die Standardwerte 2002 und 9304. Der Starter gab aus:

LibreOffice started (process 612876), listening for UNO on 127.0.0.1:2102
bridge started (process 612877), listening on 127.0.0.1:9314
a script runs on the current document of its target's family, or with doc set to None when none is open
stop the bridge with 'libremodular-launch libreoffice --stop'; LibreOffice is yours to close

LibreOffice lauschte 1,3 s nach dem Start auf UNO, und der Hub wurde danach gestartet. get_state für hub listete die drei Ziele, die sich die Brücke teilen, libreoffice-writer, libreoffice-calc und libreoffice-impress, als verbunden auf, mit der Version, die LibreOffice meldete:

Antwort des Hubs
{
  "app": "hub",
  "app_version": "0.1.0",
  "bridge_version": null,
  "captured_at": "2026-10-21T12:48:38.198Z",
  "connected": true,
  "content_origin": "hub",
  "screenshot": null,
  "state": {
    "modules": [
      {
        "app_version": "25.2.3.2",
        "bridge_version": "1",
        "connected": true,
        "mode": "uno",
        "name": "libreoffice-writer"
      },
      {
        "app_version": "25.2.3.2",
        "bridge_version": "1",
        "connected": true,
        "mode": "uno",
        "name": "libreoffice-calc"
      },
      {
        "app_version": "25.2.3.2",
        "bridge_version": "1",
        "connected": true,
        "mode": "uno",
        "name": "libreoffice-impress"
      }
    ]
  },
  "warnings": []
}

Dem Modell gegebener Text

Dem Modell gegebener Text
In LibreOffice Calc, build a quarterly sales sheet named “Sales”. Rows: Tent (120, 150, 310, 90), Stove (80, 60, 140, 200), Lantern (45, 70, 65, 120), for the columns Q1 to Q4. Add a Total column and a Total row, computed with formulas. Header row in bold white on dark blue (#1c385c). Save it as sales.ods in your working directory.

Vor dem Schreiben rief das Modell get_state für libreoffice-calc auf. active war null und documents leer, mit einer Warnung, dass LibreOffice kein aktuelles Calc-Dokument hatte. Das ist kein Fehler: Ein Skript läuft dann mit doc auf None und kann das erste Dokument anlegen.

Geschriebenes Skript

Ein Modell, Claude Sonnet 5.5, schrieb dieses Skript nach dem Leitfaden und nach diesem Zustand. Es ist kein Skript aus den Tests des Projekts. Es legt das Dokument an, wenn doc gleich None ist, und speichert mit storeToURL innerhalb des Arbeitsverzeichnisses.

python · Geschriebenes Skript
import os

import uno

if doc is None:
    doc = desktop.loadComponentFromURL("private:factory/scalc", "_blank", 0, ())
sheet = doc.Sheets.getByIndex(0)
sheet.Name = "Sales"

header = ["Product", "Q1", "Q2", "Q3", "Q4", "Total"]
data = [("Tent", 120, 150, 310, 90), ("Stove", 80, 60, 140, 200), ("Lantern", 45, 70, 65, 120)]
for column, title in enumerate(header):
    cell = sheet.getCellByPosition(column, 0)
    cell.setString(title)
    cell.CharWeight = 150.0
    cell.CharColor = 0xFFFFFF
    cell.CellBackColor = 0x1C385C
    if column > 0:
        cell.HoriJustify = uno.Enum("com.sun.star.table.CellHoriJustify", "RIGHT")
for row, (name, *values) in enumerate(data, start=1):
    sheet.getCellByPosition(0, row).setString(name)
    for column, value in enumerate(values, start=1):
        sheet.getCellByPosition(column, row).setValue(value)
    sheet.getCellByPosition(5, row).setFormula(f"=SUM(B{row + 1}:E{row + 1})")
last = len(data) + 1
sheet.getCellByPosition(0, last).setString("Total")
for column in range(1, 6):
    letter = "ABCDEF"[column]
    sheet.getCellByPosition(column, last).setFormula(f"=SUM({letter}2:{letter}{last})")
for column in range(0, 6):
    sheet.getCellByPosition(column, last).CharWeight = 150.0
    sheet.Columns.getByIndex(column).Width = 2600

path = os.path.join(LMM_WORK_DIR, "sales.ods")
properties = (uno.createUnoStruct("com.sun.star.beans.PropertyValue"),)
properties[0].Name = "FilterName"
properties[0].Value = "calc8"
doc.storeToURL(uno.systemPathToFileUrl(path), properties)
result = {"sheet": sheet.Name, "grand_total": sheet.getCellByPosition(5, last).getValue(), "ods_bytes": os.path.getsize(path)}

Antwort des Hubs

Antwort des Hubs
{
  "app": "libreoffice-calc",
  "duration_ms": 555,
  "error": null,
  "result": {
    "grand_total": 1450.0,
    "ods_bytes": 13472,
    "sheet": "Sales"
  },
  "status": "ok",
  "stderr": "",
  "stdout": "",
  "truncated": false
}

Die Gesamtsumme, von der Formel berechnet, ist 1 450: 670 für das Zelt, 480 für den Kocher und 300 für die Laterne. Die vom Skript gespeicherte .ods-Datei hat 13 472 Bytes. get_state beschrieb danach die Tabelle und gab eine Aufnahme zurück:

Antwort des Hubs
{
  "app": "libreoffice-calc",
  "app_version": "25.2.3.2",
  "bridge_version": "1",
  "captured_at": "2026-10-21T12:48:39.480Z",
  "connected": true,
  "content_origin": "document",
  "screenshot": "$XDG_RUNTIME_DIR/libremodular-mcp/captures/libreoffice-calc-20261021T124838664Z.png",
  "state": {
    "active": {
      "sheets": [
        {
          "last_column": 5,
          "last_row": 4,
          "name": "Sales"
        }
      ]
    },
    "documents": [
      {
        "family": "calc",
        "title": "Untitled 1"
      }
    ]
  },
  "warnings": []
}

Erzeugtes Bild

Die Aufnahme ist eine vom PNG-Filter von LibreOffice exportierte Seite, 828 × 1 170 Pixel, mit der Tabelle oben und dem Rest weiß. Dieses Bild behält seine oberen 230 Pixel. Die Kopfzeile ist fett und weiß auf #1c385c, und die letzte Zeile und die letzte Spalte enthalten die Summen.

Eine Tabelle mit dem Titel Sales, mit der Kopfzeile Product, Q1 bis Q4 und Total auf einem dunkelblauen Band, drei Produktzeilen und einer fetten Zeile Total.
Der obere Teil der Aufnahme der Tabelle Sales, wie der Hub sie exportiert hat.
Das LibreOffice-Calc-Fenster: eine Verkaufstabelle mit Product, Q1 bis Q4 und Total auf einem dunkelblauen Band, drei Produkte und darunter ein Balkendiagramm der vier Quartale.
Die Verkaufstabelle des Rezepts und ihr Balkendiagramm, vom selben Skript in Calc erzeugt.

Referenz-Leitfaden im Repository →

WeiterKompatibilität →