<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: MagicCreator</title>
    <description>The latest articles on DEV Community by MagicCreator (@magiccreator).</description>
    <link>https://dev.to/magiccreator</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4116842%2Fe94c3ccc-d7ca-4e41-b440-3f54c0bbebd3.png</url>
      <title>DEV Community: MagicCreator</title>
      <link>https://dev.to/magiccreator</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/magiccreator"/>
    <language>en</language>
    <item>
      <title>Vom Prompt zum Song: Asynchrone KI-Musikgenerierung mit TypeScript</title>
      <dc:creator>MagicCreator</dc:creator>
      <pubDate>Tue, 10 Nov 2026 07:39:30 +0000</pubDate>
      <link>https://dev.to/magiccreator/vom-prompt-zum-song-asynchrone-ki-musikgenerierung-mit-typescript-3n46</link>
      <guid>https://dev.to/magiccreator/vom-prompt-zum-song-asynchrone-ki-musikgenerierung-mit-typescript-3n46</guid>
      <description>&lt;p&gt;Ein Klick auf „Generieren“, dann mehrere Minuten Wartezeit: Bei einer Anwendung für KI-Musik liegt die eigentliche Integrationsarbeit zwischen dem Absenden einer Idee und dem ersten abspielbaren Track.&lt;/p&gt;

&lt;p&gt;Was zeigt der Client währenddessen an? Wann darf er aufhören, den Status abzufragen? Und was passiert, wenn die Verbindung abbricht, obwohl der Auftrag bereits angenommen wurde?&lt;/p&gt;

&lt;p&gt;Bei unserem Projekt &lt;a href="https://kisongerstellen.com/" rel="noopener noreferrer"&gt;KlangKI – KI-Songs auf Deutsch erstellen&lt;/a&gt; begegnen uns diese Fragen sowohl im Webprodukt als auch bei der Anbindung von KI-Assistenten. Dieser Beitrag betrachtet den öffentlich dokumentierten API-Vertrag und unseren &lt;a href="https://github.com/kisongerstellen/ki-song-erstellen-mcp" rel="noopener noreferrer"&gt;offenen TypeScript-MCP-Adapter&lt;/a&gt;. Im Mittelpunkt steht die Client-Seite des Ablaufs; interne Modell- oder Queue-Implementierungen werden dafür nicht vorausgesetzt.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Auftrag und Ergebnis getrennt behandeln
&lt;/h2&gt;

&lt;p&gt;Die Generierung dauert normalerweise etwa drei bis fünf Minuten. Der Client bekommt beim Start eine Aufgaben-ID und fragt das Ergebnis später separat ab.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Web-Client oder KI-Assistent
          |
          | Generierung anfordern
          v
      KlangKI API ----&amp;gt; taskId
          ^
          | Status mit taskId abfragen
          |
      Client erhält Status und Tracks
          |
          v
    audioUrl im Player verwenden
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Die wichtigen Schnittstellen sind:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Methode und Route&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Musikgenerierung starten&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POST /api/music/generate&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aufgabenstatus lesen&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /api/music/task/status/{taskId}&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Aufgabenhistorie lesen&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GET /api/music/my-music&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Für eine Beschreibung wird beispielsweise dieser Request-Body gesendet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"formData"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"prompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Deutscher Indie-Pop über einen Neuanfang in Berlin, warme Gitarren, optimistische Stimmung"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"isInstrumental"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nach erfolgreicher Übermittlung sollte ein Client die zurückgegebene &lt;code&gt;taskId&lt;/code&gt; aufbewahren. Damit lässt sich die Statusabfrage nach einer Unterbrechung wieder aufnehmen. Ein Seitenwechsel muss dann nicht automatisch zu einem neuen Generierungsauftrag führen.&lt;/p&gt;

&lt;p&gt;API-Schlüssel gehören dabei in eine vertrauenswürdige Serverumgebung oder in die lokale Konfiguration des MCP-Prozesses. Ein geheimer Account-Schlüssel gehört nicht in ausgelieferten Browsercode.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. HTTP-Erfolg und fachlichen Erfolg unterscheiden
&lt;/h2&gt;

&lt;p&gt;Die API verwendet eine Antwort-Hülle mit &lt;code&gt;code&lt;/code&gt;, &lt;code&gt;message&lt;/code&gt; und &lt;code&gt;data&lt;/code&gt;. Ein fachlicher Fehler kann auch mit HTTP 200 ankommen. Deshalb reicht die Prüfung von &lt;code&gt;response.ok&lt;/code&gt; allein nicht aus.&lt;/p&gt;

