feat: LehrerApp.McpBridge zu resilientem MCP-Proxy umgebaut
Claude Desktop deaktivierte den ganzen MCP-Server, sobald der Initialize-Handshake beim Bridge-Start fehlschlug - der Regelfall, wenn LehrerApp.Desktop (z.B. nach Windows-Autostart von Claude Desktop) noch nicht läuft. Die Bridge ist jetzt selbst ein MCP-Server gegenüber Claude Desktop und gleichzeitig ein MCP-Client gegenüber dem echten Server in LehrerApp.Desktop: der Handshake gelingt dadurch immer. Verbunden spiegelt sie die echte Werkzeugliste 1:1; ohne Verbindung bietet sie nur ein lehrerapp_status-Werkzeug an, das den Grund erklärt und erneut verbindet. Ein Hintergrund-Loop versucht unabhängig davon alle 5s zu reconnecten und schaltet per notifications/tools/list_changed automatisch auf die echten Werkzeuge um, sobald LehrerApp erreichbar ist. Bricht die Verbindung während eines laufenden Aufrufs ab, kommt ein normales Tool-Fehlerergebnis statt eines Prozessabsturzes zurück. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
using LehrerApp.Core.Mcp;
|
||||
|
||||
namespace LehrerApp.McpBridge;
|
||||
|
||||
/// <summary>
|
||||
/// Unterscheidet an jeder Stelle, an der die Bridge versucht, sich mit LehrerApp.Desktop zu
|
||||
/// verbinden, zwischen dem harmlosen Regelfall "LehrerApp läuft gerade nicht" und einem echten
|
||||
/// Verbindungsfehler. <see cref="NamedPipeClientStream.ConnectAsync"/> wirft bei einer lokalen
|
||||
/// Named Pipe (kein Netzwerk, kein Handshake) genau dann eine <see cref="TimeoutException"/>, wenn
|
||||
/// innerhalb der Frist kein Prozess auf den Pipe-Namen lauschte - praktisch immer, weil
|
||||
/// LehrerApp.Desktop nicht läuft oder der MCP-Server dort in den Einstellungen nicht aktiviert ist.
|
||||
/// Jede andere Exception (z.B. <see cref="UnauthorizedAccessException"/> bei einem Rechte-/
|
||||
/// ACL-Mismatch zwischen den Prozessen) deutet auf ein echtes Konfigurationsproblem hin.
|
||||
/// </summary>
|
||||
internal static class ConnectionDiagnostics
|
||||
{
|
||||
public const int NotRunningErrorCode = -32001;
|
||||
public const int UnexpectedErrorCode = -32002;
|
||||
|
||||
public static (int Code, string ClientMessage, string StderrLine) Describe(Exception ex) => ex switch
|
||||
{
|
||||
TimeoutException => (
|
||||
NotRunningErrorCode,
|
||||
"LehrerApp läuft nicht oder der MCP-Server ist in den Einstellungen nicht aktiviert.",
|
||||
"LehrerApp.McpBridge: Kein Kontakt zu LehrerApp (Pipe „" + McpPipeConstants.PipeName +
|
||||
"“ nicht erreichbar). Das ist normal, wenn LehrerApp.Desktop gerade nicht läuft oder der " +
|
||||
"MCP-Server dort in den Einstellungen nicht aktiviert ist - kein Hinweis auf eine " +
|
||||
"fehlerhafte Bridge-/Client-Konfiguration."),
|
||||
_ => (
|
||||
UnexpectedErrorCode,
|
||||
$"Unerwarteter Verbindungsfehler ({ex.GetType().Name}): {ex.Message}",
|
||||
$"LehrerApp.McpBridge: Unerwarteter Fehler beim Verbindungsaufbau zu LehrerApp " +
|
||||
$"({ex.GetType().Name}: {ex.Message}). Das ist vermutlich KEIN einfaches \"LehrerApp " +
|
||||
"läuft noch nicht\", sondern deutet auf ein Konfigurations- oder Rechteproblem hin."),
|
||||
};
|
||||
}
|
||||
@@ -5,5 +5,6 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LehrerApp.Core\LehrerApp.Core.csproj" />
|
||||
<PackageReference Include="ModelContextProtocol.Core" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -1,84 +1,45 @@
|
||||
using System.IO.Pipes;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using LehrerApp.Core.Mcp;
|
||||
using LehrerApp.McpBridge;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
// Zustandsloser Bridge-Prozess: reicht die vom KI-Client über stdin/stdout gesprochene
|
||||
// JSON-RPC-Verbindung unverändert an die Named Pipe des laufenden LehrerApp-Hauptprozesses durch.
|
||||
// Kein eigenes JSON-RPC-Verständnis nötig, außer im Fehlerfall (siehe unten) — stdout ist
|
||||
// ausschließlich für den durchgereichten Protokollstrom reserviert, jede Diagnose geht nach stderr.
|
||||
// Resilienter Bridge-Prozess (Nutzer-Feedback): meldet sich gegenüber dem KI-Client (z.B. Claude
|
||||
// Desktop) IMMER erfolgreich als MCP-Server, unabhängig davon, ob LehrerApp.Desktop beim Start
|
||||
// dieses Prozesses bereits läuft - Claude Desktop startet oft per Windows-Autostart, bevor der
|
||||
// Nutzer LehrerApp geöffnet hat. Die eigentliche Weiterleitung an den echten Server sowie das
|
||||
// Nachladen der echten Werkzeugliste, sobald LehrerApp erreichbar ist, übernimmt
|
||||
// RealServerBridge - Details und Begründung siehe dort.
|
||||
|
||||
const int ConnectTimeoutMs = 3000;
|
||||
var bridge = new RealServerBridge();
|
||||
await bridge.TryReconnectAsync(CancellationToken.None);
|
||||
bridge.EnsureFallbackTool();
|
||||
|
||||
await using var pipe = new NamedPipeClientStream(
|
||||
".", McpPipeConstants.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
|
||||
using var cts = new CancellationTokenSource();
|
||||
var reconnectLoop = bridge.RunBackgroundReconnectLoopAsync(TimeSpan.FromSeconds(5), cts.Token);
|
||||
|
||||
try
|
||||
var serverOptions = new McpServerOptions
|
||||
{
|
||||
await pipe.ConnectAsync(ConnectTimeoutMs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Console.Error.WriteLineAsync(
|
||||
$"LehrerApp.McpBridge: Verbindung zu LehrerApp fehlgeschlagen ({ex.Message}). " +
|
||||
"Läuft die App und ist der MCP-Server in den Einstellungen aktiviert?");
|
||||
await RespondWithConnectionErrorAsync();
|
||||
Environment.Exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
await Console.Error.WriteLineAsync("LehrerApp.McpBridge: verbunden.");
|
||||
ServerInfo = new Implementation { Name = "LehrerApp", Version = "1.0.0" },
|
||||
// ListChanged = true: Voraussetzung dafür, dass Claude Desktop reagiert, wenn RealServerBridge
|
||||
// das Status-Werkzeug nachträglich durch die echte Werkzeugliste ersetzt (oder umgekehrt, falls
|
||||
// LehrerApp während der Sitzung beendet wird).
|
||||
Capabilities = new ServerCapabilities { Tools = new ToolsCapability { ListChanged = true } },
|
||||
ToolCollection = bridge.Tools,
|
||||
};
|
||||
|
||||
await using var stdin = Console.OpenStandardInput();
|
||||
await using var stdout = Console.OpenStandardOutput();
|
||||
await using var transport = new StreamServerTransport(stdin, stdout, "LehrerApp.McpBridge");
|
||||
await using var server = McpServer.Create(transport, serverOptions, loggerFactory: null, serviceProvider: null);
|
||||
|
||||
var toApp = stdin.CopyToAsync(pipe);
|
||||
var toClient = pipe.CopyToAsync(stdout);
|
||||
|
||||
// Sobald eine Richtung endet (App beendet die Verbindung, oder der KI-Client schließt stdin),
|
||||
// ist die Session vorbei — die andere Kopie hängt sonst an einem offenen Handle.
|
||||
await Task.WhenAny(toApp, toClient);
|
||||
|
||||
// Der Verbindungsaufbau ist fehlgeschlagen, bevor irgendetwas an die App durchgereicht wurde. Der
|
||||
// MCP-Client wartet zu diesem Zeitpunkt bereits auf eine Antwort auf seine erste Anfrage
|
||||
// ("initialize") — eine korrekte JSON-RPC-Fehlerantwort braucht deren "id", also wird diese eine
|
||||
// Zeile noch selbst gelesen und beantwortet, statt den Prozess kommentarlos zu beenden.
|
||||
static async Task RespondWithConnectionErrorAsync()
|
||||
try
|
||||
{
|
||||
string? line;
|
||||
try
|
||||
{
|
||||
line = await Console.In.ReadLineAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(line)) return;
|
||||
|
||||
JsonNode? requestId = null;
|
||||
try
|
||||
{
|
||||
requestId = JsonNode.Parse(line)?["id"];
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
// Keine gültige JSON-RPC-Nachricht - ohne "id" ist keine korrekte Fehlerantwort möglich,
|
||||
// der Prozess beendet sich dann einfach mit einem Fehler-Exitcode.
|
||||
return;
|
||||
}
|
||||
if (requestId is null) return;
|
||||
|
||||
var response = new JsonObject
|
||||
{
|
||||
["jsonrpc"] = "2.0",
|
||||
["id"] = requestId.DeepClone(),
|
||||
["error"] = new JsonObject
|
||||
{
|
||||
["code"] = -32001,
|
||||
["message"] = "LehrerApp läuft nicht oder der MCP-Server ist in den Einstellungen nicht aktiviert.",
|
||||
},
|
||||
};
|
||||
await Console.Out.WriteLineAsync(response.ToJsonString());
|
||||
await Console.Out.FlushAsync();
|
||||
// Läuft, bis der KI-Client stdin schließt (Sitzungsende) - danach ist nichts mehr zu tun.
|
||||
await server.RunAsync(cts.Token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await cts.CancelAsync();
|
||||
try { await reconnectLoop; }
|
||||
catch { /* Beenden über Cancellation ist der Normalfall hier */ }
|
||||
await bridge.DisposeAsync();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace LehrerApp.McpBridge;
|
||||
|
||||
/// <summary>
|
||||
/// Reicht einen von LehrerApp.Desktop tatsächlich angebotenen MCP-Tool-Aufruf 1:1 an den echten
|
||||
/// Server weiter - Name, Beschreibung und Eingabeschema (<see cref="ProtocolTool"/>) stammen
|
||||
/// unverändert von dort (siehe <see cref="RealServerBridge"/>). Die Bridge muss dadurch kein
|
||||
/// eigenes Wissen über einzelne LehrerApp-Werkzeuge/-Parameter pflegen; ändert sich ein Tool in
|
||||
/// LehrerApp.Desktop, übernimmt die Bridge das beim nächsten Verbindungsaufbau automatisch.
|
||||
/// </summary>
|
||||
internal sealed class ProxyMcpServerTool(Tool protocolTool, RealServerBridge bridge) : McpServerTool
|
||||
{
|
||||
public override Tool ProtocolTool { get; } = protocolTool;
|
||||
public override IReadOnlyList<object> Metadata { get; } = [];
|
||||
|
||||
public override ValueTask<CallToolResult> InvokeAsync(
|
||||
RequestContext<CallToolRequestParams> request, CancellationToken cancellationToken) =>
|
||||
new(bridge.CallRealToolAsync(request.Params, cancellationToken));
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
using System.IO.Pipes;
|
||||
using LehrerApp.Core.Mcp;
|
||||
using ModelContextProtocol.Client;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace LehrerApp.McpBridge;
|
||||
|
||||
/// <summary>
|
||||
/// Hält die eigentliche Verbindung zu LehrerApp.Desktop als echter MCP-Client über die Named Pipe
|
||||
/// (dasselbe ModelContextProtocol-SDK, das der Server in LehrerApp.Desktop nutzt, siehe
|
||||
/// McpServerHostedService) und spiegelt dessen aktuelle Werkzeugliste 1:1 in <see cref="Tools"/> -
|
||||
/// der Werkzeugsammlung, die der Bridge-eigene MCP-Server gegenüber Claude Desktop anbietet.
|
||||
///
|
||||
/// Kernidee (Nutzer-Feedback): ein MCP-Client wie Claude Desktop führt den Initialize-Handshake nur
|
||||
/// einmal beim Start des Bridge-Prozesses durch und markiert den ganzen Server als fehlgeschlagen
|
||||
/// (und deaktiviert ihn für die Sitzung), wenn dieser Handshake nicht klappt - typischerweise, weil
|
||||
/// LehrerApp.Desktop beim Windows-Systemstart noch nicht läuft (Claude Desktop startet oft per
|
||||
/// Autostart, LehrerApp nicht). Diese Klasse sorgt dafür, dass der Handshake IMMER klappt: ist
|
||||
/// LehrerApp nicht erreichbar, bietet die Bridge statt der echten Werkzeuge nur ein einzelnes
|
||||
/// Status-Werkzeug (<see cref="StatusMcpServerTool"/>) an, das auf Wunsch erneut verbindet und den
|
||||
/// aktuellen Stand erklärt - der Assistent kann dem Nutzer dadurch mitteilen, dass LehrerApp
|
||||
/// gestartet werden muss, statt dass der ganze MCP-Server kommentarlos verschwindet. Ein
|
||||
/// Hintergrund-Loop (<see cref="RunBackgroundReconnectLoopAsync"/>) versucht unabhängig davon
|
||||
/// regelmäßig, die echte Verbindung wiederherzustellen - sobald das klappt, ersetzt <see cref="Tools"/>
|
||||
/// das Status-Werkzeug durch die echte, tagesaktuelle Werkzeugliste, und das SDK benachrichtigt
|
||||
/// Claude Desktop automatisch per "notifications/tools/list_changed"
|
||||
/// (<see cref="McpServerPrimitiveCollection{T}.DeferChangedEvents"/>).
|
||||
/// </summary>
|
||||
internal sealed class RealServerBridge : IAsyncDisposable
|
||||
{
|
||||
private const int ConnectTimeoutMs = 3000;
|
||||
|
||||
public McpServerPrimitiveCollection<McpServerTool> Tools { get; } = new();
|
||||
|
||||
private readonly SemaphoreSlim _gate = new(1, 1);
|
||||
private McpClient? _client;
|
||||
private NamedPipeClientStream? _pipe;
|
||||
|
||||
/// <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
|
||||
/// jedem Aufruf zu prüfen - ein <see cref="ProxyMcpServerTool"/> existiert per Konstruktion nur,
|
||||
/// solange <see cref="_client"/> zuletzt verbunden war, ein zusätzlicher Verbindungsversuch davor
|
||||
/// wäre unnötige Latenz im Erfolgsfall (dem weit überwiegenden Normalfall).</summary>
|
||||
public async Task<CallToolResult> CallRealToolAsync(CallToolRequestParams requestParams, CancellationToken ct)
|
||||
{
|
||||
var client = _client;
|
||||
if (client is null)
|
||||
{
|
||||
EnsureFallbackTool();
|
||||
return ErrorResult("LehrerApp läuft nicht oder der MCP-Server ist in den Einstellungen nicht aktiviert.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return await client.CallToolAsync(requestParams, ct);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
var (_, message, stderrLine) = ConnectionDiagnostics.Describe(ex);
|
||||
await Console.Error.WriteLineAsync(stderrLine);
|
||||
await DisconnectAsync();
|
||||
return ErrorResult("Verbindung zu LehrerApp während des Aufrufs verloren. " + message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Versucht, sofern noch nicht verbunden, jetzt sofort eine Verbindung aufzubauen -
|
||||
/// aufgerufen sowohl beim Start der Bridge als auch aus <see cref="StatusMcpServerTool"/>
|
||||
/// (expliziter Nutzer-/Assistenten-Wunsch, jetzt nachzusehen) und aus dem Hintergrund-Loop.</summary>
|
||||
public async Task<bool> TryReconnectAsync(CancellationToken ct)
|
||||
{
|
||||
if (_client is not null) return true;
|
||||
await _gate.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
if (_client is not null) return true; // ein anderer Aufrufer war währenddessen schneller
|
||||
return await ConnectCoreAsync(ct);
|
||||
}
|
||||
finally { _gate.Release(); }
|
||||
}
|
||||
|
||||
private async Task<bool> ConnectCoreAsync(CancellationToken ct)
|
||||
{
|
||||
var pipe = new NamedPipeClientStream(
|
||||
".", McpPipeConstants.PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
|
||||
try
|
||||
{
|
||||
await pipe.ConnectAsync(ConnectTimeoutMs, ct);
|
||||
var transport = new StreamClientTransport(pipe, pipe, loggerFactory: null);
|
||||
var client = await McpClient.CreateAsync(
|
||||
transport,
|
||||
new McpClientOptions { ClientInfo = new Implementation { Name = "LehrerApp.McpBridge", Version = "1.0.0" } },
|
||||
loggerFactory: null, ct);
|
||||
var tools = await client.ListToolsAsync(cancellationToken: ct);
|
||||
|
||||
using (Tools.DeferChangedEvents())
|
||||
{
|
||||
Tools.Clear();
|
||||
foreach (var tool in tools)
|
||||
Tools.Add(new ProxyMcpServerTool(tool.ProtocolTool, this));
|
||||
}
|
||||
|
||||
_client = client;
|
||||
_pipe = pipe;
|
||||
await Console.Error.WriteLineAsync(
|
||||
$"LehrerApp.McpBridge: mit LehrerApp verbunden, {tools.Count} Werkzeug(e) übernommen.");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
{
|
||||
await pipe.DisposeAsync();
|
||||
var (_, _, stderrLine) = ConnectionDiagnostics.Describe(ex);
|
||||
await Console.Error.WriteLineAsync(stderrLine);
|
||||
EnsureFallbackTool();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Stellt sicher, dass mindestens das Status-Werkzeug angeboten wird - ohne
|
||||
/// <see cref="Tools"/> anzufassen, wenn bereits etwas darin steht (egal ob Status- oder echte
|
||||
/// Werkzeuge), um ein versehentliches Überschreiben bei überlappenden Aufrufen zu vermeiden.</summary>
|
||||
public void EnsureFallbackTool()
|
||||
{
|
||||
if (Tools.Count > 0) return;
|
||||
Tools.Add(new StatusMcpServerTool(this));
|
||||
}
|
||||
|
||||
private async Task DisconnectAsync()
|
||||
{
|
||||
await _gate.WaitAsync();
|
||||
try
|
||||
{
|
||||
_client = null;
|
||||
if (_pipe is not null)
|
||||
{
|
||||
await _pipe.DisposeAsync();
|
||||
_pipe = null;
|
||||
}
|
||||
using (Tools.DeferChangedEvents())
|
||||
{
|
||||
Tools.Clear();
|
||||
Tools.Add(new StatusMcpServerTool(this));
|
||||
}
|
||||
}
|
||||
finally { _gate.Release(); }
|
||||
}
|
||||
|
||||
/// <summary>Läuft für die gesamte Prozesslaufzeit mit, unabhängig davon, ob der KI-Client
|
||||
/// jemals von sich aus "lehrerapp_status" aufruft - sonst würden die echten Werkzeuge erst
|
||||
/// wieder auftauchen, nachdem jemand aktiv danach gefragt hat.</summary>
|
||||
public async Task RunBackgroundReconnectLoopAsync(TimeSpan interval, CancellationToken ct)
|
||||
{
|
||||
while (!ct.IsCancellationRequested)
|
||||
{
|
||||
try { await Task.Delay(interval, ct); }
|
||||
catch (OperationCanceledException) { return; }
|
||||
|
||||
if (_client is null)
|
||||
{
|
||||
try { await TryReconnectAsync(ct); }
|
||||
catch (OperationCanceledException) { return; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static CallToolResult ErrorResult(string message) => new()
|
||||
{
|
||||
IsError = true,
|
||||
Content = [new TextContentBlock { Text = message }],
|
||||
};
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (_pipe is not null) await _pipe.DisposeAsync();
|
||||
_gate.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Text.Json;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace LehrerApp.McpBridge;
|
||||
|
||||
/// <summary>
|
||||
/// Einziges Werkzeug, das die Bridge anbietet, solange keine Verbindung zu LehrerApp besteht (siehe
|
||||
/// <see cref="RealServerBridge"/>). Ohne dieses Werkzeug hätte ein KI-Client keine Möglichkeit zu
|
||||
/// erfahren, WARUM gerade keine LehrerApp-Werkzeuge da sind, sondern sähe nur eine leere
|
||||
/// Werkzeugliste - der Assistent könnte dem Nutzer dann nicht erklären, dass LehrerApp erst
|
||||
/// gestartet werden muss. Versucht bei jedem Aufruf aktiv erneut zu verbinden; klappt das, ersetzt
|
||||
/// <see cref="RealServerBridge"/> dieses Werkzeug automatisch durch die echte Werkzeugliste, und der
|
||||
/// Client wird per "notifications/tools/list_changed" benachrichtigt (siehe
|
||||
/// <see cref="McpServerPrimitiveCollection{T}"/>).
|
||||
/// </summary>
|
||||
internal sealed class StatusMcpServerTool(RealServerBridge bridge) : McpServerTool
|
||||
{
|
||||
public override Tool ProtocolTool { get; } = new()
|
||||
{
|
||||
Name = "lehrerapp_status",
|
||||
Description = "Prüft, ob LehrerApp erreichbar ist, und versucht bei Bedarf erneut zu " +
|
||||
"verbinden. Solange keine Verbindung besteht, sind alle anderen LehrerApp-Werkzeuge " +
|
||||
"(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.",
|
||||
InputSchema = JsonDocument.Parse("""{"type":"object","properties":{}}""").RootElement,
|
||||
};
|
||||
public override IReadOnlyList<object> Metadata { get; } = [];
|
||||
|
||||
public override async ValueTask<CallToolResult> InvokeAsync(
|
||||
RequestContext<CallToolRequestParams> request, CancellationToken cancellationToken)
|
||||
{
|
||||
var connected = await bridge.TryReconnectAsync(cancellationToken);
|
||||
return new CallToolResult
|
||||
{
|
||||
Content =
|
||||
[
|
||||
new TextContentBlock
|
||||
{
|
||||
Text = connected
|
||||
? "Verbindung zu LehrerApp hergestellt. Die eigentlichen LehrerApp-Werkzeuge " +
|
||||
"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.",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user