Mit KI-Stack 2.19 wollte ich ursprünglich vor allem einen weiteren Schritt in Richtung eines tatsächlich nutzbaren lokalen KI-Agenten machen. Desktop Control war mit Version 2.18 bereits als eigenständige Komponente vorhanden. Die logische Fortsetzung bestand darin, diese Funktionen nicht nur über PowerShell und den lokalen Dispatcher erreichbar zu machen, sondern sie dem bestehenden MCP Runtime zur Verfügung zu stellen. Ein Agent sollte damit beispielsweise Fenster auflisten, Elemente einer Anwendung suchen, Eigenschaften auslesen, Werte setzen oder Schaltflächen betätigen können, ohne dafür einen zusätzlichen Dienst, einen weiteren Port oder eine zweite MCP-Infrastruktur betreiben zu müssen.
Am Ende ist genau das entstanden. KI-Stack 2.19 stellt zehn neue ui_*-Werkzeuge über den bereits vorhandenen MCP Runtime auf Port 8021 bereit. Dazu gehören unter anderem ui_list_windows, ui_inspect_window, ui_find_element, ui_get_properties, ui_get_value, ui_set_value, ui_invoke, ui_focus, ui_wait_for und ui_screenshot. Dabei habe ich bewusst darauf verzichtet, Desktop Control innerhalb des MCP Runtime neu zu implementieren. Die MCP-Schicht ist im Wesentlichen nur der Transport. Die eigentliche Auflösung eines UI-Elements, die Prüfung der zulässigen Operation, die Aktion und die anschließende Verifikation bleiben Bestandteil von Desktop Control. Auch Funktionen wie beliebige Tastatureingaben, send_keys, Raw-WinApp-Zugriffe oder generisches Scrollen werden nicht einfach per Policy versteckt, sondern sind in dieser Schnittstelle gar nicht erst vorhanden.
Genau bei dieser Integration wurde 2.19 allerdings interessanter als zunächst geplant. Denn die neue Funktionalität brachte nicht nur zusätzlichen Code mit sich, sondern zwang mich auch dazu, den bisherigen MCP Runtime deutlich genauer zu betrachten. Dabei kam ein Problem zum Vorschein, das schon vorher vorhanden war, aber im normalen Betrieb nicht aufgefallen war.
Das eigentliche Problem lag nicht bei MCP
Der MCP Runtime funktionierte. Er konnte gestartet werden, der Endpoint war erreichbar und die bestehenden Werkzeuge ließen sich verwenden. Genau das machte das Problem so unauffällig. Die Installation war aus Sicht des Installers erfolgreich und ein bereits vorhandener Runtime wurde bei weiteren Durchläufen als vorhanden beziehungsweise compliant erkannt.
Was dabei fehlte, war eine Frage, die im KI-Stack inzwischen immer wichtiger geworden ist: Ist das, was auf dem Zielsystem liegt, tatsächlich identisch mit dem, was der Installer ausliefern möchte?
Bei Desktop Control und WinApp hatte ich dafür bereits eine deutlich robustere Logik aufgebaut. Die Komponenten werden in einen persistenten Bereich unterhalb des KI-Stack-Verzeichnisses installiert und der Installer kann sowohl Versionen als auch Inhalte vergleichen. Beim MCP Runtime war das anders. Seine Start- und Stop-Skripte verwiesen teilweise direkt auf den Payload-Bereich einer Installer-Transaktion. Vereinfacht gesagt zeigte ein dauerhaft verwendetes Startskript damit auf einen Pfad, der eigentlich nur während eines bestimmten Installationsvorgangs existieren sollte:
state\complete-installer\transactions\<TransactionId>\payload\McpRuntime\...
Das ist architektonisch keine besonders gute Trennung. Der Transaktionsbereich ist Arbeitsbereich des Installers. Eine installierte Komponente sollte dagegen unabhängig davon in einem persistenten Verzeichnis liegen. Genau das war bei MCP Runtime nicht konsequent umgesetzt.
Noch problematischer war allerdings die dazugehörige Compliance-Prüfung. Weder Test-KIMcpRuntime noch die entsprechende Prüfung im Complete Installer verglichen den tatsächlich installierten Inhalt mit dem aktuellen Source-Payload. Solange die Versionsnummer der Komponente unverändert blieb, konnte ein veränderter Payload deshalb als bereits installiert betrachtet werden. Der Installer meldete dann sinngemäß SkippedAlreadyCompliant, obwohl Source und Ziel nicht mehr zwingend identisch waren.
Das war kein Fehler, der den Stack sofort zum Absturz brachte. Gerade deshalb halte ich diese Klasse von Fehler inzwischen für interessanter als einen simplen Syntaxfehler. Ein Syntaxfehler scheitert laut und meistens sehr früh. Ein Fehler in der Zustandsbewertung produziert dagegen ein plausibles Ergebnis. Der Installer behauptet, alles sei in Ordnung, obwohl seine Aussage auf einer unvollständigen Prüfung basiert.
Warum ist der Fehler überhaupt entstanden?
Der Hauptgrund war nicht ein einzelner falscher Befehl, sondern die Entwicklungsgeschichte des Projekts. Der KI-Stack ist inzwischen wesentlich modularer als zu Beginn. Komponenten besitzen eigene Versionen, Zustandsprüfungen, Repair-Mechanismen, Rollback-Verhalten und zunehmend auch eine Prüfung ihrer ausgelieferten Dateien. Diese Mechanismen sind aber nicht alle gleichzeitig entstanden.
MCP Runtime war ursprünglich kleiner und hatte eine andere Rolle. Solange der Runtime im Wesentlichen Open Terminal und einige MCP-Funktionen miteinander verband, reichte die damalige Implementierung praktisch aus. Mit Desktop Control bekam er plötzlich eine weitere Abhängigkeit und wurde zu einem zentraleren Bestandteil der Agentenarchitektur. Dadurch wurden Annahmen relevant, die vorher kaum auffielen.
Das ist für mich inzwischen ein wiederkehrendes Muster bei der Entwicklung des KI-Stacks: Eine Komponente kann für ihren ursprünglichen Zweck völlig ausreichend implementiert sein und trotzdem später zum Problem werden, weil sich ihre Rolle verändert. Technische Schulden entstehen nicht ausschließlich durch schlechten Code. Sie können auch dadurch entstehen, dass ursprünglich richtige Annahmen irgendwann nicht mehr gelten.
Bei 2.19 kam genau das zusammen. Der MCP Runtime benötigte plötzlich zuverlässig Desktop Control, Desktop Control wiederum basiert auf WinApp, und damit musste auch die Komponentenreihenfolge sauber abgebildet werden. Die Abhängigkeitskette wurde deshalb auf
python-git -> winapp -> desktop-control -> mcp-runtime
korrigiert. Ohne diese explizite Reihenfolge hätte der MCP Runtime zwar installiert sein können, seine neuen ui_*-Werkzeuge hätten aber auf einen Desktop-Control-Dispatcher verwiesen, der auf dem Zielsystem möglicherweise noch gar nicht existierte. Auch das ist kein spektakulärer Fehler im eigentlichen MCP-Code, sondern ein klassisches Integrationsproblem zwischen Komponenten.
Wie der Fehler entdeckt wurde
Entdeckt wurde das Problem nicht dadurch, dass der MCP Runtime plötzlich komplett ausfiel. Der entscheidende Punkt war die Integration von Desktop Control und die Frage, wie diese neue Funktion zuverlässig auf bereits bestehenden Installationen ankommt.
Ein frisches System ist für solche Tests nur bedingt aussagekräftig. Dort existiert vorher nichts, also wird ohnehin alles installiert. Interessanter ist ein bestehender KI-Stack, bei dem bereits eine ältere MCP-Runtime-Version vorhanden ist. Genau dort muss der Installer entscheiden, ob eine Komponente unverändert bleiben kann, aktualisiert oder repariert werden muss.
Bei dieser Betrachtung fiel auf, dass ein veränderter MCP-Payload bei unveränderter Versionsnummer nicht zwingend eine Neuinstallation beziehungsweise Reparatur auslöste. Der Installer konnte den Zustand als compliant bewerten, obwohl die Dateien nicht mehr dem aktuellen Source entsprachen. Damit war klar, dass nicht nur Desktop Control in MCP eingebunden werden musste, sondern die Delivery des MCP Runtime selbst überarbeitet werden musste.
Die Lösung bestand darin, MCP Runtime auf dasselbe Grundprinzip zu bringen, das sich bei Desktop Control bereits bewährt hatte. Die Komponente besitzt jetzt einen persistenten Package Tree unter
<TargetRoot>\tools\mcp-runtime\current\
und Start- sowie Stop-Skripte verwenden ausschließlich diesen installierten Pfad. Zusätzlich kann der Installer Source und Ziel miteinander vergleichen. Eine identische Version mit identischem Payload darf weiterhin übersprungen werden. Eine identische Version mit verändertem, fehlendem oder zusätzlichem Inhalt gilt dagegen nicht mehr automatisch als compliant, sondern wird reconciled.
Dafür sind in 2.19 auch eigene Regressionstests hinzugekommen. Getestet werden nicht nur die Installation auf einem leeren System und der normale Skip einer identischen Installation, sondern ausdrücklich die problematischen Fälle: gleicher Versionsstand bei verändertem Payload, fehlende Dateien, zusätzliche Dateien, Rollback, eine fehlgeschlagene Erstinstallation und Versionsabweichungen zwischen Source und Konfiguration. Für den MCP-Layer selbst existieren zudem Tests für die neuen ui_*-Tools. Die Release-Abnahme umfasste schließlich 37 erfolgreiche Repository-Gates, einen Live-Test des MCP Runtime, 22 erkannte MCP-Werkzeuge, alle zehn erwarteten UI-Werkzeuge sowie die Prüfung, dass bewusst nicht angebotene UI-Funktionen tatsächlich fehlen. Auch ein echter End-to-End-Aufruf von ui_list_windows wurde durchgeführt.
Ein zweiter Fehler: Versionen sind inzwischen Teil des Vertrags
Bei den Tests kam noch ein verwandtes Problem zum Vorschein. Mit zunehmender Modularisierung gibt es im KI-Stack mehrere Stellen, an denen eine Versionsinformation auftaucht: in der Komponentenkonfiguration, im ausgelieferten Payload und im Complete Installer. Wenn diese Informationen auseinanderlaufen, kann ein Installer zwar technisch funktionieren, aber trotzdem einen falschen Zustand erzeugen.
Deshalb wurde während der Entwicklung von 2.19 zusätzlich eine Prüfung eingebaut, die die Versionskonsistenz des Payloads erzwingt. Das war kein ursprünglich geplantes Feature der Desktop-Control-Integration, sondern ein Ergebnis der Tests während der Entwicklung. Der Commit-Verlauf zeigt entsprechend auf die erste Integration zunächst einen separaten Fix zur Durchsetzung der Payload-Version und anschließend einen weiteren Fix für ein idempotentes MCP-Runtime-Validierungsprofil.
Dieser Punkt ist mir inzwischen wichtig, weil eine Version im KI-Stack nicht nur ein Label für einen Release ist. Sie beeinflusst unmittelbar die Entscheidung des Installers, ob etwas installiert, aktualisiert, repariert oder übersprungen wird. Damit ist eine falsche oder nicht synchronisierte Versionsangabe funktionaler Bestandteil des Installationsverhaltens.
Aus diesem Grund wurden MCP Runtime und Desktop Control für 2.19 auch tatsächlich hochgezählt. MCP Runtime wechselte von 0.1.0 auf 0.2.0 und Desktop Control von 0.1.0 auf 0.1.1. Das sorgt dafür, dass eine bestehende Installation die Änderungen bei einem normalen Upgrade oder Repair auch wirklich erhält. WinApp blieb dagegen bei 0.6.1, weil dort für diesen Release keine entsprechende Änderung notwendig war.
FastMCP und ein unscheinbares DeprecationWarning
Eine weitere Auffälligkeit war wesentlich kleiner, zeigt aber gut, warum ich reale Laufzeittests inzwischen nicht durch reine statische Prüfungen ersetzen möchte. Im MCP Launcher wurde für den OpenAPIProvider ein normaler httpx.AsyncClient verwendet. Mit der tatsächlich durch open-terminal[mcp]==0.11.34 aufgelösten FastMCP-Version 4.0.3 erzeugte diese Konstruktion eine FastMCPDeprecationWarning.
Funktional lief der Runtime trotzdem. Genau solche Warnungen lassen sich deshalb leicht ignorieren. Sie weisen aber darauf hin, dass eine verwendete Schnittstelle nicht mehr dem vorgesehenen Weg der Bibliothek entspricht und bei einem späteren Update tatsächlich brechen kann.
In diesem Fall war die Korrektur vergleichsweise simpel. FastMCP erwartet an dieser Stelle einen echten httpx2.AsyncClient. Das notwendige Paket war durch die bestehenden Abhängigkeiten ohnehin bereits vorhanden. Es musste also weder eine weitere Architektur eingeführt noch eine neue externe Abhängigkeit aufgenommen werden. Trotzdem wäre das Problem ohne einen Test gegen die tatsächlich installierte und gepinnte Runtime wahrscheinlich nicht aufgefallen.
Wären diese Fehler vermeidbar gewesen?
Teilweise ja.
Der Verweis aus einer persistenten Komponente auf einen transaktionsgebundenen Payload-Pfad hätte bei einer strengeren Trennung zwischen Installer-Arbeitsbereich und installierter Runtime von Anfang an vermieden werden können. Auch eine generische Vorgabe, dass jede vom Complete Installer verwaltete Komponente zwingend Source-Parity unterstützen muss, hätte die Lücke wahrscheinlich früher sichtbar gemacht.
Ähnliches gilt für die Versionsprüfung. Wenn jede Komponente von Beginn an einem einheitlichen Vertrag aus Source-Version, Payload-Version, installierter Version und Inhaltsprüfung unterlegen hätte, wäre der Fehlerzustand schwieriger zu erzeugen gewesen.
Ganz so einfach ist die Antwort trotzdem nicht. Der KI-Stack hatte diese Architektur am Anfang schlicht noch nicht. Viele der heute vorhandenen Mechanismen sind aus konkreten Fehlerfällen entstanden. Das ist bei einem wachsenden System normal. Man kann nicht jede spätere Anforderung im ersten Entwurf vorwegnehmen, ohne gleichzeitig Gefahr zu laufen, ein kleines Projekt schon zu Beginn mit unnötiger Abstraktion zu überladen.
Vermeidbar gewesen wäre deshalb weniger der konkrete Fehler als vielmehr seine Lebensdauer. Hätte ich früher konsequent Upgrade-, Repair- und Drift-Szenarien getestet und nicht hauptsächlich Fresh Installs, wäre die fehlende Payload-Parity wahrscheinlich früher aufgefallen.
Genau deshalb hat sich auch meine Teststrategie verändert. Ein erfolgreicher Fresh Install beweist nur, dass ein Installer auf einer leeren Umgebung funktioniert. Er sagt wenig darüber aus, ob derselbe Installer nach drei Releases noch zuverlässig zwischen „bereits korrekt“, „veraltet“, „verändert“ und „defekt“ unterscheiden kann.
Idempotenz ist mehr als „beim zweiten Mal passiert nichts“
Das vielleicht wichtigste Ergebnis von 2.19 betrifft deshalb weniger MCP oder Desktop Control als meine Definition von Idempotenz.
Früher hätte ich einen Installer als idempotent bezeichnet, wenn ein zweiter Lauf keine unnötigen Änderungen mehr durchführt. Das ist zwar richtig, aber unvollständig. Ein Installer darf nur dann nichts tun, wenn er vorher auch zuverlässig festgestellt hat, dass wirklich nichts getan werden muss.
SkippedAlreadyCompliant ist nur dann ein gutes Ergebnis, wenn „compliant“ tatsächlich bewiesen wurde.
Für den KI-Stack bedeutet das inzwischen, dass Versionen allein nicht mehr ausreichen. Der installierte Zustand muss zusätzlich mit dem erwarteten Zustand verglichen werden. Eine Datei kann verändert worden sein. Eine Datei kann fehlen. Eine zusätzliche Datei kann vorhanden sein. Eine Version kann zwischen Manifest und Payload abweichen. All diese Fälle müssen eine andere Entscheidung auslösen als eine tatsächlich unveränderte Installation.
Der Complete Installer entwickelt sich dadurch immer stärker von einem Installationsskript zu einem kleinen Desired-State-System. Er kopiert nicht einfach Dateien nach C:\KI-Stack, sondern versucht zu verstehen, welcher Zustand vorhanden ist und welcher Zustand hergestellt werden muss.
Desktop Control über MCP
Trotz der vielen Arbeiten unterhalb der Oberfläche bleibt das sichtbare Feature von 2.19 natürlich Desktop Control über MCP. Nach dem Release stellt derselbe Runtime, der bereits die bisherigen MCP-Werkzeuge bereitstellt, insgesamt 22 Tools zur Verfügung. Zehn davon stammen aus der neuen Desktop-Control-Integration. Es wurde kein zweiter MCP-Server eingeführt und es gibt weder einen zusätzlichen Port noch ein zweites Credential-Modell.
Das halte ich architektonisch für wesentlich sauberer als einen weiteren Dienst neben dem bestehenden Runtime. Für Open WebUI beziehungsweise einen Agenten existiert damit weiterhin ein zentraler MCP-Einstiegspunkt. Desktop Control wird lediglich als zusätzliche Fähigkeit darunter eingebunden.
Gleichzeitig bleibt die Sicherheitslogik an einer Stelle. Der MCP Runtime darf nicht selbst entscheiden, ob beispielsweise ein UI-Element manipuliert werden darf. Er ruft den vorhandenen Desktop-Control-Dispatcher auf und gibt dessen Ergebnis zurück. Wenn Desktop Control eine Operation wegen eines Secret Context blockiert oder eine geforderte Postcondition nach einer Aktion nicht beweisen kann, wird genau dieses Ergebnis an den Agenten weitergereicht.
Damit entsteht keine zweite Implementierung derselben Sicherheitsregeln.
Was ich aus 2.19 mitnehme
KI-Stack 2.19 ist für mich deshalb weniger ein Release über zehn neue MCP-Tools als ein weiterer Schritt bei der Stabilisierung der Architektur. Desktop Control über MCP war der Anlass. Die wichtigere Arbeit bestand darin, eine bereits bestehende Komponente noch einmal unter den inzwischen strengeren Maßstäben des Projekts zu betrachten.
Dabei wurden eine fehlende persistente Deployment-Struktur des MCP Runtime, unzureichende Payload-Parity-Prüfungen, eine falsche beziehungsweise unvollständige Abhängigkeitsreihenfolge, eine Versionskonsistenz-Lücke und ein veralteter FastMCP-Aufruf sichtbar. Zusätzlich musste das Validierungsprofil so korrigiert werden, dass wiederholte Läufe tatsächlich idempotent bleiben. Die Entwicklungsfolge im Release-Branch zeigt genau dieses Muster: Auf die eigentliche Desktop-Control-Integration folgten noch separate Korrekturen für die Payload-Version und die Idempotenz der Runtime-Validierung.
Das klingt zunächst danach, als hätte die neue Funktion eine ganze Reihe zusätzlicher Fehler erzeugt. Tatsächlich sehe ich es inzwischen eher andersherum: Die neue Funktion hat mehrere Schwächen sichtbar gemacht, die vorher bereits im System vorhanden waren oder durch die zunehmende Komplexität relevant wurden.
Und genau dafür sind solche Integrationsschritte wertvoll.
Wenn eine neue Komponente lediglich eingebaut wird und anschließend der Happy Path funktioniert, hat man vor allem bewiesen, dass eine Demo funktioniert. Interessanter wird es an der Stelle, an der ein bestehendes System aktualisiert, repariert, erneut ausgeführt und bewusst in inkonsistente Zustände gebracht wird. Erst dann zeigt sich, ob ein Installer seinen eigenen Zustand wirklich versteht.
Mit KI-Stack 2.19 ist dieser Teil wieder ein Stück robuster geworden. Gleichzeitig kann ein lokaler Agent jetzt erstmals über denselben MCP Runtime nicht nur Informationen aus anderen Werkzeugen beziehen, sondern kontrolliert mit der Windows-Oberfläche interagieren.
Der Weg vom lokalen Chatbot zum lokalen Agenten wird damit ein gutes Stück praktischer.