&lt;p&gt;Der Adapter prüft beide Ebenen. Hier ein gekürzter Ausschnitt aus der Antwortbehandlung, nachdem die JSON-Struktur bereits validiert wurde:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;"SUCCESS"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApiError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;"SUCCESS"&lt;/span&gt;
      &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="sb"&gt;`HTTP_${response.status}`&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="n"&gt;envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;"string"&lt;/span&gt;
      &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`API error (HTTP ${response.status}).`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;isRecord&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;envelope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Das ist besonders bei Fehlern wie fehlenden Credits wichtig. Ein Client soll diesen Fall verständlich anzeigen können und eventuell mitgelieferte Informationen behalten. Der vollständige Adapter behandelt außerdem ungültiges JSON, fehlende Antwortfelder und Netzwerkfehler.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Polling nach einer Serverentscheidung beenden
&lt;/h2&gt;

&lt;p&gt;Eine verbreitete Versuchung ist, im Client eine eigene Liste aller abschließenden Statuswerte zu pflegen. Ändert sich der Vertrag, können verschiedene Clients dann unterschiedlich entscheiden, ob eine Aufgabe noch läuft.&lt;/p&gt;

&lt;p&gt;Unser Status-Endpunkt liefert dafür &lt;code&gt;isTerminal&lt;/code&gt;. Der Client verwendet dieses Feld, um das Polling zu beenden. Für die Darstellung bleiben &lt;code&gt;status&lt;/code&gt;, vorhandene Tracks und ein möglicher &lt;code&gt;errorCode&lt;/code&gt; relevant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;isTerminal: true&lt;/code&gt; bedeutet, dass der Vorgang abgeschlossen ist. Es bedeutet nicht automatisch, dass ein Song erfolgreich erzeugt wurde.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Für die MCP-Nutzung empfehlen wir ungefähr 15 bis 30 Sekunden Abstand zwischen Statusabfragen. Ein Client sollte jeweils die vorherige Abfrage abwarten, damit bei langsamen Antworten keine überlappenden Requests entstehen.&lt;/p&gt;

&lt;p&gt;Einzelne Tracks können bereits verfügbar sein, bevor der gesamte Auftrag abgeschlossen ist. Deshalb sind zwei Entscheidungen getrennt zu treffen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gibt es bereits einen Track mit einer abspielbaren &lt;code&gt;audioUrl&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Ist die Aufgabe insgesamt abgeschlossen, sodass keine weiteren Statusabfragen nötig sind?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Das ermöglicht eine frühere Wiedergabe, ohne den restlichen Auftrag aus dem Blick zu verlieren. Ob und wie Teilergebnisse angezeigt werden, ist eine Entscheidung der jeweiligen Oberfläche.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Ein Timeout ist ein unbekanntes Ergebnis
&lt;/h2&gt;

&lt;p&gt;Der kritischste Fall entsteht beim Starten einer Generierung:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client sendet POST
        |
Server nimmt den Auftrag an
        |
Antwort erreicht den Client nicht
        |
Client sieht einen Timeout
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Der Auftrag kann zu diesem Zeitpunkt bereits existieren und Credits verbrauchen. Ein automatisches Wiederholen des POST-Requests könnte einen zweiten kostenpflichtigen Auftrag anlegen.&lt;/p&gt;

&lt;p&gt;Unser Adapter wiederholt HTTP-Anfragen deshalb nicht automatisch. Bei einer unsicheren Musikübermittlung fordert er dazu auf, zuerst die Aufgabenhistorie mit &lt;code&gt;kisong_list_tasks&lt;/code&gt; oder im Webkonto zu prüfen.&lt;/p&gt;

&lt;p&gt;Für eine Oberfläche würde ich diesen Fall ausdrücklich als „Übermittlung unklar“ darstellen. Eine passende Meldung wäre:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Die Verbindung wurde unterbrochen. Dein Auftrag könnte bereits gestartet sein. Prüfe zuerst deine letzten Generierungen, bevor du einen neuen Auftrag sendest.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Die Historie ist dabei eine Hilfe zur Klärung, keine Garantie für eine automatische Zuordnung: Wenn jemand mehrere ähnliche Aufträge kurz hintereinander startet, kann die Zuordnung ohne zusätzliche Kennung mehrdeutig sein.&lt;/p&gt;

