Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3b5c7c7
Implement INT 15h AH=86h BIOS Wait with ASM-in-memory handler (#14)
Copilot Nov 14, 2025
d37852b
Fix INT 15h AH=86h BIOS Wait review issues (#15)
Copilot Nov 14, 2025
19dfd71
Feature/rtc clock and bios functions (#18)
maximilien-noal Nov 14, 2025
b9391cb
Centralize Avalonia telemetry opt-out in Directory.Build.props (#23)
Copilot Nov 14, 2025
13aa05f
Migrate to .NET 10 (#24)
Copilot Nov 14, 2025
5ff9c46
Add DOS CDS and DBCS structures with INT 21h AH=63h and ASM-based int…
Copilot Nov 15, 2025
0c75cff
Implement INT 70h (IRQ 8) RTC periodic interrupt handler (#28)
Copilot Nov 15, 2025
2ebe8ae
Refactor IOCTL implementation with enums and MemoryBasedDataStructure…
Copilot Nov 15, 2025
70afec6
Add production-ready MCP server with stdio transport, CFG CPU inspect…
Copilot Nov 15, 2025
57073c3
Update agent guide with coding standards, tooling requirements, and t…
Copilot Nov 15, 2025
e840933
Port keyboard buffer improvements from fix/keyboard_buffer to master …
Copilot Nov 15, 2025
82dba19
Implement Sound Blaster DSP command 0xE2 (DMA Identification) (#35)
Copilot Nov 15, 2025
d4fcffe
Fix keyboard translation regression: use PhysicalKey instead of Key (…
Copilot Nov 15, 2025
c871367
Add comprehensive XML documentation to Spice86 codebase (#38)
Copilot Nov 16, 2025
49de0a3
Add meaningful XML documentation to critical public APIs (#39)
Copilot Nov 25, 2025
26e5005
Add COMMAND.COM simulation as root of DOS PSP chain (#40)
Copilot Nov 27, 2025
8246a52
Fix DOS environment block to preserve full program path (#41)
Copilot Nov 27, 2025
5c768b7
Add DOS memory allocation strategy support (INT 21h/58h) and MCB chai…
Copilot Nov 27, 2025
47b704b
Fix EMS implementation bugs and clarify spec compliance (#43)
Copilot Nov 27, 2025
ad47b2e
Fix environment block being overwritten by PSP initialization (#48)
Copilot Nov 27, 2025
b1fa726
Implement Sound Blaster DSP command 0xE4 (Write Test Register) (#49)
Copilot Nov 27, 2025
80e8bbb
Implement INT 21h function 52h - Get DOS SYSVARS/List of Lists pointe…
Copilot Nov 27, 2025
e873e51
Add RateGenerator mode support to PcSpeaker SetPitControl (#51)
Copilot Nov 27, 2025
1a9b665
Implement FCB (File Control Block) support with FreeDOS-compatible be…
Copilot Nov 27, 2025
4d65566
Implement TSR support (INT 21h, AH=31h) (#45)
Copilot Nov 27, 2025
7c0c369
Implement DOS INT 21h PSP and process lifecycle management (#44)
Copilot Nov 27, 2025
bbef025
Remove flaky TerminateWithExitCode_TerminatesProgramNormally test (#58)
Copilot Nov 29, 2025
ae5a0ad
feat: Restore complete SetCurrentPsp (INT 21h AH=50h) handler and PSP…
Copilot Nov 29, 2025
e05feca
Implement VESA VBE 1.0 functions for INT 10h AH=4Fh (#60)
Copilot Nov 29, 2025
e67133f
Preserve DOS IOCTL improvements from copilot/compare-dos-console-ioct…
Copilot Nov 29, 2025
b625f3c
Fix DOS INT 21h BufferedInput keyboard input implementation (#62)
Copilot Nov 29, 2025
f0d09d2
feat: Implement INT 21h AH=01h character input with echo (#67)
Copilot Nov 29, 2025
fa7829b
Add OS HOOK Device Busy and Device Post handlers to BIOS INT 15h (#69)
Copilot Nov 29, 2025
eab6dc9
Implement FCB Find First/Next (INT 21h 0x11/0x12) (#68)
Copilot Nov 29, 2025
ec91a94
Implement Create Child PSP in INT21H based on DOS 4 and DOSBox (#66)
Copilot Nov 29, 2025
bb48b61
chore: merge master into branch
maximilien-noal Dec 3, 2025
7d1ac36
refactor: VgaBios uses enums and structures for VESA
maximilien-noal Dec 3, 2025
7895db2
docs: integrated VBE VESA 1.2 specs into XML
maximilien-noal Dec 3, 2025
d48911a
Implement INT 21H function 26H (Create New PSP)
Copilot Dec 3, 2025
18b26ae
Address code review feedback - optimize PSP copy and document segment…
Copilot Dec 3, 2025
b59eb1b
fix: reintroduce Grp4Callback fix for DOS EXEC
maximilien-noal Dec 4, 2025
ed9bd28
Fix DOS MCB joining to match FreeDOS kernel behavior (#1590)
Copilot Dec 4, 2025
cb8fbcf
Fix DOS MCB joining to match FreeDOS kernel behavior (#1591)
Copilot Dec 4, 2025
8ecb574
Optimize Grp4Callback to use instruction's Address property and Segme…
Copilot Dec 4, 2025
881b398
Fix EMS context save area size calculation in function 0x59h (#1593)
Copilot Dec 4, 2025
397b8a3
Fix DOS file handle inheritance from parent PSP (#1595)
Copilot Dec 4, 2025
edf1af4
feat: GetSetGlobalLoadedCodePageTable (not supported)
maximilien-noal Dec 4, 2025
9f27644
chore: merge master into this ref branch
maximilien-noal Dec 5, 2025
a0b5f67
Enable MCP server by default (#1597)
Copilot Dec 5, 2025
64a26fd
fix: (by LLM) for DOS Command Tail (backport)
LowLevelMahn Dec 5, 2025
b5ef014
fix: exe image loading is less strict, like DOS does
maximilien-noal Dec 6, 2025
ac86ffb
refactor: refined ProgramSize logic (same, less code)
maximilien-noal Dec 6, 2025
a6d974c
Merge branch 'fix/exe_loading' into reference/improvements_dos_bios
maximilien-noal Dec 6, 2025
27b7fd1
chore: remove dead code
maximilien-noal Dec 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 11 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,17 @@ Variants: `MemoryBasedDataStructureWithCsBaseAddress`, `MemoryBasedDataStructure
- The repository already has telemetry disabled in code, but it may still cause issues for AI agents.
- Be aware of this configuration to avoid telemetry-related blocking issues.

### MCP Server Usage
- **A Model Context Protocol (MCP) server is present** in this repository.
- **Always use the MCP server** and take advantage of it in every work you do.
- The MCP server provides tools for:
- Reading CPU registers (`read_cpu_registers`)
- Reading memory (`read_memory`)
- Listing functions (`list_functions`)
- Inspecting CFG CPU graph (`read_cfg_cpu_graph` - requires `--CfgCpu` flag)
- Enable with: `--McpServer true`
- See `doc/mcpServerReadme.md` for detailed documentation

### Code Style (enforced by `.editorconfig`)
- **No `var` keyword**: Use explicit types instead (enforced by `.editorconfig`)
```csharp
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- run: |
echo "running custom build action..."
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- name: Test with dotnet
working-directory: ./src
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- name: Test with dotnet
working-directory: ./src
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.x'
dotnet-version: '10.0.x'

- name: Install zip
uses: montudor/action-zip@v1
Expand Down
330 changes: 330 additions & 0 deletions doc/mcpServerExample.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
# MCP Server Usage Examples

This document provides practical examples of using the Spice86 MCP server to inspect emulator state.

## Stdio Transport Usage (Standard MCP)

The MCP server uses stdio transport, the standard communication method for MCP servers. Enable it with the `--McpServer` flag:

```bash
dotnet run --project src/Spice86 -- --Exe program.exe --McpServer true
```

When enabled, the server:
- Reads JSON-RPC requests from **stdin** (newline-delimited)
- Writes JSON-RPC responses to **stdout** (newline-delimited)
- Runs in a background task until Spice86 exits

External tools and AI models can communicate with the emulator by sending JSON-RPC requests to stdin and reading responses from stdout.

### Example: External Tool Communication

```bash
# Start Spice86 with MCP server enabled
dotnet run --project src/Spice86 -- --Exe program.exe --McpServer true &

# Send an initialize request
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18"},"id":1}' | dotnet run --project src/Spice86 -- --Exe program.exe --McpServer true

# Send a tools/list request
echo '{"jsonrpc":"2.0","method":"tools/list","id":2}' | dotnet run --project src/Spice86 -- --Exe program.exe --McpServer true

# Send a read_cpu_registers request
echo '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"read_cpu_registers","arguments":{}},"id":3}' | dotnet run --project src/Spice86 -- --Exe program.exe --McpServer true
```

## In-Process API Usage (Testing/Debugging)

For testing and debugging, you can also use the MCP server in-process via the `HandleRequest` API:

```csharp
using Spice86;
using Spice86.Core.CLI;
using Spice86.Core.Emulator.Mcp;

// Create configuration for a DOS program
Configuration configuration = new Configuration {
Exe = "path/to/program.exe",
HeadlessMode = HeadlessType.Minimal,
GdbPort = 0 // Disable GDB server if not needed
};

// Create the emulator with dependency injection
using Spice86DependencyInjection spice86 = new Spice86DependencyInjection(configuration);

// Access the MCP server
IMcpServer mcpServer = spice86.McpServer;

// Example 1: Initialize the MCP connection
string initRequest = """
{
"jsonrpc": "2.0",
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18"
},
"id": 1
}
""";

string initResponse = mcpServer.HandleRequest(initRequest);
Console.WriteLine("Initialize Response:");
Console.WriteLine(initResponse);

// Example 2: List available tools
string toolsListRequest = """
{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 2
}
""";

string toolsListResponse = mcpServer.HandleRequest(toolsListRequest);
Console.WriteLine("\nAvailable Tools:");
Console.WriteLine(toolsListResponse);

// Example 3: Read CPU registers
string readRegistersRequest = """
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "read_cpu_registers",
"arguments": {}
},
"id": 3
}
""";

string registersResponse = mcpServer.HandleRequest(readRegistersRequest);
Console.WriteLine("\nCPU Registers:");
Console.WriteLine(registersResponse);

// Example 4: Read memory at a specific address
string readMemoryRequest = """
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "read_memory",
"arguments": {
"address": 0,
"length": 256
}
},
"id": 4
}
""";

string memoryResponse = mcpServer.HandleRequest(readMemoryRequest);
Console.WriteLine("\nMemory Contents:");
Console.WriteLine(memoryResponse);

// Example 5: List functions
string listFunctionsRequest = """
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "list_functions",
"arguments": {
"limit": 20
}
},
"id": 5
}
""";

string functionsResponse = mcpServer.HandleRequest(listFunctionsRequest);
Console.WriteLine("\nFunction Catalogue:");
Console.WriteLine(functionsResponse);
```

## Integration with Debuggers

The MCP server can be used alongside the GDB server for comprehensive debugging:

```csharp
Configuration configuration = new Configuration {
Exe = "game.exe",
HeadlessMode = HeadlessType.Minimal,
GdbPort = 10000, // Enable GDB server on port 10000
Debug = true // Start paused
};

using Spice86DependencyInjection spice86 = new Spice86DependencyInjection(configuration);

// Use GDB for step-by-step debugging
// Use MCP server for programmatic state inspection
IMcpServer mcpServer = spice86.McpServer;

// You can query state at any point
string state = mcpServer.HandleRequest("""
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "read_cpu_registers",
"arguments": {}
},
"id": 1
}
""");
```

## Automated Testing

The MCP server is particularly useful for automated testing and verification:

```csharp
// Load a test program
Configuration config = new Configuration {
Exe = "test_program.com",
HeadlessMode = HeadlessType.Minimal
};

using Spice86DependencyInjection emulator = new Spice86DependencyInjection(config);

// Run the program for a certain number of cycles
// (integrate with your execution logic)

// Verify final state using MCP server
IMcpServer mcpServer = emulator.McpServer;

// Check that AX register has expected value
string response = mcpServer.HandleRequest("""
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "read_cpu_registers",
"arguments": {}
},
"id": 1
}
""");

// Parse response and assert values
// (integrate with your test framework)
```

## Real-time Monitoring

Create a monitoring tool that periodically samples emulator state:

```csharp
Configuration config = new Configuration {
Exe = "application.exe",
HeadlessMode = HeadlessType.Minimal
};

using Spice86DependencyInjection emulator = new Spice86DependencyInjection(config);
IMcpServer mcpServer = emulator.McpServer;

// Start emulation in a background task
Task.Run(() => emulator.ProgramExecutor.Run());

// Monitor state every 100ms
using System.Timers.Timer timer = new System.Timers.Timer(100);
timer.Elapsed += (sender, args) => {
string registers = mcpServer.HandleRequest("""
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "read_cpu_registers",
"arguments": {}
},
"id": 1
}
""");

// Log or visualize state
Console.WriteLine($"[{DateTime.Now:HH:mm:ss.fff}] {registers}");
};
timer.Start();

// Keep running until user stops
Console.WriteLine("Press Enter to stop monitoring...");
Console.ReadLine();
```

## CFG CPU Graph Inspection

When the Control Flow Graph CPU is enabled, you can inspect its state:

```csharp
using Spice86;
using Spice86.Core.CLI;

// Enable CFG CPU in configuration
Configuration configuration = new Configuration {
Exe = "path/to/program.exe",
CfgCpu = true, // Enable Control Flow Graph CPU
HeadlessMode = HeadlessType.Minimal
};

using Spice86DependencyInjection spice86 = new Spice86DependencyInjection(configuration);
IMcpServer mcpServer = spice86.McpServer;

// Run some emulation steps first to build the CFG
spice86.ProgramExecutor.Run();

// Now inspect the CFG CPU state
string cfgCpuRequest = """
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "read_cfg_cpu_graph",
"arguments": {}
},
"id": 1
}
""";

string response = mcpServer.HandleRequest(cfgCpuRequest);
Console.WriteLine("CFG CPU Graph State:");
Console.WriteLine(response);

// The response includes:
// - currentContextDepth: Execution context nesting level
// - currentContextEntryPoint: Entry point of current context
// - totalEntryPoints: Number of CFG entry points
// - entryPointAddresses: All entry point addresses
// - lastExecutedAddress: Most recently executed instruction
```

**Note**: The `read_cfg_cpu_graph` tool is only available when CFG CPU is enabled with `--CfgCpu` or `CfgCpu = true` in the configuration. Calling it when CFG CPU is disabled will return a JSON-RPC error with code `-32603`.

## Notes

- The MCP server is **thread-safe** and can be called from multiple threads concurrently
- The server uses an internal lock to serialize all requests, ensuring consistent state inspection
- The MCP server **automatically pauses** the emulator before inspecting state and resumes it afterward for thread-safe access
- If the emulator is already paused, the server preserves that state and doesn't auto-resume
- Requests are **synchronous** - each request is processed atomically while holding the lock
- The server does **not** modify emulator state - it's read-only by design
- All responses follow **JSON-RPC 2.0** format with proper error handling
- Memory reads are **limited to 4096 bytes** per request for safety
- The **CFG CPU graph tool** is only available when CFG CPU is enabled (`--CfgCpu` flag)

## Error Handling

Always handle potential errors in responses:

```csharp
string response = mcpServer.HandleRequest(request);
using JsonDocument doc = JsonDocument.Parse(response);
JsonElement root = doc.RootElement;

if (root.TryGetProperty("error", out JsonElement error)) {
int code = error.GetProperty("code").GetInt32();
string? message = error.GetProperty("message").GetString();
Console.WriteLine($"Error {code}: {message}");
} else if (root.TryGetProperty("result", out JsonElement result)) {
// Process successful result
Console.WriteLine("Success!");
}
```
Loading
Loading