Skip to main content
Glama
TadMSTR
by TadMSTR

task-queue-mcp

Built with Claude Code License: MIT

Ein FastMCP-Server, der die Task-Warteschlange der Agenten-Orchestrierung als MCP-Tool-Schnittstelle bereitstellt. Agenten übermitteln Tasks, prüfen Status und erfassen Abschlüsse über typisierte, validierte Tools statt über rohe YAML-Dateiänderungen.

Läuft als Docker-Container auf Port 8485. Global in ~/.claude.json eingebunden, sodass alle Claude-Code-Agent-Sitzungen darauf zugreifen können.

Tools

Tool

Description

submit_task

Neuen Task mit status: submitted anlegen

list_tasks

Tasks mit optionalen Filtern auflisten; TTL-abgelaufene Tasks werden ausgeschlossen

get_task

Einzelnen Task per UUID abrufen (auch archivierte Tasks werden aufgelöst)

update_task

Agentenbezogene Statusüberführung (streng); hängt einen Historieeintrag an

set_task_status

Operator-Statusänderung – genehmigen, abbrechen, parken oder einen übersehenen Task vorziehen (auditierte Überschreibung)

cancel_task

Sauberer Endzustand cancelled für veraltete Tasks (Datensatz bleibt erhalten, wird nie gelöscht)

park_task

Einen Task pausieren, ohne ihn zu verstecken – bleibt gelistet, von TTL ausgenommen, nichts pickt ihn auf

unpark_task

Einen geparkten Task in den Status zurückführen, aus dem er geparkt wurde

amend_task

Korrektur an einen in der Warteschlange befindlichen Task anhängen; ursprüngliche Beschreibung wird nie überschrieben

Agenten nutzen den strengen update_task-Pfad; Operatoren (über die HTTP-Steuer-API) verwenden set_task_status / cancel_task / park_task / unpark_task. Agenten können weder abbrechen noch parken – beides ist Operatoren vorbehalten. amend_task ist die Ausnahme: Der Quell-Agent des Tasks darf ihn änder, ändern, aber der Ziel-Agent nicht.

submit_task

submit_task(
    source_agent="research",
    target_agent="deploy-agent",  # agent name or "auto" for dispatcher routing
    # build | deploy | fix | research | review | audit | notify | docs |
    # ticket_audit | ticket_audit_complete
    task_type="build",
    summary="Deploy qmd update",
    description="Apply the qmd stack update from build plan...",
    risk_level="low",  # low | medium | high (default: low)
    requires_approval=False,  # explicit override of approval gate
    priority="normal",  # normal | high | urgent (default: normal)
    context_refs=["/srv/agents/build-plans/qmd/plan.md"],  # absolute paths only
    ttl_days=30,
    workflow_mode="semi-auto",  # semi-auto | auto (default: semi-auto)
    originating_task_id=None,  # UUID of the parent task, if this is a return task
)
# → {"ok": true, "task_id": "<uuid>", "filename": "<timestamp>-<slug>.yml"}

context_refs müssen absolute Pfade sein. risk_level und priority werden gegen Allowlisten validiert. workflow_mode steuert das Dispatcher-Verhalten: semi-auto (Standard) reiht den Task zur Operator-Übernahme mit einer Matrix-Benachrichtigung in die Warteschlange ein, während auto den Dispatcher veranlasst, den Ziel-Agenten ohne Oberfläche (headless) zu starten. Der Server erzeugt die UUID, setzt created und initialisiert retry_policy.

Automatischer Abschluss des Ursprungs-Tasks (seit v0.6.0)

Übergib originating_task_id, und der Parent-Task wird als completed geschlossen – das Einreichen des Rückgabe-Tasks ist das, was die Anfrage schließt. Die Antwort erhält auto_closed_task_id, wenn dies zutrifft.

Es wird nur ausgelöst, wenn alle diese Bedingungen erfüllt sind:

Bedingung

Warum

der Parent-Task existiert und ist nicht archiviert

sonst gibt es nichts zu schließen

parent.target_agent == source_agent

