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.

Auf dieser Seite
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:
{
"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
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.
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
{
"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:
{
"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.

