Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
};
}
41 changes: 30 additions & 11 deletions src/Spice86.Core/Emulator/Mcp/McpHttpHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,11 @@
};
})
.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);
Expand All @@ -70,17 +71,27 @@
}
}

builder.WebHost.ConfigureKestrel(kestrel => {
kestrel.ListenLocalhost(port);
});

builder.WebHost.UseUrls($"http://localhost:{port}");
_app = builder.Build();
_app.MapGet("/health", () => Results.Json(new {
status = "ok",
service = "Spice86 MCP Server"
}));
_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
Expand All @@ -95,12 +106,20 @@
}

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");
}

Check notice

Code scanning / CodeQL

Generic catch clause Note

Generic catch clause.

// Wait forever so the thread never exits.
new System.Threading.ManualResetEvent(false).WaitOne();
}

public void Dispose() {
Expand Down
4 changes: 2 additions & 2 deletions src/Spice86.Core/Emulator/OperatingSystem/DosDriveManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR. Looks clean !

But could those changes in DosDriveManager be removed ?

_driveMap[GetDriveIndex('B')] = new VirtualDrive() { DriveLetter = 'B', MountedHostDirectory = cDriveFolderPath };
var cDrive = new VirtualDrive { DriveLetter = 'C', MountedHostDirectory = cDriveFolderPath };
_driveMap[GetDriveIndex('C')] = cDrive;
CurrentDrive = cDrive;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public abstract class DosDriveBase {
/// <summary>
/// Gets the absolute path to the current DOS directory in use on the drive.
/// </summary>
public string CurrentDosDirectory { get; set; } = "";
public string CurrentDosDirectory { get; set; } = string.Empty;

/// <summary>
/// Gets if it is a network drive. Not supported, always <see langword="false" />
Expand Down
Loading