diff --git a/src/Spice86.Core/Emulator/InterruptHandlers/VGA/Data/RegisterValueSet.cs b/src/Spice86.Core/Emulator/InterruptHandlers/VGA/Data/RegisterValueSet.cs
index 1294604ba0..4070054477 100644
--- a/src/Spice86.Core/Emulator/InterruptHandlers/VGA/Data/RegisterValueSet.cs
+++ b/src/Spice86.Core/Emulator/InterruptHandlers/VGA/Data/RegisterValueSet.cs
@@ -54,6 +54,7 @@ internal readonly struct RegisterValueSet {
[0x11] = new VideoMode(new VgaMode(MemoryModel.Planar, 640, 480, 1, 8, 16, VgaConstants.GraphicsSegment), 0xFF, Palettes.Ega, SequencerRegisterValueSet6, 0xe3, CrtControllerRegisterValueSet9, AttributeControllerRegisterValueSet8, GraphicsControllerRegisterValueSet5),
[0x12] = new VideoMode(new VgaMode(MemoryModel.Planar, 640, 480, 4, 8, 16, VgaConstants.GraphicsSegment), 0xFF, Palettes.Ega, SequencerRegisterValueSet6, 0xe3, CrtControllerRegisterValueSet9, AttributeControllerRegisterValueSet7, GraphicsControllerRegisterValueSet5),
[0x13] = new VideoMode(new VgaMode(MemoryModel.Packed, 320, 200, 8, 8, 8, VgaConstants.GraphicsSegment), 0xFF, Palettes.Vga, SequencerRegisterValueSet7, 0x63, CrtControllerRegisterValueSet10, AttributeControllerRegisterValueSet9, GraphicsControllerRegisterValueSet6),
+ [0x69] = new VideoMode(new VgaMode(MemoryModel.Planar, 640, 480, 4, 8, 16, VgaConstants.GraphicsSegment), 0xFF, Palettes.Ega, SequencerRegisterValueSet6, 0xe3, CrtControllerRegisterValueSet11, AttributeControllerRegisterValueSet7, GraphicsControllerRegisterValueSet5),
[0x6A] = new VideoMode(new VgaMode(MemoryModel.Planar, 800, 600, 4, 8, 16, VgaConstants.GraphicsSegment), 0xFF, Palettes.Ega, SequencerRegisterValueSet6, 0xe3, CrtControllerRegisterValueSet11, AttributeControllerRegisterValueSet7, GraphicsControllerRegisterValueSet5)
};
}
\ No newline at end of file
diff --git a/src/Spice86.Core/Emulator/Mcp/McpHttpHost.cs b/src/Spice86.Core/Emulator/Mcp/McpHttpHost.cs
index 2e4ec78385..4a5c5352a2 100644
--- a/src/Spice86.Core/Emulator/Mcp/McpHttpHost.cs
+++ b/src/Spice86.Core/Emulator/Mcp/McpHttpHost.cs
@@ -56,10 +56,11 @@ public void Start(EmulatorMcpServices services, int port = 8081,
};
})
.WithHttpTransport(options => {
- // Stateless mode: the server never issues Mcp-Session-Id headers.
- // Stateful sessions cause 404 when AI clients reuse a session ID
- // from a fresh TCP connection or skip the notifications/initialized
- // handshake step, which is a common real-world pattern.
+ // Stateless mode keeps things simple — no session management,
+ // no Mcp-Session-Id headers, compatible with AI clients.
+ // The SDK does not map GET /mcp in stateless mode (POST only),
+ // but we add our own GET handler below to satisfy clients
+ // (like opencode remote MCP) that probe with GET first.
options.Stateless = true;
})
.WithToolsFromAssembly(typeof(EmulatorMcpTools).Assembly);
@@ -70,10 +71,7 @@ public void Start(EmulatorMcpServices services, int port = 8081,
}
}
- builder.WebHost.ConfigureKestrel(kestrel => {
- kestrel.ListenLocalhost(port);
- });
-
+ builder.WebHost.UseUrls($"http://localhost:{port}");
_app = builder.Build();
_app.MapGet("/health", () => Results.Json(new {
status = "ok",
@@ -81,6 +79,19 @@ public void Start(EmulatorMcpServices services, int port = 8081,
}));
_app.MapMcp("/mcp");
+ // The SDK doesn't map GET /mcp in stateless mode, but some MCP clients
+ // (including opencode remote MCP) probe with GET first. Return a minimal
+ // SSE endpoint event telling the client to POST to the same URL.
+ _app.MapGet("/mcp", async (HttpContext context) =>
+ {
+ context.Response.Headers.ContentType = "text/event-stream";
+ context.Response.Headers.CacheControl = "no-cache,no-store";
+ context.Response.Headers["X-Accel-Buffering"] = "no";
+ await context.Response.WriteAsync(
+ $"event: endpoint\ndata: /mcp/\n\n");
+ await context.Response.Body.FlushAsync();
+ });
+
_serverThread = new Thread(RunServerLoop) {
Name = "McpHttpHost",
IsBackground = true
@@ -95,12 +106,20 @@ private void RunServerLoop() {
}
try {
- _app.Run();
+ _app.StartAsync().GetAwaiter().GetResult();
+ _loggerService.Information("MCP HTTP server is now listening");
+ // Block this thread indefinitely so the server keeps running.
+ // Using _app.Run() instead caused premature shutdown on background
+ // threads because ConsoleLifetime has no console to watch on a
+ // non-main thread.
} catch (ObjectDisposedException) {
// Host disposed while thread was exiting.
- } catch (InvalidOperationException ex) {
- _loggerService.Error(ex, "MCP HTTP server stopped unexpectedly");
+ } catch (Exception ex) {
+ _loggerService.Error(ex, "MCP HTTP server crashed");
}
+
+ // Wait forever so the thread never exits.
+ new System.Threading.ManualResetEvent(false).WaitOne();
}
public void Dispose() {
diff --git a/src/Spice86.Core/Emulator/OperatingSystem/DosDriveManager.cs b/src/Spice86.Core/Emulator/OperatingSystem/DosDriveManager.cs
index c387e723dd..13a0ffb9f9 100644
--- a/src/Spice86.Core/Emulator/OperatingSystem/DosDriveManager.cs
+++ b/src/Spice86.Core/Emulator/OperatingSystem/DosDriveManager.cs
@@ -48,8 +48,8 @@ public DosDriveManager(ILoggerService loggerService, string? cDriveFolderPath, s
cDriveFolderPath = DosPathResolver.GetExeParentFolder(executablePath);
}
cDriveFolderPath = ConvertUtils.ToSlashFolderPath(cDriveFolderPath);
- _driveMap[GetDriveIndex('A')] = new VirtualDrive() { DriveLetter = 'A', MountedHostDirectory = "" };
- _driveMap[GetDriveIndex('B')] = new VirtualDrive() { DriveLetter = 'B', MountedHostDirectory = "" };
+ _driveMap[GetDriveIndex('A')] = new VirtualDrive() { DriveLetter = 'A', MountedHostDirectory = cDriveFolderPath };
+ _driveMap[GetDriveIndex('B')] = new VirtualDrive() { DriveLetter = 'B', MountedHostDirectory = cDriveFolderPath };
var cDrive = new VirtualDrive { DriveLetter = 'C', MountedHostDirectory = cDriveFolderPath };
_driveMap[GetDriveIndex('C')] = cDrive;
CurrentDrive = cDrive;
diff --git a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosDriveBase.cs b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosDriveBase.cs
index cdd170f7c8..d1540080d8 100644
--- a/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosDriveBase.cs
+++ b/src/Spice86.Core/Emulator/OperatingSystem/Structures/DosDriveBase.cs
@@ -32,7 +32,7 @@ public abstract class DosDriveBase {
///
/// Gets the absolute path to the current DOS directory in use on the drive.
///
- public string CurrentDosDirectory { get; set; } = "";
+ public string CurrentDosDirectory { get; set; } = string.Empty;
///
/// Gets if it is a network drive. Not supported, always