@@ -30,12 +30,14 @@ namespace LehrerApp.McpBridge;
|
|||||||
internal sealed class RealServerBridge : IAsyncDisposable
|
internal sealed class RealServerBridge : IAsyncDisposable
|
||||||
{
|
{
|
||||||
private const int ConnectTimeoutMs = 3000;
|
private const int ConnectTimeoutMs = 3000;
|
||||||
|
private static readonly TimeSpan ReconnectCooldown = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
public McpServerPrimitiveCollection<McpServerTool> Tools { get; } = new();
|
public McpServerPrimitiveCollection<McpServerTool> Tools { get; } = new();
|
||||||
|
|
||||||
private readonly SemaphoreSlim _gate = new(1, 1);
|
private readonly SemaphoreSlim _gate = new(1, 1);
|
||||||
private McpClient? _client;
|
private McpClient? _client;
|
||||||
private NamedPipeClientStream? _pipe;
|
private NamedPipeClientStream? _pipe;
|
||||||
|
private DateTime? _lastFailedConnectAttemptUtc;
|
||||||
|
|
||||||
/// <summary>Reicht einen Tool-Aufruf an die echte Verbindung weiter (aufgerufen aus
|
/// <summary>Reicht einen Tool-Aufruf an die echte Verbindung weiter (aufgerufen aus
|
||||||
/// <see cref="ProxyMcpServerTool"/>). Bricht die Verbindung erst hier ab, statt sie proaktiv vor
|
/// <see cref="ProxyMcpServerTool"/>). Bricht die Verbindung erst hier ab, statt sie proaktiv vor
|
||||||
@@ -66,19 +68,29 @@ internal sealed class RealServerBridge : IAsyncDisposable
|
|||||||
|
|
||||||
/// <summary>Versucht, sofern noch nicht verbunden, jetzt sofort eine Verbindung aufzubauen -
|
/// <summary>Versucht, sofern noch nicht verbunden, jetzt sofort eine Verbindung aufzubauen -
|
||||||
/// aufgerufen sowohl beim Start der Bridge als auch aus <see cref="StatusMcpServerTool"/>
|
/// aufgerufen sowohl beim Start der Bridge als auch aus <see cref="StatusMcpServerTool"/>
|
||||||
/// (expliziter Nutzer-/Assistenten-Wunsch, jetzt nachzusehen) und aus dem Hintergrund-Loop.</summary>
|
/// (expliziter Nutzer-/Assistenten-Wunsch, jetzt nachzusehen) und aus dem Hintergrund-Loop.
|
||||||
|
/// Nach einem fehlgeschlagenen Versuch schlägt ein weiterer Aufruf innerhalb von
|
||||||
|
/// <see cref="ReconnectCooldown"/> 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.</summary>
|
||||||
public async Task<bool> TryReconnectAsync(CancellationToken ct)
|
public async Task<bool> TryReconnectAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (_client is not null) return true;
|
if (_client is not null) return true;
|
||||||
|
if (IsInCooldown()) return false;
|
||||||
await _gate.WaitAsync(ct);
|
await _gate.WaitAsync(ct);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (_client is not null) return true; // ein anderer Aufrufer war währenddessen schneller
|
if (_client is not null) return true; // ein anderer Aufrufer war währenddessen schneller
|
||||||
|
if (IsInCooldown()) return false;
|
||||||
return await ConnectCoreAsync(ct);
|
return await ConnectCoreAsync(ct);
|
||||||
}
|
}
|
||||||
finally { _gate.Release(); }
|
finally { _gate.Release(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsInCooldown() =>
|
||||||
|
_lastFailedConnectAttemptUtc is { } last && DateTime.UtcNow - last < ReconnectCooldown;
|
||||||
|
|
||||||
private async Task<bool> ConnectCoreAsync(CancellationToken ct)
|
private async Task<bool> ConnectCoreAsync(CancellationToken ct)
|
||||||
{
|
{
|
||||||
var pipe = new NamedPipeClientStream(
|
var pipe = new NamedPipeClientStream(
|
||||||
@@ -102,6 +114,7 @@ internal sealed class RealServerBridge : IAsyncDisposable
|
|||||||
|
|
||||||
_client = client;
|
_client = client;
|
||||||
_pipe = pipe;
|
_pipe = pipe;
|
||||||
|
_lastFailedConnectAttemptUtc = null;
|
||||||
await Console.Error.WriteLineAsync(
|
await Console.Error.WriteLineAsync(
|
||||||
$"LehrerApp.McpBridge: mit LehrerApp verbunden, {tools.Count} Werkzeug(e) übernommen.");
|
$"LehrerApp.McpBridge: mit LehrerApp verbunden, {tools.Count} Werkzeug(e) übernommen.");
|
||||||
return true;
|
return true;
|
||||||
@@ -111,6 +124,7 @@ internal sealed class RealServerBridge : IAsyncDisposable
|
|||||||
await pipe.DisposeAsync();
|
await pipe.DisposeAsync();
|
||||||
var (_, _, stderrLine) = ConnectionDiagnostics.Describe(ex);
|
var (_, _, stderrLine) = ConnectionDiagnostics.Describe(ex);
|
||||||
await Console.Error.WriteLineAsync(stderrLine);
|
await Console.Error.WriteLineAsync(stderrLine);
|
||||||
|
_lastFailedConnectAttemptUtc = DateTime.UtcNow;
|
||||||
EnsureFallbackTool();
|
EnsureFallbackTool();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,12 @@ internal sealed class StatusMcpServerTool(RealServerBridge bridge) : McpServerTo
|
|||||||
"(Schüler, Noten, Stundenplanung, Kompetenzen, ...) nicht verfügbar - sie erscheinen erst " +
|
"(Schüler, Noten, Stundenplanung, Kompetenzen, ...) nicht verfügbar - sie erscheinen erst " +
|
||||||
"nach einer erfolgreichen Verbindung in der Werkzeugliste. Bei einer Fehlermeldung eines " +
|
"nach einer erfolgreichen Verbindung in der Werkzeugliste. Bei einer Fehlermeldung eines " +
|
||||||
"anderen LehrerApp-Werkzeugs, die auf eine fehlende Verbindung hindeutet, dieses Werkzeug " +
|
"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,
|
InputSchema = JsonDocument.Parse("""{"type":"object","properties":{}}""").RootElement,
|
||||||
};
|
};
|
||||||
public override IReadOnlyList<object> Metadata { get; } = [];
|
public override IReadOnlyList<object> Metadata { get; } = [];
|
||||||
@@ -44,8 +49,9 @@ internal sealed class StatusMcpServerTool(RealServerBridge bridge) : McpServerTo
|
|||||||
"sind jetzt verfügbar."
|
"sind jetzt verfügbar."
|
||||||
: "LehrerApp läuft nicht oder der MCP-Server ist in den Einstellungen nicht " +
|
: "LehrerApp läuft nicht oder der MCP-Server ist in den Einstellungen nicht " +
|
||||||
"aktiviert. Bitte den Nutzer bitten, LehrerApp zu starten und den " +
|
"aktiviert. Bitte den Nutzer bitten, LehrerApp zu starten und den " +
|
||||||
"MCP-Server in den Einstellungen zu aktivieren - dieses Werkzeug danach " +
|
"MCP-Server in den Einstellungen zu aktivieren. Dieses Werkzeug NICHT im " +
|
||||||
"erneut aufrufen, um zu prüfen, ob die Verbindung jetzt klappt.",
|
"selben Zug erneut aufrufen - die Bridge verbindet im Hintergrund " +
|
||||||
|
"automatisch neu, sobald LehrerApp läuft, und meldet das von selbst.",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user