&lt;p&gt;Für eine zukünftige API-Erweiterung wäre ein serverseitig unterstützter Idempotenzschlüssel eine Möglichkeit, Wiederholungen eindeutig demselben Auftrag zuzuordnen. Der hier beschriebene Adapter setzt eine solche Funktion nicht voraus.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Abbrechen der Abfrage und Abbrechen des Auftrags trennen
&lt;/h2&gt;

&lt;p&gt;Ein lokales &lt;code&gt;AbortSignal&lt;/code&gt; kann einen laufenden HTTP-Request beenden. Daraus folgt jedoch nicht, dass die Musikgenerierung auf dem Server gestoppt wurde.&lt;/p&gt;

&lt;p&gt;Im Adapter werden das konfigurierte Zeitlimit und das Abbruchsignal des MCP-Requests kombiniert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;
  &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;AbortSignal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eine Schaltfläche „Nicht weiter warten“ sollte daher genau diese lokale Wirkung vermitteln. Ohne einen bestätigten serverseitigen Abbruch sollte sie nicht versprechen, den Auftrag oder seine Abrechnung rückgängig zu machen.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Dieselben Grenzen für KI-Assistenten sichtbar machen
&lt;/h2&gt;

&lt;p&gt;Beim Model Context Protocol (MCP) erhält ein Assistent Werkzeuge für einzelne Operationen. Unser Adapter trennt beispielsweise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kisong_get_credits&lt;/code&gt;: Musik-Credits prüfen.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kisong_generate_song&lt;/code&gt;: einen Generierungsauftrag starten.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kisong_get_task&lt;/code&gt;: den Fortschritt eines bekannten Auftrags lesen.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kisong_list_tasks&lt;/code&gt;: die Aufgabenhistorie zur Klärung heranziehen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Generierungswerkzeuge sind mit &lt;code&gt;idempotentHint: false&lt;/code&gt; beschrieben. Status- und Historienwerkzeuge haben Hinweise auf lesende, idempotente Nutzung. Solche Metadaten helfen dem Client bei der Einordnung; sie ersetzen weder Berechtigungsprüfungen noch die eigentliche Fehlerbehandlung.&lt;/p&gt;

&lt;p&gt;Auch die Werkzeugbeschreibungen gehören zum Integrationsdesign: Sie nennen den Credit-Verbrauch, die asynchrone Aufgaben-ID und die Regel, nach einem Timeout nicht automatisch erneut zu generieren.&lt;/p&gt;

&lt;p&gt;Die vollständigen Eingabegrenzen und Endpunkte stehen im &lt;a href="https://github.com/kisongerstellen/ki-song-erstellen-mcp/blob/main/docs/api-contract.md" rel="noopener noreferrer"&gt;API-Vertrag des Adapters&lt;/a&gt;. Das Repository enthält außerdem Tests mit einer lokalen Mock-API für Validierung, fachliche Fehler und das Ausbleiben automatischer Wiederholungen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Was ich daraus für die Oberfläche ableite
&lt;/h2&gt;

&lt;p&gt;Für eine brauchbare Oberfläche würde ich mindestens zwischen „wird übermittelt“, „Generierung läuft“, „Übermittlung unklar“ und einem abschließenden Ergebnis unterscheiden. Verfügbare Tracks können unabhängig davon angezeigt werden.&lt;/p&gt;

&lt;p&gt;Ein Fortschrittsbalken mit erfundenen Prozentwerten hilft dabei wenig. Eine gespeicherte Aufgaben-ID, ein verständlicher Status und ein Weg zurück zur Historie helfen unmittelbar, wenn etwas schiefgeht.&lt;/p&gt;

&lt;p&gt;Wer den Anwendungsfall ausprobieren möchte, findet auf &lt;a href="https://kisongerstellen.com/" rel="noopener noreferrer"&gt;KlangKI&lt;/a&gt; die Generierung aus Beschreibungen und eigenen Songtexten. Der offene MCP-Adapter zeigt dazu die konkrete API-Anbindung in TypeScript.&lt;/p&gt;

&lt;p&gt;Wie behandelt ihr in euren Anwendungen den Fall, dass ein kostenpflichtiger Auftrag möglicherweise angenommen wurde, die Antwort aber verloren geht?&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>architecture</category>
      <category>ai</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
