From c528bd825f0065d27b7d7a136639168d558b9350 Mon Sep 17 00:00:00 2001 From: Baddi86 Date: Sun, 13 Sep 2026 00:38:05 +0200 Subject: [PATCH] fix: mcp timeout --- LehrerApp.McpBridge/RealServerBridge.cs | 16 +++++++++++++++- LehrerApp.McpBridge/StatusMcpServerTool.cs | 12 +++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/LehrerApp.McpBridge/RealServerBridge.cs b/LehrerApp.McpBridge/RealServerBridge.cs index 3df5df9..9d889c4 100644 --- a/LehrerApp.McpBridge/RealServerBridge.cs +++ b/LehrerApp.McpBridge/RealServerBridge.cs @@ -30,12 +30,14 @@ namespace LehrerApp.McpBridge; internal sealed class RealServerBridge : IAsyncDisposable { private const int ConnectTimeoutMs = 3000; + private static readonly TimeSpan ReconnectCooldown = TimeSpan.FromSeconds(3); public McpServerPrimitiveCollection Tools { get; } = new(); private readonly SemaphoreSlim _gate = new(1, 1); private McpClient? _client; private NamedPipeClientStream? _pipe; + private DateTime? _lastFailedConnectAttemptUtc; /// Reicht einen Tool-Aufruf an die echte Verbindung weiter (aufgerufen aus /// ). Bricht die Verbindung erst hier ab, statt sie proaktiv vor @@ -66,19 +68,29 @@ internal sealed class RealServerBridge : IAsyncDisposable /// Versucht, sofern noch nicht verbunden, jetzt sofort eine Verbindung aufzubauen - /// aufgerufen sowohl beim Start der Bridge als auch aus - /// (expliziter Nutzer-/Assistenten-Wunsch, jetzt nachzusehen) und aus dem Hintergrund-Loop. + /// (expliziter Nutzer-/Assistenten-Wunsch, jetzt nachzusehen) und aus dem Hintergrund-Loop. + /// Nach einem fehlgeschlagenen Versuch schlägt ein weiterer Aufruf innerhalb von + /// sofort fehl, statt erneut den vollen Pipe-Connect-Timeout + /// abzuwarten - sonst kostet ein versehentlicher Doppelaufruf (z.B. weil ein KI-Client + /// "lehrerapp_status" trotz gegenteiligem Hinweis im selben Zug erneut aufruft) unnötig weitere + /// mehrere Sekunden. public async Task TryReconnectAsync(CancellationToken ct) { if (_client is not null) return true; + if (IsInCooldown()) return false; await _gate.WaitAsync(ct); try { if (_client is not null) return true; // ein anderer Aufrufer war währenddessen schneller + if (IsInCooldown()) return false; return await ConnectCoreAsync(ct); } finally { _gate.Release(); } } + private bool IsInCooldown() => + _lastFailedConnectAttemptUtc is { } last && DateTime.UtcNow - last < ReconnectCooldown; + private async Task ConnectCoreAsync(CancellationToken ct) { var pipe = new NamedPipeClientStream( @@ -102,6 +114,7 @@ internal sealed class RealServerBridge : IAsyncDisposable _client = client; _pipe = pipe; + _lastFailedConnectAttemptUtc = null; await Console.Error.WriteLineAsync( $"LehrerApp.McpBridge: mit LehrerApp verbunden, {tools.Count} Werkzeug(e) übernommen."); return true; @@ -111,6 +124,7 @@ internal sealed class RealServerBridge : IAsyncDisposable await pipe.DisposeAsync(); var (_, _, stderrLine) = ConnectionDiagnostics.Describe(ex); await Console.Error.WriteLineAsync(stderrLine); + _lastFailedConnectAttemptUtc = DateTime.UtcNow; EnsureFallbackTool(); return false; } diff --git a/LehrerApp.McpBridge/StatusMcpServerTool.cs b/LehrerApp.McpBridge/StatusMcpServerTool.cs index cd8668a..9911e4a 100644 --- a/LehrerApp.McpBridge/StatusMcpServerTool.cs +++ b/LehrerApp.McpBridge/StatusMcpServerTool.cs @@ -24,7 +24,12 @@ internal sealed class StatusMcpServerTool(RealServerBridge bridge) : McpServerTo "(Schüler, Noten, Stundenplanung, Kompetenzen, ...) nicht verfügbar - sie erscheinen erst " + "nach einer erfolgreichen Verbindung in der Werkzeugliste. Bei einer Fehlermeldung eines " + "anderen LehrerApp-Werkzeugs, die auf eine fehlende Verbindung hindeutet, dieses Werkzeug " + - "aufrufen, um den Grund zu klären, statt den Nutzer ohne Erklärung zu vertrösten.", + "aufrufen, um den Grund zu klären, statt den Nutzer ohne Erklärung zu vertrösten. Nach " + + "einem erfolglosen Versuch NICHT im selben Zug erneut aufrufen (ein Verbindungsversuch " + + "dauert bis zu mehrere Sekunden, wiederholte Aufrufe direkt hintereinander können vom " + + "Client blockiert werden) - stattdessen den Nutzer informieren; ein Hintergrund-Prozess " + + "verbindet automatisch neu, sobald LehrerApp läuft, und die Werkzeugliste aktualisiert " + + "sich dann von selbst.", InputSchema = JsonDocument.Parse("""{"type":"object","properties":{}}""").RootElement, }; public override IReadOnlyList Metadata { get; } = []; @@ -44,8 +49,9 @@ internal sealed class StatusMcpServerTool(RealServerBridge bridge) : McpServerTo "sind jetzt verfügbar." : "LehrerApp läuft nicht oder der MCP-Server ist in den Einstellungen nicht " + "aktiviert. Bitte den Nutzer bitten, LehrerApp zu starten und den " + - "MCP-Server in den Einstellungen zu aktivieren - dieses Werkzeug danach " + - "erneut aufrufen, um zu prüfen, ob die Verbindung jetzt klappt.", + "MCP-Server in den Einstellungen zu aktivieren. Dieses Werkzeug NICHT im " + + "selben Zug erneut aufrufen - die Bridge verbindet im Hintergrund " + + "automatisch neu, sobald LehrerApp läuft, und meldet das von selbst.", }, ], };