die Begrenzung des gesamten Features – Agent A darf nicht durch die Angabe als Parent-Task stehenden Tasks von Agent B schließen können. Dies wird explizit geprüft und nicht der Besitzkontrolle von update_task überlassen, die auch "operator" akzeptiert.

parent.source_agent == target_agent

die andere Hälfte der Rückgabe-Gestalt – du beantwortest den, der gefragt hat. Ohne diese sieht eine Weiterleitungsanfrage identisch mit einer Rückgabe aus (siehe unten)

Parent-Task ist bei approved oder in-progress

parked ist eine bewusste Pause durch den Operator; submitted/pending-approval sind noch nicht genehmigt; routing-failed ist noch in Dispatcher-Wiederholungen

Warum beide Hälften (seit v0.6.1). originating_task_id ist überladen: Bei einem Rückgabe-Task bedeutet es „das beantwortet diese Anfrage“, bei einer Weiterleitungsanfrage bedeutet es „workflow_mode von diesem Parent-Task erben“ – genau das übergibt ein Build-Agent, wenn er eine Audit-Anfrage für seinen eigenen laufenden Build einreicht. Mit nur der ersten Prüfung werden beide Fälle verwechselt: Wenn der Build-Task den Build-Agenten als Ziel hat und der Build-Agent der Absender ist, schloss v0.6.0 mit nur der ersten Bedingung innerhalb prompt einen echten laufenden Build-Task ab.

Eine echte Rückgabe ist symmetrisch; eine Weiterleitung ist es nicht.

parent-Task

neuer Task

löst aus?

Rückgabe

Audit developer → security

security → developer

ja – beide Hälften gelten

Weiterleitung

Build dodaver → developer

Audit-Anfrage developer → security

nein – dodaver != security

Ein approved Parent-Task wird zuerst durch in-progress geführt, sodass die History als „beansprucht, dann geschlossen“ gelesen wird, statt teleportiert zu werden.

Dies ist ein Sicherheitsnetz, kein Primärweg. Continue to expect agents to close their own tasks explicitly – that puts the agent's own note in the history, while this writes only auto-closed: return task <id> submitted. Every failure in close is logged at warning level and the return Task is executed normally; it can never fail the task created by this side effect.

reword a sentence

list_tasks

list_tasks(
    target_agent="deploy-agent",  # optional
    source_agent="research",  # optional
    status="approved,in-progress",  # comma-separated, optional
    task_type="build",  # optional
    include_archived=False,  # include archive/ subdirectory
    limit=20,  # max 200
)
# → list of task dicts, sorted by created descending

Ein nicht erkannter status ist ein Fehler, kein leeres Ergebnis (seit v0.6.0). Früher wurde stillschweigend gefiltert. So lieferte eine Suche nach status="pending" – hier kein echter Status – monatelang [], ununterscheidbar von „keine Arbeit für dich“. Eine leere Liste ist diekorrekte Antwort auf eine wohlgeformte Frage; daher ist die einzige Möglichkeit, einen Tippfehler von einer leeren Warteschlange zu unterscheiden, den Tippfehler abzulehnen. Leerzeichen und ein abschließendes Komma werden weiterhin toleriert; ein leerer String weiterhin gilt als Filter.

Terminale Tasks, deren, deren Zeit nicht funktioniert, mehr als ttl_days beträgt, werden ausgeschlossen. Der Dispatcher ist maßgeblich für die TTL-Archivierung, aber list_tasks filtert abgeschlossene Datensätze proaktiv heraus, damit Agenten nicht mit veralteten Elementen arbeiten.

