apic
apic
Ein App-zu-API-Compiler. Richte ihn auf eine Web-App ohne API für Agenten. Ein Computer-Use-Agent erkundet die UI, verifiziert das Gefundene durch Ausführung und erzeugt einen typisierten MCP-Server für die App.
Playwright MCP interpretiert die App bei jedem Aufruf. apic kompiliert sie einmal.
An einem Tag solo gebaut beim {Tech: Europe} × VEED Hackathon, London, 22. August 2026.
→ apic-ui.vercel.app — dort gibt es das Demo-Video, was jedes Partnermodell entscheidet und was der Compiler gemessen hat.
Öffentliche Verbraucher-Websites
apic --read https://example.com kompiliert die öffentliche, nur lesbare Oberfläche jeder Verbraucher-Website zu MCP-Tools. Es beginnt an dieser Website (plus optionalen Same-Site-Seeds), entdeckt Suchfelder, Filter und wiederkehrende Ergebniskarten und erzeugt nur Tools, deren Zeilen einen kalten Replay überstehen. Es nimmt keine Deliveroo-Routen, kein Restaurant-Vokabular, kein Konto, keinen Warenkorb und keinen Checkout an.
Für eine bekannte Sammlungs-/Artikelseite übergib sie explizit als Same-Site-Direkt-Seed: APIC_READ_DIRECT_URL=https://example.com/catalog/item apic --read https://example.com. Vom Agenten gelieferte URLs sind auf den Ursprung beschränkt, der in das Rezept kompiliert wurde.
Ein Prompt, keine Ziel-URL
Wenn APIC als MCP-Server verbunden ist, verwende für eine normale Verbraucherfrage fulfill_request statt compile_app:
{ "request": "Find me the cheapest pizza near 17 & 18 Clere Street" }Der Server verwendet Tavily, um öffentliche Kandidatendienste zu finden, OpenAI, um den kompilierten Ablauf auszuwählen und zu betreiben, h, um mehrdeutige Lese-Steuerungen zu priorisieren, Pioneer, um zu klassifizieren, ob Sonden sinnvolle Ergebnisse geliefert haben, und fal nur dort, wo diese Klassifizierung eine visuelle Entscheidung braucht. Er versucht eine kleine, ursprungsverschiedene Fallback-Menge, wenn ein Kandidat angefochten wird oder keinen abspielbaren öffentlichen Ablauf hat. Er loggt sich nie ein, bestellt nie, führt keinen Checkout durch und umgeht keine Challenge. Die überlebenden Tools werden kalt verifiziert, als Beleg zurückgegeben und auf demselben MCP-Server für spätere Aufrufe registriert.
Related MCP server: mcp-apps-demo-engine
Demo
Auf der Seite ansehen: apic-ui.vercel.app — zwei Minuten, ungeschnitten: die Kompilierung, die generierten Tools in einer Live-Sitzung und der Beobachter, der eine UI-Änderung von selbst erkennt.
Dieselbe Seite enthält die Zahlen, die dieses README berichtet, die Aufschlüsselung pro Partner und das Installations-Snippet für jeden MCP-Client.
Das Problem
Computer-Use-Agenten skalieren wirtschaftlich nicht. Jeder Lauf leitet dasselbe Wissen aus Pixeln erneut ab: ein Modell-Roundtrip pro Schritt, ein Seiten-Snapshot pro Schritt, der den Kontextfenster füllt, und Zuverlässigkeit, die sich über eine Kette abwärts potenziert. Deshalb werden sie ständig demonstriert und selten eingesetzt.
Die Software, die Agenten am meisten steuern müssen, ist genau die Software, die am wenigsten wahrscheinlich jemals eine API bekommt — interne Tools, Altsysteme, alles, dessen Anbieter nicht mehr existiert. Du kannst keinen Netzwerk-Tab abhören, auf dem nichts ist, und du kannst eine Line-of-Business-App von 2011 nicht bitten, ein neues Protokoll zu übernehmen.
apic nutzt den teuren Agenten einmal, um die Schnittstelle zu schreiben. Danach ist es ein Funktionsaufruf.
So funktioniert es
Phase | Aktion | Technik |
Verankern | Liest die eigene Dokumentation des Ziels und lernt die Substantive dieser App, sodass das Vokabular nicht auf das von Vikunja hartcodiert ist | Tavily + OpenAI, pro Host gecacht — nur CLI-Pfad |
Erkunden | Steuert die App, priorisiert Affordanzen, sodass Erstellen-Aktionen zuerst kommen, öffnet Formulare und sendet sie ab | Playwright + h (Eskalationsstufe für Steuerelemente, die das Vokabular nicht benennen kann) |
Wahrnehmen | Entscheidet, ob sich etwas Bedeutsames geändert hat | DOM-Diff, auf dem CLI-Pfad zu fal eskalierend |
Synthetisieren | Wandelt eine Trajektorie in ein typisiertes Tool-Schema um | deterministisch — kein Modellaufruf |
Verifizieren | Spielt das Tool kalt mit Argumenten ab, die die App nie gesehen hat | schlüssellose Diff-Untergrenze, dann der feinabgestimmte Pioneer-Richter, mit OpenAI im Bereitschaftsmodus |
Erzeugen | Schreibt einen lauffähigen MCP-Server, seine Schemas und seinen Beleg | — |
Beobachten | Führt die Suite in einem Intervall erneut aus | — |
Heilen | Ein rotes Tool tritt an seinem eigenen Seed erneut in die Discovery ein | — |
Der Reparaturpfad ist der Erstellungspfad. Heilen patcht keinen Selektor — es führt die Discovery erneut aus, die das Tool ursprünglich gefunden hat, und gleicht über den Namen ab, den die Synthese erzeugt. Ein umbenannter Button ergibt weiterhin createProject.
Ein Tool existiert nur, wenn die App den Schreibvorgang bestätigt hat
Das Zählen von DOM-Knoten erzeugt für jeden Button auf der Seite ein plausibel aussehendes Tool. apic gibt eines nur aus, wenn die App selbst behauptet, dass sich der Zustand geändert hat, über drei Signale, die drei verschiedene App-Verhaltensweisen abdecken:
Verhalten | Beispiel | Signal |
Ankündigen-und-Bleiben | ein Label erstellen | Erfolgsbanner in einer Statusregion |
Ankündigen-und-Navigieren | ein Projekt erstellen | Banner übersteht die URL-Änderung |
Stilles Anhängen | Kanban-Schnellhinzufügen | der übermittelte Wert erscheint als gerenderter Inhalt |
Verschiebung | eine Karte zwischen Spalten ziehen | die Karte hat den Container gewechselt |
Die Verschiebung ist wichtig, denn ein Drag hat kein Banner und gibt nichts zurück — die Karte existierte bereits. Die Containment-Änderung ist der Beleg, und kein kosmetisches Re-Rendering kann sie erzeugen.
Rezepte binden an Identität, nicht an Ort
Vikunja regeneriert Element-IDs bei jedem Seitenaufruf, daher ist ein gespeicherter Selektor bei der Ankunft tot. Ein Rezept hält fest, was ein Feld ist — sein Label, Platzhalter, Name — und die Wiedergabe löst es live erneut auf, mit Rückgriff auf eine stabil-zuerst-Selektorkette (name → stabile id → Platzhalter → generierte id zuletzt).
Ergebnisse
Aus der UI kompiliert. Die OpenAPI-Spezifikation des Ziels wird während der Kompilierung nie gelesen — sie wird nur als Ground Truth für die Bewertung verwendet, weshalb die Recall-Zahl überhaupt etwas bedeutet.
Der Nenner, vor der Zahl genannt: 18 ist jede Schreiboperation (POST/PUT/DELETE) auf /projects, /tasks und /labels in Vikunjas eigener OpenAPI-Spezifikation, nachdem entfernt wurde, was keine Board-Geste ist — Teams, projektweite Berechtigungen, Link-Sharing, Anhänge, Aufgabenbeziehungen, Duplizierung, Bulk-Endpunkte und Lesebestätigungen. Vikunja veröffentlicht insgesamt 105 Schreiboperationen; 18 ist die Teilmenge, die eine Person auf einem Kanban-Board ausführen kann, und jedes ausgegebene Tool darf höchstens eine davon beanspruchen, sodass Recall nicht durch lockeres Matching aufgebläht werden kann.
RECALL 8/18 of the board write-ops in the target's own API
PRECISION 9/9 emitted tools that map to a real operation
VERIFIED 9/9 survived a cold replay with arguments never seen beforeNeun Tools entdeckt, neun ausgeliefert. Abgelehnte Tools werden nicht gelöscht — sie bleiben in tools.json mit verified: false, denn ein abgelehntes Tool ist ein Beleg über den Compiler, kein Müll.
markTask ist flaky und das ist mehr wert als die 9/9. Zwei aufeinanderfolgende Verify-Läufe gegen dasselbe Bundle, ohne Änderungen dazwischen, ergaben 8/9 und dann 9/9: Es scheiterte mit „beobachtete Mutation, aber nichts bestätigte einen Schreibvorgang“ und bestand dann mit „Erfolg — die Aufgabe wurde erfolgreich gespeichert.“ Die wahrscheinliche Ursache ist der Zustand der geseedeten Aufgabe — landet man auf einer bereits erledigten, liest sich das Steuerelement als MARK AS UNDONE und bestätigt anders. Es nimmt keine Parameter entgegen, kann also auch per Argument nicht disambiguieren.
Das ist ein lebendes Beispiel für das Flake-vs-Drift-Problem, das unten als ungelöst aufgeführt ist: watch würde diesen Fehler als Drift zählen und heal aufrufen, obwohl überhaupt nichts gedriftet ist.
Kontinuierliche Verifizierung über einen Live-Nachmittag:
327 checks · 118 breaks · 3 automatic repairs · MTTR 20s(out/watch-stats.json, 38 Zyklen ab 11:33 BST, während dies geschrieben wurde noch aktiv — die Zähler bewegen sich.)
Verstehe diese Break-Zählung richtig. stats.breaks++ feuert bei jeder roten Wiedergabe in jedem Zyklus, daher ergeben drei Tools, die über 38 Zyklen rot bleiben, ~114 Breaks — es ist eine Rot-Tool-Zyklus-Zählung, nicht 118 separate Drift-Ereignisse. Und dieser Watcher wurde um 11:33 gestartet, vor dem Fix an heal(), das ein repariertes Rezept ohne die frische provenance zurückgab, auf die der Opener von replay() tatsächlich klickt; ein Tool, dessen Steuerelement umbenannt worden war, wurde daher in jedem Zyklus geheilt und in keinem grün. Das ist der Großteil der 6/9. Im Code behoben, nicht über ein vergleichbares Fenster neu erfasst.
Partner-Technologien
Jede hat eine Stufe, und jede verschlechtert sich, statt zu blockieren: Die gesamte Pipeline läuft mit überhaupt keinen API-Schlüsseln, bei reduzierter Genauigkeit. Diese Eigenschaft ist der Grund, warum der Compiler gebaut werden konnte, bevor irgendwelche Anmeldedaten eintrafen — und auch der Grund, warum eine Integration aufhören kann beizutragen, ohne dass die Kompilierung es bemerkt, was die Statusspalte festhält.
Tech | Stage | Warum es seinen Platz verdient | Status |
OpenAI | Verify | Ein unabhängiges Urteil darüber, ob der vorhergesagte Effekt eingetreten ist, auf der Grundlage des keyless-Diff-Floors. Es kann eine Ablehnung bestätigen, niemals eine aufheben | in Verwendung — es entschied über das eine Tool, das |
fal | Perceive | Schnelles VLM für die Beurteilung bedeutsam-vs-kosmetisch, nur eskaliert, wenn der DOM-Diff mehrdeutig ist | in Verwendung — 4/4 eskalierte Schritte beim letzten Compile beurteilt, 2 davon als kosmetisch eingestuft. Nur CLI-Pfad; |
Pioneer | Verify, Distil | Ein GLiNER2-Encoder, der auf apics eigenen Verify-Evidenzen feinabgestimmt ist, ersetzt den GPT-4.1-mini-Judge — und schlägt ihn auf zurückgehaltenen Tools (unten). Außerdem der Diff-Text-Klassifikator in | in Verwendung — |
h | Explore | Liest die Seite und benennt die Schreibaktionen, die das keyless-Vokabular verweigerte | in Verwendung — läuft einmal pro Seed auf den Restposten; benennt 0 von 3 auf Vikunja, korrekterweise |
Tavily | Ground | App-Dokumentation → Domänenvokabular, sodass Tools | in Verwendung — |
Die zweistufige Aufteilung ist die eigene These des Produkts, auf sich selbst angewandt: fal ist die günstige hochfrequente Wahrnehmungsschicht, OpenAI ist die teure niederfrequente Reasoning-Schicht. Bei Fehlern eskalieren, nicht bei jedem Aufruf.
Wie jedes einzelne tatsächlich aufgerufen wird
h — holo3-1-35b-a3b, api.hcompany.ai/v1 (OpenAI-kompatibel).
gesture() bildet den sichtbaren Text eines Steuerelements mit Regexes auf ein
<Verb, Ressource>-Paar ab und gibt für alles andere null zurück. Dieses
null ist das Präzisions-Gate und zugleich der Ort, an dem Recall verloren geht:
Ein rein ikonisches Schaltflächenelement, ein Steuerelement, das nicht mit
einem Verb beginnt, oder eine App, deren Formulierungen das Vokabular nie
antizipiert hat, wird verworfen, so deutlich es auch beschriftet ist. h ist die
Eskalationsstufe genau für diese Menge — discover.js
classify() sendet ein JPEG der Seite und der verweigerten Steuerelemente,
einmal pro Seed, und fragt, welche davon schreiben.
Drei Dinge verhindern, dass das Präzision kostet. Antworten werden gegen das
geschlossene Vokabular — sechs Verben, vier Ressourcen — von
plan.gestureFrom() validiert, sodass ein erfundenes Verb kein Tool benennen
kann. Steuerelemente außerhalb des Slices werden zurückgehalten statt
angeboten, denn ADD TO FAVORITES auszuschließen ist eine
Scope-Entscheidung und keine Lücke, die ein Modell füllen soll. Und ein
klassifiziertes Steuerelement muss die App trotzdem einen Schreibvorgang
bestätigen lassen wie jeder andere Kandidat.
Gemessen, beim Compile, über den dieses README berichtet: h liest die drei Steuerelemente, die das Vokabular auf Vikunjas Aufgaben-Seite ungelöst lässt, und benennt eines davon — ein rein ikonisches Steuerelement, das die Regexes glatt verwerfen:
! h read 3 unresolved controls, named 1
! h: "Kanban bucket: To-Do" -> move task (Pencil icon allows changing task status)Das ist die Eskalationsstufe, die genau den Job macht, für den sie existiert: ein Steuerelement ohne führendes Verb und ohne brauchbaren Text, aus seinem Icon zurückgewonnen und in das geschlossene Vokabular abgebildet.
Es hat kein Tool hinzugefügt, und wir behaupten das auch nicht. move task
war zu diesem Zeitpunkt bereits zweimal gefunden worden — einmal durch den
Board-Drag (Move card between columns), einmal durch das Bucket-Dropdown der
Aufgabenseite (Kanban bucket: Doing) —, sodass h's Antwort in das
moveTask deduplizierte, das der Drag erzeugt hatte. Auf diesem Ziel ist h
Korrobation, nicht Recall: ein dritter unabhängiger Weg zu einer Aktion, die
zwei andere Wege bereits erreicht hatten. Eine frühere Revision dieser Datei
sagte, h sei nie erreicht worden und habe nichts benannt; beides war falsch.
Ob h Recall hinzufügt, ist hier ungetestet, weil Vikunjas Schreibaktionen ungewöhnlich gut beschriftet sind. Der Fall, für den es gebaut ist — eine App, deren Schaltflächen Icons sind — ist genau der Fall, den dieses Ziel nicht bietet. Ohne den Schlüssel verliert der Compile diese Korrobation und sonst nichts.
fal — google/gemini-2.5-flash-lite über fal-ai/any-llm/vision.
Der DOM-Differ sagt, ob sich die Seite geändert hat. Er kann eine Änderung
nicht beurteilen, die der Text nicht beschreibt — eine Karte, die die Spalte
gewechselt hat, ein Steuerelement, das nur aufgeleuchtet ist.
perceive.js adjudicate() eskaliert diese Schritte, und
nur diese, an Pixel.
Gemessen, vom letzten vollständigen Compile: vision: 4/4 eskalierte Schritte von fal beurteilt, 1 Drag korroboriert, 2 als kosmetisch befunden. Die zwei
kosmetischen Urteile sind die interessante Hälfte — fal entfernt Kandidaten,
die sonst als Schreibvorgänge untersucht worden wären. Es läuft von
cli.js; ein Compile, der über compile_app auf dem MCP-Server angestoßen
wird, eskaliert nicht.
OpenAI — gpt-4.1-mini, strukturierte Ausgabe.
verify.js spielt jedes emittierte Tool kalt mit Argumenten
ab, die die App nie gesehen hat, und beurteilt das Ergebnis zweimal: zuerst
einen deterministischen Diff-Floor, dann das Modell. Das Modell kann eine
Ablehnung bestätigen und niemals eine aufheben — ein Tool, das der Diff nicht
bestätigen konnte, bleibt abgelehnt, so überzeugt der Judge auch ist.
Gemessen: Beim Lauf, bei dem markTask fehlschlug, lautet sein Eintrag
openai/gpt-4.1-mini disagreed but cannot overturn a rejection. Diese
Asymmetrie ist beabsichtigt: Ein Judge, der seine eigenen Vermutungen
befördern kann, ist ein Präzisionsleck.
Pioneer — GLiNER2 (fastino/gliner2-base-v1), ein POST /inference pro Schritt.
distill.js sendet den Diff-Text jedes Schritts in einer
eigenen Anfrage und erhält eine Zustandsänderungsklasse, ein
Destruktivitäts-Flag und die Domänennomen, oberhalb einer
0,6-Konfidenzschwelle. Früher wurde die gesamte Trajektorie gebatcht, und
Batching ist genau das Thema der dritten hart erarbeiteten Lektion unten:
Derselbe Text erzielte creation 0,777 allein, creation 1,000 an Position 0
und DELETION 0,600 an Position 2 des umgekehrten Batches — ein falsches
Label, das die Schwelle überschreitet. Eine abgeschlossene
Trainings-Job-ID in PIONEER_MODEL tauscht den Basis-Encoder gegen einen
Checkpoint aus, der auf apics eigenen Labels feinabgestimmt ist — das System,
das seine eigene Wahrnehmungsschicht kompiliert —, und sonst ändert sich
nichts.
Pioneer — der feinabgestimmte Verify-Judge. Das ist der Pioneer-Seitenwettbewerbsbeitrag:
ein Modell feinabzustimmen, das einen allgemeinen LLM-API-Aufruf übertrifft
oder ersetzt. Der Aufruf, den es ersetzt, ist judgeModel() in
verify.js — GPT-4.1-mini, ein 200-Wörter-Systemprompt,
strukturierte Ausgabe, eine Frage pro abgespieltem Tool: ist dieser
vorhergesagte Schreibvorgang angesichts dieses DOM-Diffs nachweislich
eingetreten? Das ist eine Zwei-Label-Textklassifikation im Gewand eines
Chat-Completions.
pioneer-train.js baut den Ersatz aus den eigenen
Abgasen des Produkts, ohne manuelle Beschriftung:
collect — jedes kompilierte Tool sechsmal mit frischen Argumenten über
verifyAll()abspielen und die Evidenz sowie das Urteil des ausgelieferten Judges (Diff-Floor + GPT) aufzeichnen. 54 echte Zeilen.dataset — Negative ableiten, indem die Evidenz gelöscht wird, auf die der Floor sich stützt (Banner weg, Echo in das Eingabefeld verschoben, das es getippt hat, Argument unausgefüllt, Replay warf Fehler, nichts geändert), und Positive, die das Label erhalten (Knotenreihenfolge umgekehrt, unzusammenhängende Knoten hinzugefügt, Argumente in Werte umbenannt, die ein Mensch tippen würde). Jede abgeleitete Zeile wird vom selben deterministischen Floor neu beschriftet. 788 Zeilen; nach Tool zurückgehalten, sodass die Bench Tools misst, die der Encoder nie gesehen hat.
upload / train —
POST /felix/datasets/upload/url→ presigned PUT →POST /felix/training-jobs,fastino/gliner2-base-v1, LoRA, 12 Epochen. Trainiert in etwa vier Minuten.bench — die zurückgehaltenen Zeilen durch beide Judges. Das LLM wird über die unveränderte
judgeModel()aufgerufen, sieht also genau das, was es in Produktion sieht.
judge | Genauigkeit | Präzision | Recall | falsch pos | falsch neg | ms/Zeile |
Pioneer GLiNER2-Feintuning (Job | 94,4 % | 100 % | 87,6 % | 0 | 12 | 150 |
OpenAI GPT-4.1-mini | 89,3 % | 84,3 % | 93,8 % | 17 | 6 | 890 |
215 zurückgehaltene Zeilen, zwei Tools (createTask, assignLabel), die im
Training fehlen. Der Encoder gibt etwas Recall für null falsch Positive auf —
der richtige Trade-off für diesen Judge, der konstruktionsbedingt eine
Ablehnung bestätigen, aber nie eine Vermutung befördern darf. Setzt man
PIONEER_JUDGE_MODEL auf die Job-ID, verwendet verify es; OpenAI bleibt als
Fallback in Bereitschaft, und ganz ohne Schlüssel läuft der Floor trotzdem.
Drei Dinge auf die harte Tour gelernt, alle live verifiziert und im Code
festgehalten: multi_label/top_k innerhalb einer Klassifikations-Spezifikation
lassen den vereinheitlichten /inference-Pfad für jeden Text categories: []
zurückgeben (das, nicht Guthaben, ist der Grund, warum die Distil-Stufe den
ganzen Morgen still war); GLiNER2 trainiert nur als LoRA —
training_type: "full" wird akzeptiert und schlägt in Modal ohne
Log-Zeile fehl; und Batch-Inferenz (text: [...]) auf einem feinabgestimmten
Modell liefert Labels, die nicht zu den Eingaben passen, also sendet der Judge
einen Text pro Anfrage.
Tavily — api.tavily.com/search, fünf Ergebnisse, Antwort inklusive.
ground.js läuft vor dem ersten Seed. plan.js bringt
Vikunjas Nomen mit — bucket, task, label, project — und bei allem anderen, nach
dem gesture() gefragt wird, gibt eine Tabelle, die nie davon gehört hat,
null zurück, und das Steuerelement wird verworfen. Tavily holt die
Dokumentation des Ziels selbst; OpenAI strukturiert diese Prosa unter einem
strengen Schema in eine geschlossene Nomenmenge; jeder Begriff wird gegen
/^[a-z][a-z-]{1,18}$/ validiert, auf 12 begrenzt und in die eingebaute
Tabelle eingemischt, statt sie zu ersetzen, sodass Grounding Vokabular
hinzufügen und Vikunjas nie wegnehmen kann. Pro Host unter .apic/ gecacht,
sodass ein wiederholter Compile nichts kostet und eine Demo nicht vom
Veranstaltungs-WLAN abhängt.
Es degradiert in drei Schritten — kein Tavily-Schlüssel, keine Evidenz; kein
OpenAI-Schlüssel, die Evidenz kann nicht strukturiert werden; nichts übersteht
die Validierung — und jeder Schritt loggt und lässt die eingebaute Tabelle
stehen. Wie fal läuft es von cli.js: compile_app auf dem MCP-Server
verwendet das eingebaute Vokabular.
Die Recall-Zahlen oben wurden also keyless erzeugt, mit fal auf den eskalierten Wahrnehmungsschritten und einem OpenAI-Judge im Verify-Durchlauf. Sie sind keine Demonstration des vollständigen Partner-Stacks, und dieses README wird das nicht behaupten.
Setup
git clone https://github.com/brwbo/apic && cd apic
npm install && npx playwright install chromium
cp .env.example .env # fill in keys; .env is gitignored
npm run setup # starts the target app, checks every credentialZiel-App (selbst gehostet, wegwerfbar — niemals auf ein Produkt Dritter richten):
docker volume create vikunja-files
docker run --rm -v vikunja-files:/data alpine sh -c "chown -R 1000:0 /data"
docker run -d --name vikunja -p 3456:3456 -v vikunja-files:/app/vikunja/files \
-e VIKUNJA_SERVICE_PUBLICURL=http://localhost:3456 \
-e VIKUNJA_DATABASE_PATH=/app/vikunja/files/vikunja.db \
-e VIKUNJA_RATELIMIT_ENABLED=false \
vikunja/vikunja:latestBefehl | Funktion |
| Welche Anmeldedaten funktionieren, welche Ziele erreichbar sind |
| Erkunden → synthetisieren → ausgeben |
| Jedes Tool kalt erneut ausführen; nur Überlebende werden bereitgestellt |
| Kontinuierliche Verifikation mit automatischer Reparatur |
| Recall und Präzision gegen die echte API des Ziels |
| apic selbst als MCP-Server ausführen — siehe unten |
Jeder Befehl liest dieselben zwei Variablen, sodass ein kompletter Lauf auf ein alternatives Bundle zeigen kann, ohne das Live-Bundle anzufassen:
APIC_OUT_DIR=out/rescue APIC_APP=vikunja npm run verifyVariable | Standard | Bedeutung |
|
| Wo kompilierte Bundles liegen. |
|
| Welches Bundle darin |
|
| Die App, die kompiliert wird |
|
| Anmeldedaten für das Ziel |
| ermittelt | Nur nötig, wenn das Anmeldeformular nicht auf einem |
|
| Seiten, von denen aus die Erkundung beginnt |
Seeds und Ziel sind umgebungsgesteuert — nichts im Compiler kennt Vikunjas Routen:
APIC_APP=gitea TARGET_URL=http://localhost:3001 APIC_SEEDS=/repo/create,/issues npm run compileGenerierte Ausgabe
generated/vikunja/ — Server, Schemas und die Belege für
jedes Tool. Nichts in diesem Verzeichnis wurde von einem Menschen geschrieben.
Als MCP-Server verwenden
claude mcp add apic -- node /path/to/apic/src/server.jssrc/server.js startet mit einem Tool, compile_app. Zeigen Sie es auf
eine URL, und es führt die Pipeline prozessintern aus, gibt generated/<app>/
aus, registriert die kompilierten Tools auf sich selbst und sendet
notifications/tools/list_changed — sodass sie auf derselben Verbindung
aufrufbar sind, ohne Neustart. Aus einem Kaltstart:
[apic] ready - 0 compiled tools + compile_app
BEFORE compile, tools/list = [ 'compile_app' ]
compile_app returned in 22.1s
list_changed notification: YES
AFTER compile = [compile_app, createProject, createLabel, updateLabel, createTask]
createLabel -> {"ok":true,"effect":"creation","expected":"creation"}Ein Compiler, der einen Neustart des gerade erweiterten Systems erfordert, ist ein Build-Schritt. Einer, der das nicht tut, ist ein Live-Compiler. Hinweise zur Client-Kompatibilität und vollständige Transkripte: docs/mcp-client.md.
Beide Sackgassen werden beantwortet, nicht gemeldet
Ein Client trifft apic nur in dem Moment, in dem etwas fehlt, und beide dieser Momente beendeten früher das Gespräch.
Ein Tool, das nicht existiert, gibt die Kompilierung zurück, die es erstellen würde:
unknown tool: createIssue
No compiled tool exposes that action (compiled so far: vikunja). If the app has no API for it, make one:
compile_app { "url": "http://localhost:3456", "goal": "createIssue" }Ein Tool, das die App unter den Füßen weggezogen hat, wird auf dem
Aufrufpfad repariert — watch heilt per Timer, der Server heilt auf Anfrage,
über dieselbe heal(). Das Tool wird rot, der Compiler erkundet diese eine
Aktion erneut, die Reparatur wird zurück in tools.json geschrieben, und der
Aufruf wird erneut versucht, bevor der Aufrufer einen Fehler sieht:
[apic] createLabel is red (no control matched "ADD LABEL (RENAMED)") - re-exploring to heal it
[apic] createLabel healed in 13.6s (click "…" -> "create label"; selectors re-resolved); retry passed
{ "ok": true, "effect": "creation", "healed": { "ms": 13589, "persisted": true } }Ein gesundes Tool wird von all dem nicht berührt: gleicher Aufruf, 4.4s, keine erneute Erkundung.
Verwandte Arbeiten und der Unterschied
Projekt | Was es tut | Der Unterschied |
Browser-Automatisierung als typisierte MCP-Tools | Generische Verben ( | |
Generiert automatisch typisierte Tools aus Actor-Eingabeschemas | Actors sind von Menschen erstellt — es generiert den Wrapper aus einem von Menschen geschriebenen Vertrag | |
URL + einfaches Englisch → strukturierte Daten | Gibt Daten zurück, keine Schnittstelle, und führt das LLM bei jedem Aufruf erneut aus | |
URL/HAR/OpenAPI → CLI + MCP-Server, mit Verifikations-Gates | Belauscht den Netzwerkverkehr — die App muss bereits eine API haben. apic steuert die UI | |
OpenAPI-Spezifikation → MCP-Tools | Erfordert, dass die API bereits existiert | |
Agent generiert und verwendet MCPs pro Aufgabe wieder | Generiert Tools durch Websuche. apic leitet sie durch Bedienung der Software ab | |
Seiten deklarieren ihre eigenen Tools in JavaScript | Erfordert, dass die Entwickler der App es übernehmen | |
Fähigkeit schreiben, verifizieren, speichern, wiederverwenden | Der Vorfahre der Verifizieren-dann-Beh alten-Schleife |
Die Schleife ist nicht neu — woher die Fähigkeit kommt, schon. Alita liest das Internet, um Tools zu erstellen; cli-printing-press liest das Netzwerk; Easy MCP liest eine Spezifikation. apic liest die App.
Was noch nicht funktioniert
Klartext, denn ein Compiler, der seine Fehlermodi verbirgt, ist keiner.
h ist im Pfad und trägt auf diesem Ziel nichts bei. Es liest die Steuerelemente, die das Vokabular abgelehnt hat, und benennt keines davon korrekt, weil Vikunjas Board-Bereich bereits von den Regexen abgedeckt ist. Die Eskalation ist real und gemessen; der Gewinn ist hier null, und ein Ziel, dessen Steuerelemente eher Icons als Verbphrasen sind, wäre der Fall, der es zeigen würde.
compile_appführt eine reduzierte Pipeline aus.compile.jsist die prozessinterne Kompilierung, die der MCP-Server aufruft, und es istcli.jsminus fünf Dinge: Grounding (Tavily/OpenAI), Seed-Erkennung, die dedizierte Formularseiten-Sonde, den Task-Detail-Seed und den Kanban-Drag, plus fals Vision-Ebene —adjudicate()läuft nur auscli.js. Es behält Erkennung, Persistenz, Pioneer-Destillation, Synthese und Ausgabe. Deshalb zeigt das obige Transkript vier Tools, wonpm run compileneun erzeugt: die Live-Compiler-Demo und das 9-Tool-Bundle sind zwei verschiedene Pfade, und nur der CLI-Pfad ist das, was die Recall-Zahlen beschreiben.Pioneer schien den ganzen Vormittag nicht verfügbar — zuerst
403 payment_method_required, und nach einem neuen Schlüsselcategories: []bei jedem Aufruf, was der Code als „keine Meinung" las und auf die Heuristik zurückfiel. Das Zweite war ein Request-Form-Bug (multi_label/top_k), nicht die API. Jede Integration wurde so geschrieben, dass sie still degradiert, und jede tat es — die Degradation ist das beabsichtigte Verhalten; es einen ganzen Vormittag lang nicht zu bemerken, ist es nicht.Der feinabgestimmte Richter hat eine App gesehen. Seine 788 Trainingszeilen sind alle Vikunja. Der zurückgehaltene Split ist nach Tool, nicht nach App; eine Gitea- oder ParaBank-Bench ist der nächste ehrliche Test, und
collectgegen ein zweites Ziel ist der Weg dorthin.Das zweite Ziel kompiliert dünn. Gitea kompiliert jetzt Ende zu Ende —
createRepositoryundcreateIssue, beide verifiziert, 2/13 auf seinem Issue-Bereich gegenswagger.v1.json. Es brauchte keine Änderung an der Erkennung: Die beiden Fixes waren eine Bestätigungsklasse (Gitea bestätigt einen Schreibvorgang, indem es das Ergebnis unter einer neuen URL ausliefert, die den übermittelten Wert trägt, und das Gate suchte nur nach Bannern und Body-Echos) und das Verschieben der Container/Item-URL-Muster auscli.jsin die Konfiguration, woAPIC_SEEDSbereits lebte. Label- und Kommentar-Aktionen werden weiterhin verpasst: Sie sitzen hinter Steuerelementen, die das Vokabular nicht benennt, undhbenannte keines der 14, die ihm übergeben wurden.8/18 Recall auf Vikunja. Fehlend: Bucket-Erstellung, Kommentare, Beziehungen und Anhänge.
markTaskschlägt etwa bei jedem zweiten Lauf fehl (siehe Ergebnisse). Der Effekt ist real und beobachtet; ob etwas ihn bestätigt, hängt vom bestehenden Zustand der Aufgabe ab. Jedes Live-npm run verifysollte erwartungsgemäß 8/9 oder 9/9 ausgeben.Parallele Läufe kollidieren. Jeder Befehl teilt eine gespeicherte Sitzung unter
.apic/session.json, sodass ein gleichzeitig gestarteter Compile und Verify den Browser-Kontext des jeweils anderen mitten im Lauf zerstören können (Error setting storage state: Execution context was destroyed). Übergeben Sie pro Lauf eine eigeneAPIC_SESSIONals Workaround; die echte Lösung ist standardmäßig eine Sitzungsdatei pro Lauf.Watch behandelt jeden Fehler als Drift. Eine echte Flake-vs.-Drift-Klassifikation existiert nicht. Drei False-Positive-Klassen wurden von Hand behoben — Rate-Limiting, Token-Ablauf und eine abgestürzte Seite — aber das allgemeine Problem besteht.
Semantische Änderung wird nicht erkannt und ist gefährlich. Wenn
deleteProjectstatt zu löschen archiviert, ist das Heilen des Selektors die falsche Antwort. Verifikation prüft, dass ein Effekt aufgetreten ist, nicht, dass es derselbe Effekt ist.Keine inversen Aktionen, daher verschmutzt die Suite ihre eigene Fixture. Wiederholte Läufe degradieren das Ziel, bis es zurückgesetzt wird.
Auth wird umgangen. Ein Login, ein Benutzer, keine Berechtigungsbereiche — was der schwierige Teil des Problems in realer Unternehmenssoftware ist.
Erklärung zu Vorarbeiten
Von Grund auf beim Hackathon geschrieben. Kein Boilerplate übernommen; das Repo wurde am Morgen der Veranstaltung leer erstellt. Playwright, das MCP-SDK und die OpenAI- und fal-Clients sind die einzigen Abhängigkeiten.
Lizenz
MIT
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseCqualityAmaintenanceA production-grade MCP server that turns natural language intent into fully-architected, accessible, production-ready UI code through a 7-step agentic pipeline.201Apache 2.0
- FlicenseNot gradedqualityBmaintenanceAn extensible MCP server and generative UI engine for hosting interactive B2B enterprise workflows with dynamic styling and stateful simulators.
- FlicenseNot gradedqualityBmaintenanceThis MCP server renders UI design artifacts headlessly, runs deterministic linters, and manages stateful design review loops with an independent vision critic.
- FlicenseNot gradedqualityCmaintenanceA universal MCP server for registering internal, external, and OpenAPI-based APIs as MCP tools. It exposes them to MCP clients via Streamable HTTP and provides admin portal, RBAC/session auth, credential injection, and audit logging.
Related MCP Connectors
MCP server for secureFlows: token-free URL builders and integration-linting tools for AI agents.
MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.
Read-only MCP server for the WebAssembly spec: instructions, types, sections, search, proposals.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/brwbo/apic'
If you have feedback or need assistance with the MCP directory API, please join our Discord server