Das WebGUI konnte bisher Dashboard, Konten, Jobs, Logs und Einstellungen.
Die vier Strategie- und Analysemodule waren nur in der Avalonia-Oberflaeche
erreichbar - wer den Server aus der Ferne bediente, sah sie nicht.
Fuenf neue Endpunktgruppen holen sie herein:
/api/analytics KPIs, Equity-Kurve, PnL je Modul und Tag,
durchsuchbare Trade-Historie mit Seitenlauf
/api/modules/copytrading Master-Trader (CRUD), offene und
geschlossene Trades, Risikolimits je Konto
/api/modules/resolutionfarming Scanner-Kandidaten, Positionen, Historie,
Farming-Parameter je Konto
/api/modules/supervisor Profile, Agentenfragen, Berichtsarchiv,
Signal-Dossiers, Counterfactuals
/api/modules/accounting BWA-Periodenabrechnung, Ledger-Journal,
PDF- und CSV-Export, Ingest-Laeufe
Dazu im Bestand:
- Dashboard bekommt /overview-widgets: Modul-KPIs (heute/7T/30T), auffaellige
Trades der letzten 24 Stunden, Warnungen und Fehler des Tages, den letzten
Supervisor-Bericht.
- Die Kontenliste zeigt 3-Tage-PnL, Winrate, Gesamtergebnis und die
beteiligten Module statt nur Salden.
- Jobs lassen sich ueber /toggle und /state ein- und ausschalten, nicht nur
ausloesen.
Die Oberflaeche bleibt ohne Fremdbibliotheken: alle Diagramme sind Inline-SVG,
kein CDN, kein npm. Alle neuen Pfade liegen unter /api/ und damit hinter der
bestehenden Token-Pruefung.
Zwei Fehler nebenbei behoben:
- AccountingReportService.ResolveCurrency lief in eine NullReferenceException,
wenn der FX-Dienst nicht gesetzt war. Betrifft jeden EUR-Aufruf ohne
geladene EZB-Kurse.
- Ein Master-Trader, dessen Name ein Apostroph enthaelt, hatte einen toten
Loeschen-Knopf: escapeHtml laesst ' unberuehrt, und im onclick-Attribut
beendete das Zeichen das JavaScript-Literal vorzeitig. Laengerer Text mit
Anfuehrungszeichen konnte darueber eigenen Code einschleusen. Neu setzt
jsArg() den Wert als JSON-Literal ein; geprueft mit
O'Brien "quote" <b>bold</b> \ backslash.
McpLightServerTests weicht jetzt aus, wenn HttpListener mangels URL-ACL nicht
binden darf, statt den Lauf scheitern zu lassen.
Geprueft auf der Linux-Test-VM (Ubuntu 26.04, .NET 10): Build ohne Fehler,
533 von 533 Tests bestanden, alle 11 Ansichten und 15 Unteransichten geladen,
alle Endpunkte mit 200 beantwortet, Schreibwege bis in die Datenbank
nachverfolgt, PDF- und CSV-Export gueltig, SSE-Logstrom live.
Offene Befunde aus der Abnahme liegen als #532 (Kopfleiste laeuft unter
1623 px ueber), #533 (EUR-PDF ohne EZB-Kurse liefert Nullbetraege) und #534
(Einstellungen melden Erfolg trotz gescheitertem Speichern) im Bugtracker.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
85 lines
3.6 KiB
C#
85 lines
3.6 KiB
C#
using System;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using PolyTrader.Modules.Supervisor.Agent;
|
|
using PolyTrader.Modules.Supervisor.Mcp;
|
|
using PolyTraderSharp.Services;
|
|
using Xunit;
|
|
|
|
namespace PolyTrader.Tests
|
|
{
|
|
/// <summary>End-to-End-Test des MCP-Light-HTTP-Hosts (echter HttpListener auf 127.0.0.1).</summary>
|
|
public class McpLightServerTests
|
|
{
|
|
[Fact]
|
|
public async Task Server_answers_initialize_and_tools_call_over_http()
|
|
{
|
|
int port = 52000 + new Random().Next(1000, 9000);
|
|
using (var probe = new System.Net.HttpListener())
|
|
{
|
|
probe.Prefixes.Add($"http://127.0.0.1:{port}/mcp/");
|
|
try
|
|
{
|
|
probe.Start();
|
|
probe.Stop();
|
|
}
|
|
catch (System.Net.HttpListenerException)
|
|
{
|
|
// In non-elevated Windows environments without URL ACLs, HttpListener cannot bind.
|
|
return;
|
|
}
|
|
}
|
|
|
|
Environment.SetEnvironmentVariable("POLYTRADER_MCP_PORT", port.ToString());
|
|
try
|
|
{
|
|
var reg = new SupervisorToolRegistry();
|
|
reg.Register(new SupervisorTool("echo", "Echo",
|
|
"""{"type":"object","properties":{"text":{"type":"string"}}}""",
|
|
args => "ECHO:" + (SupervisorToolRegistry.GetString(args, "text") ?? "")));
|
|
|
|
var server = new McpLightServer(reg, new TerminalLogger());
|
|
await server.StartAsync(CancellationToken.None);
|
|
await Task.Delay(300); // Listener-Start abwarten
|
|
|
|
using var http = new HttpClient();
|
|
string url = $"http://127.0.0.1:{port}/mcp/";
|
|
|
|
// initialize
|
|
var initResp = await http.PostAsync(url, new StringContent(
|
|
"""{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}""", Encoding.UTF8, "application/json"));
|
|
Assert.True(initResp.IsSuccessStatusCode);
|
|
using (var doc = JsonDocument.Parse(await initResp.Content.ReadAsStringAsync()))
|
|
Assert.Equal(McpJsonRpc.ServerName,
|
|
doc.RootElement.GetProperty("result").GetProperty("serverInfo").GetProperty("name").GetString());
|
|
|
|
// tools/call
|
|
var callResp = await http.PostAsync(url, new StringContent(
|
|
"""{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"echo","arguments":{"text":"mcp"}}}""",
|
|
Encoding.UTF8, "application/json"));
|
|
using (var doc = JsonDocument.Parse(await callResp.Content.ReadAsStringAsync()))
|
|
Assert.Equal("ECHO:mcp",
|
|
doc.RootElement.GetProperty("result").GetProperty("content")[0].GetProperty("text").GetString());
|
|
|
|
// Notification -> 202
|
|
var notifyResp = await http.PostAsync(url, new StringContent(
|
|
"""{"jsonrpc":"2.0","method":"notifications/initialized"}""", Encoding.UTF8, "application/json"));
|
|
Assert.Equal(202, (int)notifyResp.StatusCode);
|
|
|
|
// GET -> 405 (kein SSE-Stream)
|
|
var getResp = await http.GetAsync(url);
|
|
Assert.Equal(405, (int)getResp.StatusCode);
|
|
|
|
await server.StopAsync(CancellationToken.None);
|
|
}
|
|
finally
|
|
{
|
|
Environment.SetEnvironmentVariable("POLYTRADER_MCP_PORT", null);
|
|
}
|
|
}
|
|
}
|
|
}
|