Nicht-terminale Tasks sind niemals TTL-gefiltert (seit v0.8.1, vikunja#395). Offene Arbeiten verschwanden nach ttl_days aus den Auflistungen, obwohl sie noch auf der Disk warteten – eine blinder Fleck, keine Schutzmechanismus; dies hatte bereits dazu geführt, dass eine Warteschlangenaktion 17 verwaiste Tasks fand, wo dieses Tool 13 gemeldet hatte. Nichts, was noch zu jemandes Verantwortung ist, sollte eine Uhr unsichtbar machen: Ein Agent kann einen veralteten offenen Task beurteilen; niemand kann auf einen Task zugreifen, den er nicht sieht.

Parked Tasks sind von der TTL-Filterung ausgenommen. Parken ist eine bewusste „Pause, ich komme zurück“ – ein geparkter Task, der leise aus der Liste entschwindet, würde den Status ad absurdumeführen.

get_task

get_task(task_id="a7f3d2c1-1234-5678-abcd-000000000000")
# → full task dict, or {"ok": false, "error": "not found"}

Durchsucht zuerst die Hauptwarteschlange, danach archive/. Erfordert eine vollständige UUID – kein Präfix-Abglecich.

update_task

update_task(
    task_id="a7f3d2c1-1234-5678-abcd-000000000000",
    status="in-progress",  # see transition table below
    actor="deploy-agent",
    note="Claimed task, starting build.",
    output=None,  # written to result.output on completed/failed
)
# → {"ok": true, "task_id": "<uuid>"} or {"ok": false, "error": "..."}

Besitzprüfung (seit v0.5.0): actor muss mit target_agent des Tasks übereinstimmen oder "operator" sein – jeder andere Ausführer wird abgeleehnt. Das schließt die Lücke, durch die ein Agent, andere der das Task zugewiesen wurde, es beanspricht oder abschließt.

Zulässige Überführungen:

Von

Zu

approved

in_progress

in_progress

completed

Beliebiger nicht-tendler Status

failed

Nicht-terminal: submitted, pending-approval, approved, in-progress, parked, routing-failed. Terminal: completed, failed, cancelled.

routing-failed ist der Dispatcher-schreibt und ist aus der Zeile Any non-terminal → failed oben bewusst herausgenommen – ein Agent darf eine von einem Dispatcher noch erneut versuchendem Task nicht endparamterial schließen. Es ist eine ganz normale Quelle für die Operator-Übergänge unten (cancelled, parked, override).

retry_policy ist im Dispatcher-Besitz; update_task rührt ihn nie an.

Operator-Überführungen (set_task_status)

Wieder mehr Möglichkeiten als update_task, aber weiterhin überwacht und begrenzt:

Von

Zu

Hinweise

submitted / pending-approval

approved

Standard

Beliebiger nicht-terminaler Status

cancelled

Standard (auch über cancel_task)

Beliebiger nicht-terminaler Status

parked

Standard (auch über park_task)

Beliebiger nicht-terminaler Status

Beliebiger nicht-terminal Status

Erfordert allow_override=True + eine nicht-leere Notiz (die „Aufgabe als versäumt vorziehen“-Überschreibung)

Beliebiger unerkannten Status

Beliebiger gültiger Status

Erfordert allow_override=True + eine Zettel (der Reparaturpfad)

Terminale Tasks sind auch für Operatoren unveränderlich. Jede Operator-Änderung fügt einen Historieeintrag mit actor + note hinzu.

Der Reparaturpfad existiert, weil das Warteschlangenverzeichnis mehr als einen Schreiber hat. Ein Datensatz, dessen Status komplett außerhalb des Vokabulars dieses Servers schlägt – ein historischer Tippfehler complete oder ein zukünftigen dispatcher-Status, der hier noch nicht zugelassen ist – ist über alle anderen Zweige unerreichbar und wäre sonst permanent gerutschtert. Die Reparatur bewegt einen Task immer aus einem ungültigen Status; das Ziel muss weiterhin gültig sein und der Historieintrag erfasst repaired_from. routing-failed braucht diesen Pfad nicht mehr – es ist jetzt ein erstklassiger nicht-terminaler Status (siehe oben), erreichbar über die normalen cancelled/parked-Zeilen oder die normale Override-Zeile.

park_task / unpark_task

park_task(task_id="...", actor="operator", note="waiting on upstream fix")
# → {"ok": true, "task_id": "<uuid>"}

unpark_task(task_id="...", actor="operator", status=None)
# → returns the task to the status it was parked from

Parken ändert nur den Status – YAML wird nie bewegt. Der Task bleibt in in list_tasks sichtbar, ist von TTL-Ablauf ausgenommen, und nichts pickt ihn auf, genau die Dispatcher-Holschleifen matchen nur submitted und routing-failed. Der vorherige Status wird in parked_from gespeichert und beim Verlassen entfernt, er kann also nie überaltern. Übergib status an unpark_task, damit der Task wo anders hingeschickt wird, als er herkam – daher erforderlich für für einen _Task, der direkt per YAML geschrieben wurde und findet kein parked_from vor.

Parken ist für „nicht jetzt, aber bitte nicht verlieren“. Ein lange unbeparkter Task ist nicht zwangsläufig Vernachlässigung, und parked ist die Vokabular, die ein absichtliches Lesezeichen von etwas wirklich Verlassenem unterscheidet.

amend_task

amend_task(
    task_id="...",
    amendment="Preflight answered the open question — FastMCP mount() is live-linked.",
    actor="research",  # the task's source_agent, or "operator"
    reason="preflight ran after queuing",
)
# → {"ok": true, "task_id": "...", "amendment_count": 1, "agent_may_have_started": false}

Sobald ein Task eingereiht ist, ist seine Beschreibung unveränderlich. Wenn sich zwischen Einreihung und Start etwas ändert – eine Preflight-Check beantwortet eine offene Frage, eine Dependency ist fertig, ein Reviewer entdeckt einen Fehler, den Scope verengt sich – die konnte die Korrektur sonst nirgendwo hingehen, und ein Agent, der die Beschreibung des Task vertraut, macht falsch.

amend_task schließt diese Lücke nur durch Anhängen (append-only). payload.description wird nie verändert; Änderungen werden unter payload.amendments als {timestamp, actor, reason, text} gesammelt und von Lesern nach der Beschreibung gerendert. Das, was ursprünglich gefragt war, bleibt im Datensatz erhalten.

Regel

Verhalten

Wer ändern darf

Die source_agent der Aufgabe oder der operator. Der Ziel-Agent ist abgelehnt — er darf die ihm übergebenen Anweisungen nicht umschreiben, dieselbe Vertrauensgrenze, die cancelled auf den operator beschränkt.

Wann

Jede nicht-abgeschlossene Aufgabe, einschließlich in-progress und parked. Abgeschlossene und archivierte Aufgaben werden abgelehnt.

in-progress

Zulässig — es ist der Fall, der am meisten zählt — aber die Antwort setzt agent_may_have_started: true, da der Agent die ursprüngliche Aufgabe möglicherweise bereits gelesen hat. Außerhalb des Protokolls mitteilen.

Grenzen

10 Änderungen pro Aufgabe, jeweils 4096 Zeichen.

Umfangswarnung: Mehr als ein oder zwei Änderungen an einer Aufgabe sind ein Signal zum Abbrechen und erneuten Einreihen, statt weiter anzusammeln. Die Grenzen sind eine Absicherung, kein Budget.

Related MCP server: task-manager-mcp

Status-Lebenszyklus

submitted → [pending-approval] → approved → in-progress → completed
                                                 ↓
                                              failed

routing-failed  # dispatcher-written on a failed dispatch attempt; non-terminal

Any non-terminal ──(operator)──> cancelled     # graceful dismissal, record kept
Any non-terminal <──(operator)──> parked       # pause; stays listed, TTL-exempt

Der Dispatcher besitzt die Übergänge submitted → approved/pending-approval und schreibt außerdem routing-failed, wenn ein Zustellversuch fehlschlägt (er wiederholt nach eigenem Zeitplan; Operatoren können den Status auch über set_task_status an anderer Stelle abbrechen, parken oder erzwingen). Agenten besitzen approved → in-progress → completed (oder failed) — routing-failed ist über update_task nicht erreichbar. Operatoren besitzen cancelled, parked und geprüfte Statusüberschreibungen. Die Freigabe wird durch Agenten-Manifeste und das Feld requires_approval gesteuert.

Jede Aufgabe wird von ihrem Ziel-Agenten geschlossen. Das folgt aus der Eigentümerprüfung von update_task, und es ist die eine Regel, die man beim Verdrahten eines neuen agentenübergreifenden Workflows im Kopf behalten sollte: Der Agent, der eine Anfrage einreicht, kann sie nicht schließen, weil die Anfrage an jemand anderen gerichtet ist. Ein Anfrage-/Antwort-Paar benötigt daher den empfangenden Agenten, um seinen eigenen Eintrag zu beanspruchen und zu schließen — zwei Aufrufe, da completed nur von in-progress aus erreichbar ist. Der Auto-Schließen ist die Absicherung für den Fall, dass es nicht passiert, kein Ersatz dafür.

HTTP-Steuerungs-API

Nicht-MCP-Clients (das CloudCLI-Plugin und der Matrix-Bot) können den Python-Kern nicht importieren, daher laufen alle ihre Änderungen über eine schlanke HTTP-Steuerungs-API, die als FastMCP-Custom-Routen auf Port 8485 bereitgestellt wird. Jeder Endpunkt delegiert an die oben genannten Tool-Handler und erbt damit die Übergangsvalidierung, fcntl-Sperrung und atomare Schreibvorgänge — es gibt also genau einen validierten Schreibpfad für das gesamte System.

Methode

Pfad

Delegiert an

POST

/tasks/{id}/approve

set_task_status(approved)

POST

/tasks/{id}/cancel

cancel_task

POST

/tasks/{id}/status

set_task_status (Body: status, note, allow_override)

POST

/tasks/{id}/park

park_task

POST

/tasks/{id}/unpark

unpark_task (Body: optional status)

POST

/tasks/{id}/amend

amend_task (Body: amendment, optional reason)

POST

/tasks/{id}/update

update_task (Body: status, note, output, optional on_behalf_of)

GET

/queue/summary

Zählungen nach Status über die aktive Warteschlange

Body-Felder: note, plus status / allow_override für die Status-Route, amendment / reason für Amend, status / output / on_behalf_of für Update. Antworten bilden das kanonische Ergebnis ab: 200 ok, 404 nicht gefunden, 400 Validierungs-/Übergangsfehler.

actor ist auf jeder dieser Routen an operator gebunden und wird nicht aus dem Body gelesen (seit v0.8.0). Zuvor war es body.get("actor", "operator") — in der Praxis korrekt, aber es machte die Operator-Identität zu etwas, das ein Aufrufer durch Weglassen erbte, statt zu etwas, das jemand wählte. Die Bindung bedeutet, dass ein zukünftiger Nicht-Operator-Client hier nicht stillschweigend die Identität erlangen kann, die jede Eigentümerprüfung ausnimmt.

Der Operator-Durchgriff — POST /tasks/{id}/update

Der einzige Pfad zu einem abschließenden Übergang bei der Aufgabe eines anderen Agenten. Er existiert, weil v0.8.0 die unehrliche Variante schloss: Agenten pflegten eine gestrandete Aufgabe aufzuräumen, indem sie den Namen dieses Agenten als actor übergaben, was die Bindung von actor an ein Inhaber-Token unmöglich macht. Nichts anderes erreicht ihn — set_task_status kann keine abschließenden Übergänge vornehmen und das update_task-Tool verlangt nun die aufgelöste Identität — daher müsste ohne dies jede verwaiste Aufgabe einen manuellen Eingriff des Operators benötigen.

Übergib on_behalf_of mit dem Namen des Agenten, dessen Aufgabe es ist. Der Handler prüft ihn gegen den tatsächlichen target_agent der Aufgabe (eine Abweichung ist ein 400, denn einem Operator, der eine falsch identifizierte Aufgabe schließt, soll das mitgeteilt werden, nicht als vorsätzlicher Fehler protokolliert werden) und schreibt beide Namen in die Historie:

history:
  - timestamp: ...
    status: completed
    actor: operator
    on_behalf_of: developer
    note: "stranded; swept during queue cleanup"

Ein Durchgriff soll Jahre später auch als Durchgriff lesbar sein, nicht so, als hätte der Agent seine eigene Arbeit stillschweigend abgeschlossen. on_behalf_of ist optional — das Weglassen bedeutet, dass der Operator in eigenem Namen handelt — und wird für jeden Nicht-operator-Actor ausdrücklich abgelehnt.

GET /queue/summary gibt {"ok": true, "counts": {...}, "active": N, "total": N} zurück, wobei active die Summe der nicht-abgeschlossenen Status ist (jetzt einschließlich routing-failed, nach Name gezählt). Status außerhalb des Vokabulars werden unter "unknown" einsortiert statt verworfen, sodass Einträge anderer direkter YAML-Schreiber sichtbar bleiben.

Auth: Die Custom-Routen umgehen die Bearer-Authentifizierung des Transports, daher ist ein Shared-Secret-Header das Tor — und diese Routen liegen bewusst außerhalb davon, weil sie die Operator-Oberfläche sind:

  • Sende X-Task-Queue-Secret: $TASK_QUEUE_API_SECRET bei jeder Änderung.

  • Der Server vergleicht es in konstanter Zeit (hmac.compare_digest) und schließt fehlgeschlagen ab (401), wenn das Secret fehlt, falsch oder nicht konfiguriert ist.

  • Das Secret liegt in einer vom Operator verwalteten Env-Datei außerhalb des Repos und wird über env_file in den Container und in die Umgebung jedes Clients injiziert — niemals ins Quellverzeichnis committet.

Bereitstellung

Docker (Produktion)

services:
  task-queue-mcp:
    image: task-queue-mcp:latest
    container_name: task-queue-mcp
    ports:
      # The loopback bind is load-bearing, not cosmetic. The MCP transport on this port
      # is unauthenticated (see Trust model below), so publishing it as "8485:8485"
      # would expose an unauthenticated queue-mutation endpoint to your whole LAN.
      - "127.0.0.1:8485:8485"
    volumes:
      - ~/.claude/task-queue:/task-queue   # host queue directory
    environment:
      - TASK_QUEUE_DIR=/task-queue
      # 0.0.0.0 here is the *container-internal* bind and must stay wide, or the port
      # mapping above has nothing to forward to. The host-side bind is what limits reach.
      - MCP_HOST=0.0.0.0
      - MCP_PORT=8485
    cap_drop: [ALL]
    security_opt: [no-new-privileges:true]
    read_only: true
    tmpfs: [/tmp]
    user: "1000:1000"
    restart: unless-stopped
    networks:
      - agent-net

Der Container mountet nur das Task-Queue-Verzeichnis beschreibbar. Der Rest des Dateisystems ist schreibgeschützt. /tmp ist ein tmpfs für flüchtigen Zwischenspeicher.

Claude-Code-Einstellungen

{
  "mcpServers": {
    "task-queue-mcp": {
      "type": "url",
      "url": "http://localhost:8485/mcp"
    }
  }
}

Umgebungsvariablen

Variable

Standard

Beschreibung

TASK_QUEUE_DIR

/task-queue

Pfad zum Task-Queue-Verzeichnis im Container

MCP_HOST

0.0.0.0

Bind-Adresse für den HTTP-Server

MCP_PORT

8485

Port für den HTTP-Server

TASK_QUEUE_API_SECRET

Gemeinsames Secret für die HTTP-Steuerungs-API. Erforderlich für jede Steuerungs-API-Änderung — schließt fehlgeschlagen ab (401), wenn nicht gesetzt. Die MCP-Tools selbst verwenden es nicht.

TASK_QUEUE_TOKEN_<AGENT>

Bearer-Token für einen aufrufenden Agenten, z. B. TASK_QUEUE_TOKEN_DEVELOPER. Mindestens eines ist erforderlich — der HTTP-Transport weigert sich, ohne eines zu starten. Das Suffix wird zur Agenten-Identität, kleingeschrieben mit _- (TASK_QUEUE_TOKEN_DOC_HEALTHdoc-health).

Jeder Agent benötigt sein eigenes Token — das Token ist das, was den Aufrufer identifiziert, daher macht das Teilen eines Tokens zwischen zwei Agenten die Zuordnung bedeutungslos. Der Server weigert sich zu starten bei einem geteilten Token, einem leeren Wert, einem Token unter 16 Zeichen oder einem Token, das für die reservierte operator-Identität geprägt wurde. Erzeugen mit:

python -c "import secrets; print(secrets.token_urlsafe(32))"

Aufrufer übergeben es als standardmäßigen Bearer-Header:

headers:
  Authorization: "Bearer ${TASK_QUEUE_TOKEN}"

Erstellung

docker build -t task-queue-mcp:latest .

Entwicklung

Erfordert Python 3.11+.

pip install -e ".[dev]"

# Lint + format (Baseline gate)
ruff check .
ruff format --check .

# Tests with coverage (gate: >=80%)
python -m pytest --cov=src --cov-report=term-missing

# Run server locally against a local task-queue directory
TASK_QUEUE_DIR=~/.claude/task-queue python -m src.server

Die Testsuite deckt jedes Tool und die HTTP-Steuerungs-API ab — Validierungsgrenzfälle, feindselige YAML-Zeichenketten, unzulässige Übergänge, den Park/Unpark-Roundtrip, amend_task-Autorisierung (einschließlich des abgelehnten Ziel-Agenten), Operator-Override-Prüfung, Reparatur von Status außerhalb des Vokabulars und das Shared-Secret-Tor (fehlendes/falsches Secret → 401). Alle Schreibvorgänge verwenden yaml.dump — niemals String-Interpolation — um YAML-Injection zu verhindern.

Sicherheit

Beide Oberflächen auf Port 8485 erfordern eine Berechtigung:

  • MCP-Tool-Pfad (/mcp) — ein pro-Agent-Bearer-Token, verifiziert durch FastMCPs StaticTokenVerifier. Fehlendes oder unbekanntes Token → 401. Der Transport weigert sich, ohne konfigurierte Token zu starten, sodass dies nicht stillschweigend offen fehlschlagen kann.

  • HTTP-Steuerungsrouten (/tasks/..., /queue/summary) — ein Shared-Secret-Header (X-Task-Queue-Secret, Konstantzeit-Vergleich, Fail-Closed). Siehe HTTP-Steuerungs-API.

Der Container läuft als UID 1000 mit cap_drop: ALL, no-new-privileges und einem schreibgeschützten Root-Dateisystem (nur /task-queue ist beschreibbar).

Vertrauensmodell

Bis v0.7.0 war der MCP-Tool-Pfad unauthentifiziert und die README argumentierte, dass Loopback eine ausreichende Vertrauensgrenze sei. Das war sie nicht: Der Port ist veröffentlicht und der Container tritt einem gemeinsamen Docker-Netzwerk bei, sodass jeder Container in diesem Netzwerk ebenfalls den Tool-Pfad erreichen konnte. Jeder von ihnen konnte set_task_status, cancel_task, park_task, unpark_task oder amend_task aufrufen und dabei einen beliebigen actor behaupten — einschließlich operator, den die Eigentümerprüfungen ausdrücklich ausnehmen. Das machte completed_by und history[].actor zu Behauptungen statt zu Belegen. (vikunja#387)

v0.7.0 schließt diesen Pfad. Jeder Agent hält ein eigenes Token, daher authentifiziert das Token den Aufrufer und identifiziert ihn zugleich. Es gibt bewusst keinen separaten Identitäts-Header: Sobald ein Agent ein Token besitzt, kann er bei einer direkten Anfrage jeden beliebigen Header setzen, daher wäre eine aus dem Header abgeleitete Identität ein strikt schwächerer zweiter Kanal, der mit dem aus dem Token abgeleiteten konkurriert. Eine Identitätsquelle, nicht zwei.

Was dies leistet und was nicht. Es enthält einen fehlgeleiteten oder prompt-injizierten Agenten, der über seine eigene Werkzeugoberfläche agiert, und es stellt sicher, dass das Audit-Protokoll das bedeutet, was es sagt. Es ist bewusst keine Grenze gegen einen Agenten, der nach Anmeldeinformationen sucht: Wenn Agenten über ein Shell-Werkzeug verfügen und als derselbe Betriebssystem-Benutzer laufen, dem die geheimen Dateien gehören, ist jedes Token auf dem Host für jeden von ihnen lesbar. Um das zu schließen, braucht es betriebssystemweite Benutzerkonten pro Agent oder einen Anmeldeinformations-Vermittler – beides liegt außerhalb des Rahmens dieses Servers.

Die operator-Identität ist ausschließlich über die HTTP-Steuerungsrouten erreichbar. Ein TASK_QUEUE_TOKEN_OPERATOR wird beim Start abgelehnt, weil operator von jeder Besitzprüfung ausgenommen ist und ein Token, das über den agentenorientierten Transport geprägt wird, dem Inhaber die gesamte Warteschlange übergeben würde.

Identitätsbindung (seit v0.8.0)

actor wird aus dem Bearer-Token abgeleitet, nicht aus der Übergabe übernommen. Die Übergabe eines Namens, der nicht mit der authentifizierten Identität übereinstimmt, wird abgelehnt, statt stillschweigend korrigiert zu werden – ein falscher Name in einem Aufruf ist ein Fehler, der sichtbar gemacht werden sollte. Das Weglassen ist in Ordnung; der Wert wird aus dem Token befüllt.

Dies gilt auch für source_agent bei submit_task, das eine Identitätsangabe und nicht nur ein Label ist: Die automatische Schließung zum Zeitpunkt der Einreichung entscheidet anhand von source_agent/target_agent, ob sie ausgelöst wird – eine Fälschung würde also die Aufgabe eines anderen Agenten endgültig schließen, ohne dass jemals update_task aufgerufen wird.

Werkzeug

Wer es aufrufen darf

submit_task, list_tasks, get_task

jeder authentifizierte Agent (source_agent ist an den Aufrufer gebunden)

update_task

der target_agent der Aufgabe oder der operator

park_task, unpark_task

der target_agent der Aufgabe oder der operator

amend_task

der source_agent der Aufgabe oder der operator

set_task_status, cancel_task

nur operator – für jede Agenten-Identität verweigert

set_task_status ist nur für den operator vorgesehen, weil sein allow_override-Pfad eine Aufgabe zwischen zwei beliebigen Nicht-Endstatus verschieben kann – genau so lässt sich eine Übergangsregel umgehen, statt sie zu erfüllen. cancel_task ist ein endgültiges, unwiderrufliches Urteil über die Arbeit eines anderen; ein Agent, der seine eigene Aufgabe abbricht, markiert sie über update_task mit einer Begründung als failed.

Schema der Aufgabendatei

Aufgaben sind YAML-Dateien in ~/.claude/task-queue/, benannt nach dem Muster YYYYMMDD-HHMMSS-<uuid-präfix>.yml. Alle Schreibvorgänge sind atomar (zuerst in .tmp schreiben, dann os.rename()). Dateisperren pro Aufgabe über fcntl.flock verhindern Wettlaufsituationen zwischen gleichzeitigen MCP-Aufrufen und dem Dispatcher.

Die vollständige Dokumentation zu Schema und Lebenszyklus finden Sie in der homelab-agent-Komponentendokumentation.

Verwandte Themen

A
license - permissive license
Not graded
quality - not tested
A
maintenance

Maintenance

Maintainers
Response time
1wRelease cycle
8Releases (12mo)
Commit activity

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

  • A
    license
    A
    quality
    D
    maintenance
    Model Context Protocol server for Task Management. This allows Claude Desktop (or any MCP client) to manage and execute tasks in a queue-based system.
    10
    154
    215
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    A task manager MCP server that demonstrates all three MCP primitives (tools, resources, prompts). Enables users to manage tasks, read task summaries and details, and run structured planning/review prompts through natural language.
  • F
    license
    A
    quality
    C
    maintenance
    A production-ready MCP server for task management, enabling LLMs to create, list, and manage tasks via tools and resources, with support for local stdio and cloud Streamable HTTP deployment.
    5

View all related MCP servers

Related MCP Connectors

  • Project management MCP for AI agents with safe task reads and writes.

  • Reliable async execution for agent tool calls: schema gating, retries, idempotency, audit trail.

  • Coordinate multiple AI agents over MCP: atomic claims, leases, shared ledger, handoffs, tasks.

View all MCP Connectors

Latest Blog Posts

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/TadMSTR/task-queue-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server