docs(readme): CORS-Konfiguration und MCP-Endpoint mit curl-Handshake dokumentiert

This commit is contained in:
m3ta-chiron committed 2026-10-18 20:06:11 +02:00
1 parent 150c374bdc
commit 30bd535400
1 file changed
+61 -2
+61 -2
View File
@@ -33,7 +33,7 @@ mit der kompletten Provider-Kette und Daemon-Anbindung (non-blocking über
`talkd`, siehe unten), `mute`, `unmute`, `status`, `cancel`. Die übrigen `talkd`, siehe unten), `mute`, `unmute`, `status`, `cancel`. Die übrigen
Befehle sind Stubs und melden `not implemented yet` (Exit-Code ≠ 0). Befehle sind Stubs und melden `not implemented yet` (Exit-Code ≠ 0).
## Daemon talkd (Task 04 + 05) ## Daemon talkd (Task 04–06)
`talkd` besitzt die Ansage-Queue und spricht seriell ab — genau eine Ansage `talkd` besitzt die Ansage-Queue und spricht seriell ab — genau eine Ansage
gleichzeitig, Reihenfolge `now` > `high` > `normal` > `low` (FIFO je gleichzeitig, Reihenfolge `now` > `high` > `normal` > `low` (FIFO je
@@ -43,9 +43,12 @@ config `[server] port`, Env `TALK_PORT` (schlagen den Default).
| Endpunkt | Wirkung | | Endpunkt | Wirkung |
| --- | --- | | --- | --- |
| `POST /speak` | `{text, priority?, provider?, voice?, lang?, now?}` → `202 {"id":"…"}`; bei Mute: `202 {"id":"","muted":true}`; bei Gesetz-Verwurf: `202 {"id":"","discarded":"rate_limit"\|"quiet_hours"}` (+ `"toast":true` im Toast-Modus) — verworfen, dokumentiertes Verhalten | | `POST /speak` | `{text, priority?, provider?, voice?, lang?, now?}` → `202 {"id":"…"}`; bei Mute: `202 {"id":"","muted":true}`; bei Gesetz-Verwurf: `202 {"id":"","discarded":"rate_limit"\|"quiet_hours"}` (+ `"toast":true` im Toast-Modus) — verworfen, dokumentiertes Verhalten |
| `GET /status` | `{"version", "queue", "queue_ids", "current", "muted", "provider", "rate_limit": {"interval", "allow_high_burst", "next_slot"}, "quiet_hours": {"window", "active", "mode", "until"}}` | | `OPTIONS /speak` | CORS-Preflight → `204` mit Freigabe-Headern, wenn die `Origin` in `server.allowed_origins` steht (siehe unten); sonst `204` ohne CORS-Header |
| `GET /status` | `{"version", "queue", "queue_ids", "current", "muted", "provider", "rate_limit": {"interval", "allow_high_burst", "next_slot"}, "quiet_hours": {"window", "active", "mode", "until"}, "mcp": true, "cors": [origins]}` |
| `POST /cancel` | bricht die laufende Ansage ab + leert die Queue → `200 {"cancelled":n}` | | `POST /cancel` | bricht die laufende Ansage ab + leert die Queue → `200 {"cancelled":n}` |
| `POST /mute` / `POST /unmute` | persistenter Mute-State → `200 {"muted":bool}` | | `POST /mute` / `POST /unmute` | persistenter Mute-State → `200 {"muted":bool}` |
| `POST /mcp` | MCP (Streamable HTTP): JSON-RPC `initialize`, `notifications/initialized`, `ping`, `tools/list`, `tools/call` — ein Tool `speak` (siehe unten) |
| `GET /mcp` | `405` — talkd bietet keine server-initiierten Streams an |
- **CLI-Verhalten:** `talk say` (und `talk "Text"`) erkennt den Daemon via - **CLI-Verhalten:** `talk say` (und `talk "Text"`) erkennt den Daemon via
`GET /status`. Läuft er: enqueue + sofortige Rückkehr (non-blocking, `GET /status`. Läuft er: enqueue + sofortige Rückkehr (non-blocking,
@@ -105,6 +108,62 @@ quiet_mode = "discard" # discard | toast (TALK_QUIET_MODE)
erlaubt), nächster slot …` und `ruhezeit: 18:00-08:00 (modus discard, erlaubt), nächster slot …` und `ruhezeit: 18:00-08:00 (modus discard,
aktiv bis 08:00)`. aktiv bis 08:00)`.
### CORS-Tür + MCP-Endpoint (Task 06)
**CORS** gilt bewusst nur für `POST /speak` — genau die eine Tür, die eine
spätere Browser-Anbindung (z. B. LibreChat) braucht; Steuer-Endpunkte
bleiben browserseitig unerreicht. Die Erlaubnis ist eine exakte Origin-
Liste; `"*"` ist erlaubt, aber nicht Default; **leere Liste (Default) =
gar keine CORS-Header = der Browser blockt**:
```toml
[server]
allowed_origins = ["https://chat.az-gruppe.com"] # Env TALK_ALLOWED_ORIGINS
```
- `OPTIONS /speak` (Preflight) von gelisteter Origin → `204` mit
`Access-Control-Allow-Origin` (die Origin bzw. `*`), `-Methods: POST,
OPTIONS`, `-Headers: Content-Type`, `Max-Age: 600`.
- Echter `POST /speak` mit gelisteter `Origin` trägt denselben ACAO-Header.
- Ungelistete Origins bekommen saubere `204`/`202` **ohne** CORS-Header —
die Blockade macht der Browser.
**MCP** (`POST /mcp`, Streamable HTTP): schlanke eigene Implementierung
des JSON-RPC-Subsets ohne SDK — `initialize` (angefragte
`protocolVersion` wird geechot; Ergebnis `protocolVersion`,
`capabilities: {tools:{}}`, `serverInfo`), `notifications/initialized`
(Notification → `202` ohne Body), `ping`, `tools/list`,
`tools/call`. Der Server ist zustandslos (keine Session-IDs) und pusht
nichts (daher `GET /mcp` → `405`). Ein Tool:
```json
{"name": "speak",
"inputSchema": {"type": "object",
"properties": {"text": {"type": "string"},
"priority": {"enum": ["low","normal","high"]},
"provider": {"type": "string"},
"lang": {"enum": ["de","en"]},
"now": {"type": "boolean"}},
"required": ["text"]}}
```
`tools/call` fährt exakt denselben Aufnahme-Pfad wie `POST /speak`
(inklusive Mute, Rate-Limit, Ruhezeiten). Ergebnis ist immer Text-Content:
`"queued: <id>"` bei Aufnahme; `muted`/`discarded: <grund>` mit Erklärung
— bewusst `isError: false`, denn ein Verwurf ist das korrekte
dokumentierte Ergebnis, kein Protokollfehler. Validierungsfehler
(fehlender Text, ungültige Priorität, unbekanntes Tool) sind dagegen
JSON-RPC-Fehler (`-32602`). Handshake als curl-Sequenz:
```sh
curl -s http://127.0.0.1:7800/mcp -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'
curl -s http://127.0.0.1:7800/mcp -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}'
curl -s http://127.0.0.1:7800/mcp -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"speak","arguments":{"text":"Build fertig"}}}'
```
### Provider-Kette (Task 03) ### Provider-Kette (Task 03)
Ohne Pin läuft die Vier-Stufen-Fallback-Kette Ohne Pin läuft die Vier-Stufen-Fallback-Kette