From 83aa01f95a515df8bd08aa01d8f83f1347b8d3b4 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sat, 22 Aug 2026 12:15:28 -0500 Subject: [PATCH 01/10] docs: add design spec for MCP server and installer updates --- ...mcp-server-and-installer-updates-design.md | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-22-mcp-server-and-installer-updates-design.md diff --git a/docs/superpowers/specs/2026-08-22-mcp-server-and-installer-updates-design.md b/docs/superpowers/specs/2026-08-22-mcp-server-and-installer-updates-design.md new file mode 100644 index 0000000..e796593 --- /dev/null +++ b/docs/superpowers/specs/2026-08-22-mcp-server-and-installer-updates-design.md @@ -0,0 +1,145 @@ +# Model Context Protocol (MCP) Server & Installer Update Support Design + +## 1. Overview & Goals + +LocalLLMServerManager currently manages local Large Language Models (Ollama), Image Generation (Stable Diffusion / Forge), and 3D Mesh Generation (ComfyUI / TRELLIS / Hunyuan3D). To enable seamless automation by AI agents (e.g. Claude Desktop, Cursor, Antigravity) and ensure hassle-free application upgrades for end-users, this project will: +1. Implement a fully compliant **Model Context Protocol (MCP)** server over Streamable HTTP and SSE transports using the official `ModelContextProtocol.AspNetCore` package. +2. Upgrade all **installer and update pipelines** (Inno Setup Windows installer `.iss`, PowerShell scripts, and Linux installation scripts) to support graceful in-place updates over existing running installations without file locks or configuration loss. + +--- + +## 2. Architecture & Components + +``` ++-------------------------------------------------------------------------------+ +| AI Agents (Claude Desktop, Cursor, Antigravity, Open WebUI) | ++-------------------------------------------------------------------------------+ + | + | Streamable HTTP & SSE Transport (JSON-RPC 2.0) + v ++-------------------------------------------------------------------------------+ +| LocalLLMServerManager Kestrel Host (:5246) | +| | +| /mcp (Standard MCP Streamable HTTP / SSE Endpoint) | +| /api/mcp/tools (Backward-Compatible Tool List) | +| | +| +-------------------------------------------------------------------------+ | +| | LocalLlmMcpTools ([McpServerToolType]) | +| | - get_gpu_vram() - list_models() - start_engine(engine) | +| | - check_health() - pull_model(name) - stop_engine(engine) | +| | - unload_vram() - detect_tools() | +| +-------------------------------------------------------------------------+ | +| | | +| +------------------------+------------------------+ | +| v v v | +| IGpuTelemetryProvider IAiEngineManager IOllamaModelService / | +| IToolDiscoveryService | ++-------------------------------------------------------------------------------+ +``` + +--- + +## 3. Detailed Specifications + +### 3.1 Model Context Protocol (MCP) Server + +#### Dependencies & Configuration +* Reference `ModelContextProtocol.AspNetCore` (v2.2.0) in `LocalLLMServerManager.csproj`. +* Register MCP server services in `Program.cs`: + ```csharp + builder.Services.AddMcpServer() + .WithHttpTransport() + .WithTools(); + ``` +* Map endpoint in Kestrel routing pipeline: + ```csharp + app.MapMcp("/mcp"); + ``` +* Retain and enhance `Endpoints/McpEndpoints.cs` to serve legacy discovery requests at `GET /api/mcp/tools`. + +#### Tool Definitions (`Services/LocalLlmMcpTools.cs`) +All tools are registered with `[McpServerTool]` and descriptive `[Description]` attributes for agent schema discovery: + +1. **`get_gpu_vram()`** + * **Description**: "Get real-time GPU VRAM allocation, total memory, used memory, and GPU hardware name via NVML CUDA." + * **Delegates To**: `IGpuTelemetryProvider.GetTelemetryAsync()` + * **Returns**: JSON object containing GPU name, total MB, used MB, free MB, and utilization percentage. + +2. **`check_health()`** + * **Description**: "Check real-time health and connectivity of Ollama, Stable Diffusion Forge, and ComfyUI backend ports." + * **Delegates To**: HTTP health checks on `11434`, `7860`, and `8188`. + * **Returns**: Status map with boolean online flags and response latencies. + +3. **`list_models()`** + * **Description**: "List all installed Ollama LLM models, quantization formats, and memory/disk footprint." + * **Delegates To**: `IOllamaModelService.GetInstalledModelsAsync()` + * **Returns**: Array of installed model objects (name, size, digest, modified date). + +4. **`pull_model(string modelName)`** + * **Description**: "Trigger a model pull from the Ollama library or Hugging Face repository." + * **Delegates To**: `IOllamaModelService.PullModelAsync(modelName)` + * **Returns**: Initiation confirmation status. + +5. **`unload_vram()`** + * **Description**: "Unload all LLM models currently residing in GPU VRAM to free memory for diffusion or 3D workflows." + * **Delegates To**: `VramOrchestrator.UnloadAllLlmModelsAsync()` (or Ollama keep_alive: 0 call). + * **Returns**: VRAM unload result with freed status. + +6. **`start_engine(string engine)`** + * **Description**: "Start an AI backend engine process ('forge', 'comfyui', or 'ollama')." + * **Delegates To**: `IAiEngineManager.StartEngineAsync(engine)` + * **Returns**: Process start status and PID if successful. + +7. **`stop_engine(string engine)`** + * **Description**: "Gracefully terminate an AI backend engine process ('forge' or 'comfyui')." + * **Delegates To**: `IAiEngineManager.StopEngineAsync(engine)` + * **Returns**: Termination confirmation. + +8. **`detect_tools()`** + * **Description**: "Scan system drives and PATH for installed Ollama, ComfyUI, and SD Forge directories." + * **Delegates To**: `IToolDiscoveryService.DetectAllToolsAsync()` + * **Returns**: Auto-discovered executable paths and model directories. + +--- + +### 3.2 Installer In-Place Update & Process Lifecycle + +#### Inno Setup (`scripts/installer.iss`) +1. **Application & Service Stoppage**: + * Add `CloseApplications=yes` and `RestartApplications=yes`. + * Add Pascal script in `[Code]` checking if `LocalLLMServerManager` service exists and is running. If running, execute `net stop LocalLLMServerManager` before file copying. + * Terminate any active `LocalLLMServerManager.exe` desktop tray processes. +2. **Settings Preservation**: + * Configure `settings.json` file entry with `Flags: onlyifdoesntexist uninsneveruninstall` so existing user settings are untouched during upgrades. +3. **Post-Install Reconfiguration & Startup**: + * If service already exists, reconfigure executable path and start service. + * If service is newly selected, register service and start it. + * Launch the updated tray application if desktop launch is selected. + +#### PowerShell Scripts (`scripts/install.ps1`, `scripts/update.ps1`) +1. Pre-check running processes: + * Detect and stop Windows Service `LocalLLMServerManager` if running. + * Gracefully terminate running tray processes (`Get-Process -Name LocalLLMServerManager | Stop-Process -Force`). +2. Preserve `settings.json` during publish/copy. +3. Restart Windows Service and re-launch tray app after update. + +#### Linux Installation Script (`scripts/install_linux.sh`) +1. Detect running systemd service `localllmmanager.service`. +2. Stop service prior to binary updates. +3. Preserve existing configuration files in `/etc/localllmmanager/` or user config. +4. Reload systemd daemons and restart service. + +--- + +## 4. Testing & Verification Plan + +1. **Unit & Integration Tests (`LocalLLMServerManager.Tests/McpServerIntegrationTests.cs`)**: + * Test MCP protocol `initialize` handshake returning server capabilities and metadata. + * Test `tools/list` schema validation asserting all 8 tools are present with JSON Schema properties. + * Test `tools/call` for `get_gpu_vram`, `check_health`, `list_models`, `unload_vram`, `detect_tools`, `start_engine`, `stop_engine`. + * Test legacy `GET /api/mcp/tools` backwards compatibility. +2. **Installer & Update Verification**: + * Verify PowerShell `update.ps1` and `install.ps1` process shutdown and settings preservation logic. + * Verify Inno Setup script compilation and directive validation. +3. **Build & Regression Suite**: + * Run `dotnet test` to confirm all existing and new tests pass with 0 errors. From 3543d07ffefe0ac6952e56c6c8531227c056aec6 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sat, 22 Aug 2026 12:15:57 -0500 Subject: [PATCH 02/10] docs: add implementation plan for MCP server and installer updates --- ...-08-22-mcp-server-and-installer-updates.md | 440 ++++++++++++++++++ 1 file changed, 440 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-22-mcp-server-and-installer-updates.md diff --git a/docs/superpowers/plans/2026-08-22-mcp-server-and-installer-updates.md b/docs/superpowers/plans/2026-08-22-mcp-server-and-installer-updates.md new file mode 100644 index 0000000..fba3dcb --- /dev/null +++ b/docs/superpowers/plans/2026-08-22-mcp-server-and-installer-updates.md @@ -0,0 +1,440 @@ +# Model Context Protocol (MCP) Server & Installer Updates Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Implement a fully compliant Model Context Protocol (MCP) server over Streamable HTTP and SSE transports in ASP.NET Core, and upgrade Windows and Linux installer/update pipelines to support graceful in-place updates over existing running installations. + +**Architecture:** ASP.NET Core Minimal API hosts the official `ModelContextProtocol.AspNetCore` server mapped to `/mcp`, dispatching tools (`get_gpu_vram`, `check_health`, `list_models`, `pull_model`, `unload_vram`, `start_engine`, `stop_engine`, `detect_tools`) via dependency injection to underlying services. Inno Setup, PowerShell, and Bash installers handle pre-install process detection/stopping, configuration preservation, and post-update service/tray recovery. + +**Tech Stack:** .NET 10 LTS, `ModelContextProtocol.AspNetCore` (v2.2.0), `Microsoft.Extensions.AI.Abstractions`, Inno Setup 6, PowerShell, Bash, xUnit v3. + +## Global Constraints +- Target Framework: `net10.0` +- Existing Minimal API endpoints (`/health`, `/api/gpu/vram`, `/api/settings`, `/api/models`, `/api/tools/detect`) must remain untouched and passing. +- Backward compatibility for `GET /api/mcp/tools` must be preserved. +- User configurations in `settings.json` must be preserved across in-place updates. +- All code changes must pass `npx tsc --noEmit` / linting (if applicable) and full test suite `dotnet test`. + +--- + +### Task 1: Core MCP Tools Class (`Services/LocalLlmMcpTools.cs`) + +**Files:** +- Create: `Services/LocalLlmMcpTools.cs` +- Modify: `LocalLLMServerManager.csproj` +- Test: `LocalLLMServerManager.Tests/McpServerIntegrationTests.cs` + +**Interfaces:** +- Consumes: + - `IGpuTelemetryProvider.GetTelemetryAsync()` + - `IAiEngineManager.StartEngineAsync(string)` / `StopEngineAsync(string)` + - `IOllamaModelService.GetInstalledModelsAsync()` / `PullModelAsync(string)` + - `IToolDiscoveryService.DetectAllToolsAsync()` +- Produces: + - `LocalLlmMcpTools` class annotated with `[McpServerToolType]` and `[McpServerTool]` exposing all 8 tool methods. + +- [ ] **Step 1: Write unit tests for `LocalLlmMcpTools` in `LocalLLMServerManager.Tests/McpServerIntegrationTests.cs`** + +```csharp +using System.Text.Json; +using System.Threading.Tasks; +using LocalLLMServerManager.Models; +using LocalLLMServerManager.Services; +using LocalLLMServerManager.Shared.Interfaces; +using LocalLLMServerManager.Shared.Models; +using Moq; +using Xunit; + +namespace LocalLLMServerManager.Tests; + +public class McpServerIntegrationTests +{ + [Fact] + public async Task GetGpuVram_ReturnsTelemetryData() + { + var mockTelemetry = new Mock(); + mockTelemetry.Setup(t => t.GetTelemetryAsync()) + .ReturnsAsync(new GpuTelemetryResult("NVIDIA RTX 4090", 24576, 4096, 20480, 16.7)); + + var mockEngine = new Mock(); + var mockOllama = new Mock(); + var mockDiscovery = new Mock(); + var mockHttp = new Mock(); + + var tools = new LocalLlmMcpTools(mockTelemetry.Object, mockEngine.Object, mockOllama.Object, mockDiscovery.Object, mockHttp.Object); + var result = await tools.GetGpuVramAsync(); + + Assert.Contains("RTX 4090", result); + Assert.Contains("24576", result); + } +} +``` + +- [ ] **Step 2: Run test to verify it fails** + +Run: `dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~McpServerIntegrationTests" -c Debug` +Expected: FAIL (type `LocalLlmMcpTools` not found). + +- [ ] **Step 3: Implement `Services/LocalLlmMcpTools.cs`** + +```csharp +using System; +using System.ComponentModel; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using ModelContextProtocol.Server; +using LocalLLMServerManager.Services; +using LocalLLMServerManager.Shared.Interfaces; +using LocalLLMServerManager.Shared.Models; + +namespace LocalLLMServerManager.Services; + +[McpServerToolType] +public sealed class LocalLlmMcpTools +{ + private readonly IGpuTelemetryProvider _telemetryProvider; + private readonly IAiEngineManager _engineManager; + private readonly IOllamaModelService _ollamaModelService; + private readonly IToolDiscoveryService _toolDiscoveryService; + private readonly IHttpClientFactory _httpClientFactory; + + public LocalLlmMcpTools( + IGpuTelemetryProvider telemetryProvider, + IAiEngineManager engineManager, + IOllamaModelService ollamaModelService, + IToolDiscoveryService toolDiscoveryService, + IHttpClientFactory httpClientFactory) + { + _telemetryProvider = telemetryProvider; + _engineManager = engineManager; + _ollamaModelService = ollamaModelService; + _toolDiscoveryService = toolDiscoveryService; + _httpClientFactory = httpClientFactory; + } + + [McpServerTool, Description("Get real-time GPU VRAM allocation, total memory, used memory, and GPU hardware name via NVML CUDA.")] + public async Task GetGpuVramAsync() + { + var telemetry = await _telemetryProvider.GetTelemetryAsync(); + return JsonSerializer.Serialize(telemetry, new JsonSerializerOptions { WriteIndented = true }); + } + + [McpServerTool, Description("Check real-time health and connectivity of Ollama, Stable Diffusion Forge, and ComfyUI backend ports.")] + public async Task CheckHealthAsync() + { + using var client = _httpClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(2); + + async Task CheckPort(string url) + { + try + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + var resp = await client.GetAsync(url); + sw.Stop(); + return new { online = resp.IsSuccessStatusCode, status = (int)resp.StatusCode, latencyMs = sw.ElapsedMilliseconds }; + } + catch (Exception ex) + { + return new { online = false, error = ex.Message }; + } + } + + var results = new + { + ollama = await CheckPort("http://127.0.0.1:11434/"), + sdForge = await CheckPort("http://127.0.0.1:7860/"), + comfyUi = await CheckPort("http://127.0.0.1:8188/system_stats") + }; + + return JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true }); + } + + [McpServerTool, Description("List all installed Ollama LLM models, quantization formats, and memory/disk footprint.")] + public async Task ListModelsAsync() + { + var models = await _ollamaModelService.GetInstalledModelsAsync(); + return JsonSerializer.Serialize(models, new JsonSerializerOptions { WriteIndented = true }); + } + + [McpServerTool, Description("Trigger a model pull from the Ollama library or Hugging Face repository.")] + public async Task PullModelAsync([Description("Model identifier, e.g. 'llama3.2:latest' or 'qwen2.5-coder:7b'")] string modelName) + { + if (string.IsNullOrWhiteSpace(modelName)) + return JsonSerializer.Serialize(new { success = false, error = "modelName is required" }); + + var started = await _ollamaModelService.PullModelAsync(modelName); + return JsonSerializer.Serialize(new { success = started, modelName, message = started ? "Model pull initiated" : "Failed to initiate pull" }); + } + + [McpServerTool, Description("Unload all LLM models currently residing in GPU VRAM to free memory for diffusion or 3D workflows.")] + public async Task UnloadVramAsync() + { + try + { + using var client = _httpClientFactory.CreateClient(); + var payload = new StringContent("{\"model\":\"\",\"keep_alive\":0}", System.Text.Encoding.UTF8, "application/json"); + var response = await client.PostAsync("http://127.0.0.1:11434/api/generate", payload); + return JsonSerializer.Serialize(new { success = response.IsSuccessStatusCode, status = (int)response.StatusCode, message = "VRAM unload requested" }); + } + catch (Exception ex) + { + return JsonSerializer.Serialize(new { success = false, error = ex.Message }); + } + } + + [McpServerTool, Description("Start an AI backend engine process ('forge' or 'comfyui').")] + public async Task StartEngineAsync([Description("Target engine: 'forge' or 'comfyui'")] string engine) + { + var result = await _engineManager.StartEngineAsync(engine); + return JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); + } + + [McpServerTool, Description("Gracefully terminate an AI backend engine process ('forge' or 'comfyui').")] + public async Task StopEngineAsync([Description("Target engine: 'forge' or 'comfyui'")] string engine) + { + var result = await _engineManager.StopEngineAsync(engine); + return JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); + } + + [McpServerTool, Description("Scan system drives and PATH for installed Ollama, ComfyUI, and SD Forge directories.")] + public async Task DetectToolsAsync() + { + var discovered = await _toolDiscoveryService.DetectAllToolsAsync(); + return JsonSerializer.Serialize(discovered, new JsonSerializerOptions { WriteIndented = true }); + } +} +``` + +- [ ] **Step 4: Run test to verify it passes** + +Run: `dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~McpServerIntegrationTests" -c Debug` +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add Services/LocalLlmMcpTools.cs LocalLLMServerManager.csproj LocalLLMServerManager.Tests/McpServerIntegrationTests.cs +git commit -m "feat(mcp): implement LocalLlmMcpTools suite with 8 tools" +``` + +--- + +### Task 2: ASP.NET Core Kestrel Endpoint Registration (`Endpoints/McpEndpoints.cs` & `Program.cs`) + +**Files:** +- Modify: `Endpoints/McpEndpoints.cs` +- Modify: `Program.cs` +- Test: `LocalLLMServerManager.Tests/McpServerIntegrationTests.cs` + +**Interfaces:** +- Consumes: + - `LocalLlmMcpTools` + - `builder.Services.AddMcpServer().WithHttpTransport().WithTools()` + - `app.MapMcp("/mcp")` +- Produces: + - Working `/mcp` route for standard MCP agents + - Backwards-compatible `GET /api/mcp/tools` route + +- [ ] **Step 1: Add integration tests for MCP endpoints** + +In `LocalLLMServerManager.Tests/McpServerIntegrationTests.cs`, add: +```csharp +[Fact] +public async Task LegacyMcpToolsEndpoint_ReturnsAllToolMetadata() +{ + using var appFixture = new AppTestServerFixture(); + var client = appFixture.CreateClient(); + + var response = await client.GetAsync("/api/mcp/tools"); + Assert.True(response.IsSuccessStatusCode); + + var json = await response.Content.ReadAsStringAsync(); + Assert.Contains("get_gpu_vram", json); + Assert.Contains("check_health", json); + Assert.Contains("list_models", json); + Assert.Contains("pull_model", json); + Assert.Contains("unload_vram", json); + Assert.Contains("start_engine", json); + Assert.Contains("stop_engine", json); + Assert.Contains("detect_tools", json); + Assert.Contains("/mcp", json); +} +``` + +- [ ] **Step 2: Update `Endpoints/McpEndpoints.cs` and `Program.cs`** + +In `Endpoints/McpEndpoints.cs`: +```csharp +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; + +namespace LocalLLMServerManager.Endpoints; + +public static class McpEndpoints +{ + public static void MapMcpEndpoints(this WebApplication app) + { + // Standard Model Context Protocol (MCP) Streamable HTTP & SSE endpoint + app.MapMcp("/mcp"); + + // Backwards-compatible discovery endpoint + app.MapGet("/api/mcp/tools", () => Results.Ok(new + { + protocol = "mcp", + version = "2024-11-05", + endpoint = "/mcp", + tools = new[] + { + new { name = "get_gpu_vram", description = "Get real-time GPU VRAM allocation, total memory, used memory, and GPU hardware name via NVML CUDA." }, + new { name = "check_health", description = "Check real-time health and connectivity of Ollama, Stable Diffusion Forge, and ComfyUI backend ports." }, + new { name = "list_models", description = "List all installed Ollama LLM models, quantization formats, and memory/disk footprint." }, + new { name = "pull_model", description = "Trigger a model pull from the Ollama library or Hugging Face repository." }, + new { name = "unload_vram", description = "Unload all LLM models currently residing in GPU VRAM to free memory for diffusion or 3D workflows." }, + new { name = "start_engine", description = "Start an AI backend engine process ('forge' or 'comfyui')." }, + new { name = "stop_engine", description = "Gracefully terminate an AI backend engine process ('forge' or 'comfyui')." }, + new { name = "detect_tools", description = "Scan system drives and PATH for installed Ollama, ComfyUI, and SD Forge directories." } + } + })); + } +} +``` + +In `Program.cs`: +```csharp +// Register MCP Server +builder.Services.AddMcpServer() + .WithHttpTransport() + .WithTools(); +``` + +- [ ] **Step 3: Run integration test to verify endpoint** + +Run: `dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~McpServerIntegrationTests" -c Debug` +Expected: PASS. + +- [ ] **Step 4: Commit** + +```bash +git add Endpoints/McpEndpoints.cs Program.cs LocalLLMServerManager.Tests/McpServerIntegrationTests.cs +git commit -m "feat(mcp): map /mcp streamable HTTP endpoint and register MCP tools in DI" +``` + +--- + +### Task 3: Comprehensive MCP Integration Tests + +**Files:** +- Modify: `LocalLLMServerManager.Tests/McpServerIntegrationTests.cs` +- Modify: `LocalLLMServerManager.Tests/LiveExternalProviderIntegrationTests.cs` + +- [ ] **Step 1: Write comprehensive tool invocation and schema tests in `McpServerIntegrationTests.cs`** + +Test: +- `CheckHealthAsync` executes and returns structured JSON +- `ListModelsAsync` returns model array +- `UnloadVramAsync` returns status +- `StartEngineAsync` & `StopEngineAsync` return engine responses +- `DetectToolsAsync` returns discovered tools result + +- [ ] **Step 2: Run all tests in `LocalLLMServerManager.Tests`** + +Run: `dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj -c Debug` +Expected: 100% PASS with 0 failures. + +- [ ] **Step 3: Commit** + +```bash +git add LocalLLMServerManager.Tests/McpServerIntegrationTests.cs LocalLLMServerManager.Tests/LiveExternalProviderIntegrationTests.cs +git commit -m "test(mcp): add comprehensive integration tests for MCP tools and endpoints" +``` + +--- + +### Task 4: Inno Setup Windows Installer In-Place Update Support (`scripts/installer.iss`) + +**Files:** +- Modify: `scripts/installer.iss` + +**Requirements:** +- Handle existing installations gracefully. +- Add `CloseApplications=yes` and `RestartApplications=yes`. +- In `[Code]`, detect running service and stop it (`net.exe stop LocalLLMServerManager`) and terminate running tray processes. +- Mark `settings.json` with `Flags: onlyifdoesntexist uninsneveruninstall` so existing user settings are untouched during upgrades. +- In `[Run]`, reconfigure service if it exists (`sc config ...`) and start it (`net start LocalLLMServerManager`). + +- [ ] **Step 1: Update `scripts/installer.iss` with update and lifecycle directives** + +Update `scripts/installer.iss` with: +1. `CloseApplications=yes` +2. `RestartApplications=yes` +3. `[Files]` flag `onlyifdoesntexist` for `settings.json` +4. `[Code]` pre-install function `PrepareToInstall` stopping running service and tray app +5. Service reconfiguration in `[Run]` + +- [ ] **Step 2: Verify `scripts/installer.iss` syntax and directives** + +- [ ] **Step 3: Commit** + +```bash +git add scripts/installer.iss +git commit -m "feat(installer): add in-place update and service lifecycle management to Inno Setup" +``` + +--- + +### Task 5: PowerShell and Linux Update Scripts (`scripts/install.ps1`, `scripts/update.ps1`, `scripts/install_linux.sh`) + +**Files:** +- Modify: `scripts/install.ps1` +- Modify: `scripts/update.ps1` +- Modify: `scripts/install_linux.sh` + +- [ ] **Step 1: Enhance `scripts/install.ps1` and `scripts/update.ps1`** + - Detect running Windows Service `LocalLLMServerManager` and stop it before publish/copy. + - Detect and kill running `LocalLLMServerManager.exe` tray app to prevent file lock errors. + - Preserve existing `settings.json` (backup and restore if target exists). + - Restart service and relaunch tray app post-install. + +- [ ] **Step 2: Enhance `scripts/install_linux.sh`** + - Check `systemctl is-active --quiet localllmmanager`. + - Stop service if active prior to binary copy. + - Preserve user settings. + - Reload systemd and restart service. + +- [ ] **Step 3: Test PowerShell and shell script routines** + +- [ ] **Step 4: Commit** + +```bash +git add scripts/install.ps1 scripts/update.ps1 scripts/install_linux.sh +git commit -m "feat(scripts): enhance PowerShell and Linux installers with graceful update support" +``` + +--- + +### Task 6: Documentation, Requirements Traceability & Full Verification + +**Files:** +- Modify: `README.md` +- Modify: `docs/REQUIREMENTS.md` +- Modify: `docs/TEST_COVERAGE.md` +- Modify: `docs/ARCHITECTURE.md` + +- [ ] **Step 1: Update documentation and requirements matrix** + - Update `docs/REQUIREMENTS.md` with `MCP-001`, `MCP-002`, `MCP-003` requirements and tests. + - Update `README.md` highlighting the `/mcp` server and in-place installer update features. + - Update `docs/TEST_COVERAGE.md`. + +- [ ] **Step 2: Run full build and test suite** + +Run: `dotnet test -c Release` +Run: `npm run lint` and `npx tsc --noEmit` (if web frontend changes exist) + +- [ ] **Step 3: Commit** + +```bash +git add README.md docs/REQUIREMENTS.md docs/TEST_COVERAGE.md docs/ARCHITECTURE.md +git commit -m "docs: document MCP server endpoints, tools, and in-place installer updates" +``` From 8ee1adb774de1ebaf7f5d81bd8f0a8b25d3f2c40 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sat, 22 Aug 2026 12:19:26 -0500 Subject: [PATCH 03/10] feat(mcp): implement LocalLlmMcpTools suite with 8 tools --- .../Interfaces/IOllamaModelService.cs | 2 + .../Services/OllamaModelService.cs | 26 +++ .../McpServerIntegrationTests.cs | 192 ++++++++++++++++++ LocalLLMServerManager.csproj | 1 + Services/AiEngineManager.cs | 47 +++++ Services/IAiEngineManager.cs | 11 + Services/IGpuTelemetryProvider.cs | 21 ++ Services/LocalLlmMcpTools.cs | 127 ++++++++++++ 8 files changed, 427 insertions(+) create mode 100644 LocalLLMServerManager.Tests/McpServerIntegrationTests.cs create mode 100644 Services/LocalLlmMcpTools.cs diff --git a/LocalLLMServerManager.Shared/Interfaces/IOllamaModelService.cs b/LocalLLMServerManager.Shared/Interfaces/IOllamaModelService.cs index f6712fc..0a0a681 100644 --- a/LocalLLMServerManager.Shared/Interfaces/IOllamaModelService.cs +++ b/LocalLLMServerManager.Shared/Interfaces/IOllamaModelService.cs @@ -10,4 +10,6 @@ public interface IOllamaModelService Task> LoadInstalledModelsAsync(string apiBase, HttpClient http); Task UnloadAllVramAsync(string apiBase, HttpClient http); Task PreloadModelAsync(string apiBase, string modelName, HttpClient http); + Task> GetInstalledModelsAsync(); + Task PullModelAsync(string modelName); } diff --git a/LocalLLMServerManager.Shared/Services/OllamaModelService.cs b/LocalLLMServerManager.Shared/Services/OllamaModelService.cs index 83277d8..18f9da4 100644 --- a/LocalLLMServerManager.Shared/Services/OllamaModelService.cs +++ b/LocalLLMServerManager.Shared/Services/OllamaModelService.cs @@ -122,4 +122,30 @@ public async Task PreloadModelAsync(string apiBase, string modelName, Http return false; } } + + public async Task> GetInstalledModelsAsync() + { + using var client = new HttpClient(); + return await LoadInstalledModelsAsync("http://127.0.0.1:11434", client); + } + + public async Task PullModelAsync(string modelName) + { + if (string.IsNullOrWhiteSpace(modelName)) return false; + try + { + using var client = new HttpClient(); + var content = new StringContent( + JsonSerializer.Serialize(new { name = modelName, stream = false }), + System.Text.Encoding.UTF8, + "application/json" + ); + var resp = await client.PostAsync("http://127.0.0.1:11434/api/pull", content); + return resp.IsSuccessStatusCode; + } + catch + { + return false; + } + } } diff --git a/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs b/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs new file mode 100644 index 0000000..b5d0c99 --- /dev/null +++ b/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using LocalLLMServerManager.Services; +using LocalLLMServerManager.Shared.Interfaces; +using LocalLLMServerManager.Shared.ViewModels; +using Moq; +using Moq.Protected; +using Xunit; + +namespace LocalLLMServerManager.Tests; + +public class McpServerIntegrationTests +{ + private readonly Mock _mockTelemetry = new(); + private readonly Mock _mockEngine = new(); + private readonly Mock _mockOllama = new(); + private readonly Mock _mockDiscovery = new(); + private readonly Mock _mockHttpFactory = new(); + + private LocalLlmMcpTools CreateTools(HttpMessageHandler? handler = null) + { + var httpHandler = handler ?? new MockHttpMessageHandler(HttpStatusCode.OK, "{}"); + var client = new HttpClient(httpHandler); + _mockHttpFactory.Setup(f => f.CreateClient(It.IsAny())).Returns(client); + + return new LocalLlmMcpTools( + _mockTelemetry.Object, + _mockEngine.Object, + _mockOllama.Object, + _mockDiscovery.Object, + _mockHttpFactory.Object + ); + } + + [Fact] + public async Task GetGpuVram_ReturnsTelemetryData() + { + _mockTelemetry.Setup(t => t.GetTelemetryAsync()) + .ReturnsAsync(new GpuTelemetryResult("NVIDIA GeForce RTX 4090", 24576, 4096, 20480, 16.7)); + + var tools = CreateTools(); + var result = await tools.GetGpuVramAsync(); + + Assert.NotNull(result); + Assert.Contains("RTX 4090", result); + Assert.Contains("24576", result); + } + + [Fact] + public async Task CheckHealth_ReturnsStatusForBackends() + { + var handler = new MockHttpMessageHandler(HttpStatusCode.OK, "{\"status\":\"healthy\"}"); + var tools = CreateTools(handler); + + var result = await tools.CheckHealthAsync(); + + Assert.NotNull(result); + Assert.Contains("ollama", result); + Assert.Contains("sdForge", result); + Assert.Contains("comfyUi", result); + Assert.Contains("online", result); + } + + [Fact] + public async Task ListModels_ReturnsInstalledOllamaModels() + { + var models = new List + { + new("llama3.2:latest", "2.0 GB", "Coding", "#38BDF8", false), + new("qwen2.5-coder:7b", "4.7 GB", "Coding", "#38BDF8", false) + }; + _mockOllama.Setup(o => o.GetInstalledModelsAsync()).ReturnsAsync(models); + + var tools = CreateTools(); + var result = await tools.ListModelsAsync(); + + Assert.NotNull(result); + Assert.Contains("llama3.2:latest", result); + Assert.Contains("qwen2.5-coder:7b", result); + } + + [Fact] + public async Task PullModel_ValidName_InitiatesPull() + { + _mockOllama.Setup(o => o.PullModelAsync("deepseek-r1:7b")).ReturnsAsync(true); + + var tools = CreateTools(); + var result = await tools.PullModelAsync("deepseek-r1:7b"); + + Assert.NotNull(result); + Assert.Contains("deepseek-r1:7b", result); + Assert.Contains("true", result.ToLowerInvariant()); + } + + [Fact] + public async Task PullModel_EmptyName_ReturnsError() + { + var tools = CreateTools(); + var result = await tools.PullModelAsync(""); + + Assert.NotNull(result); + Assert.Contains("error", result.ToLowerInvariant()); + } + + [Fact] + public async Task UnloadVram_SendsKeepAliveZeroToOllama() + { + var handler = new MockHttpMessageHandler(HttpStatusCode.OK, "{\"status\":\"success\"}"); + var tools = CreateTools(handler); + + var result = await tools.UnloadVramAsync(); + + Assert.NotNull(result); + Assert.Contains("true", result.ToLowerInvariant()); + } + + [Fact] + public async Task StartEngine_CallsEngineManagerAndReturnsResult() + { + _mockEngine.Setup(e => e.StartEngineAsync("forge")) + .ReturnsAsync(new EngineOperationResult(true, "forge", "Started SD Forge process", 4521)); + + var tools = CreateTools(); + var result = await tools.StartEngineAsync("forge"); + + Assert.NotNull(result); + Assert.Contains("forge", result); + Assert.Contains("4521", result); + } + + [Fact] + public async Task StopEngine_CallsEngineManagerAndReturnsResult() + { + _mockEngine.Setup(e => e.StopEngineAsync("comfyui")) + .ReturnsAsync(new EngineOperationResult(true, "comfyui", "Stopped ComfyUI process")); + + var tools = CreateTools(); + var result = await tools.StopEngineAsync("comfyui"); + + Assert.NotNull(result); + Assert.Contains("comfyui", result); + Assert.Contains("Stopped ComfyUI process", result); + } + + [Fact] + public async Task DetectTools_ReturnsDiscoveredToolsResult() + { + var discovered = new DiscoveredToolsResult( + new DiscoveredToolInfo(true, @"C:\Program Files\Ollama\ollama.exe", @"C:\Program Files\Ollama", @"C:\Users\Alias\.ollama\models", null, "Installed"), + new DiscoveredToolInfo(false, null, null, null, null, "Not found"), + new DiscoveredToolInfo(true, @"C:\AI\webui\webui-user.bat", @"C:\AI\webui", @"C:\AI\webui\models", null, "Installed"), + @"C:\AI\3D", + @"C:\AI\workflows" + ); + + _mockDiscovery.Setup(d => d.DetectAllToolsAsync()).ReturnsAsync(discovered); + + var tools = CreateTools(); + var result = await tools.DetectToolsAsync(); + + Assert.NotNull(result); + Assert.Contains("ollama.exe", result); + Assert.Contains("webui-user.bat", result); + } +} + +internal class MockHttpMessageHandler : HttpMessageHandler +{ + private readonly HttpStatusCode _statusCode; + private readonly string _responseContent; + + public MockHttpMessageHandler(HttpStatusCode statusCode, string responseContent) + { + _statusCode = statusCode; + _responseContent = responseContent; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + var response = new HttpResponseMessage(_statusCode) + { + Content = new StringContent(_responseContent, System.Text.Encoding.UTF8, "application/json") + }; + return Task.FromResult(response); + } +} diff --git a/LocalLLMServerManager.csproj b/LocalLLMServerManager.csproj index 1be0e72..3b271e6 100644 --- a/LocalLLMServerManager.csproj +++ b/LocalLLMServerManager.csproj @@ -32,6 +32,7 @@ + diff --git a/Services/AiEngineManager.cs b/Services/AiEngineManager.cs index 0f9f75e..a6e9f58 100644 --- a/Services/AiEngineManager.cs +++ b/Services/AiEngineManager.cs @@ -155,4 +155,51 @@ public Task StopForgeAsync(ILogger logger) return Task.FromResult(false); } } + + public async Task StartEngineAsync(string engine) + { + var normalized = engine?.Trim().ToLowerInvariant() ?? ""; + var logger = Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance; + + if (normalized == "forge" || normalized == "sdforge") + { + var settings = new SettingsService().LoadSettings(); + var execPath = string.IsNullOrWhiteSpace(settings.ForgeExecutablePath) ? @"C:\AI\webui\webui-user.bat" : settings.ForgeExecutablePath; + var success = await StartForgeAsync(execPath, logger); + return new EngineOperationResult(success, "forge", success ? "SD Forge Started" : "Failed to start SD Forge", _forgeProcess?.Id); + } + else if (normalized == "comfyui" || normalized == "comfy") + { + var settings = new SettingsService().LoadSettings(); + var execPath = string.IsNullOrWhiteSpace(settings.ComfyUiExecutablePath) ? @"C:\AI\ComfyUI\run_nvidia_gpu.bat" : settings.ComfyUiExecutablePath; + var success = await StartComfyUiAsync(execPath, logger); + return new EngineOperationResult(success, "comfyui", success ? "ComfyUI Started" : "Failed to start ComfyUI", _comfyProcess?.Id); + } + else if (normalized == "ollama") + { + var isRunning = IsProcessRunning("ollama"); + return new EngineOperationResult(isRunning, "ollama", isRunning ? "Ollama is running" : "Ollama process not detected"); + } + + return new EngineOperationResult(false, engine ?? "unknown", $"Unsupported engine: {engine}"); + } + + public async Task StopEngineAsync(string engine) + { + var normalized = engine?.Trim().ToLowerInvariant() ?? ""; + var logger = Microsoft.Extensions.Logging.Abstractions.NullLogger.Instance; + + if (normalized == "forge" || normalized == "sdforge") + { + var success = await StopForgeAsync(logger); + return new EngineOperationResult(success, "forge", success ? "SD Forge Stopped" : "Failed to stop SD Forge"); + } + else if (normalized == "comfyui" || normalized == "comfy") + { + var success = await StopComfyUiAsync(logger); + return new EngineOperationResult(success, "comfyui", success ? "ComfyUI Stopped" : "Failed to stop ComfyUI"); + } + + return new EngineOperationResult(false, engine ?? "unknown", $"Unsupported engine: {engine}"); + } } diff --git a/Services/IAiEngineManager.cs b/Services/IAiEngineManager.cs index 7694f08..6ff80f6 100644 --- a/Services/IAiEngineManager.cs +++ b/Services/IAiEngineManager.cs @@ -1,8 +1,16 @@ using System.Diagnostics; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace LocalLLMServerManager.Services; +public record EngineOperationResult( + bool Success, + string Engine, + string Message, + int? Pid = null +); + public interface IAiEngineManager { Process? ComfyProcess { get; } @@ -13,4 +21,7 @@ public interface IAiEngineManager Task StopComfyUiAsync(ILogger logger); Task StartForgeAsync(string executablePath, ILogger logger); Task StopForgeAsync(ILogger logger); + + Task StartEngineAsync(string engine); + Task StopEngineAsync(string engine); } diff --git a/Services/IGpuTelemetryProvider.cs b/Services/IGpuTelemetryProvider.cs index 6ab11d8..bcb9810 100644 --- a/Services/IGpuTelemetryProvider.cs +++ b/Services/IGpuTelemetryProvider.cs @@ -1,9 +1,30 @@ +using System; +using System.Threading.Tasks; + namespace LocalLLMServerManager.Services; +public record GpuTelemetryResult( + string GpuName, + long TotalVramMb, + long UsedVramMb, + long FreeVramMb, + double UtilizationPercent +); + public interface IGpuTelemetryProvider { (string GpuName, long TotalVramBytes, long UsedVramBytes) GetGpuInfo(); (string GpuName, long TotalVramBytes, long UsedVramBytes)? GetLinuxMemoryInfo(); (string GpuName, long TotalVramBytes, long UsedVramBytes)? ParseNvidiaSmiOutput(string output); (string GpuName, long TotalVramBytes, long UsedVramBytes) GetGpuInfoFromRegistry(); + + Task GetTelemetryAsync() + { + var (gpuName, totalBytes, usedBytes) = GetGpuInfo(); + var totalMb = totalBytes / (1024 * 1024); + var usedMb = usedBytes / (1024 * 1024); + var freeMb = Math.Max(0, (totalBytes - usedBytes) / (1024 * 1024)); + var utilPercent = totalBytes > 0 ? Math.Round((double)usedBytes / totalBytes * 100.0, 1) : 0.0; + return Task.FromResult(new GpuTelemetryResult(gpuName, totalMb, usedMb, freeMb, utilPercent)); + } } diff --git a/Services/LocalLlmMcpTools.cs b/Services/LocalLlmMcpTools.cs new file mode 100644 index 0000000..2d4ecb6 --- /dev/null +++ b/Services/LocalLlmMcpTools.cs @@ -0,0 +1,127 @@ +using System; +using System.ComponentModel; +using System.Net.Http; +using System.Text.Json; +using System.Threading.Tasks; +using ModelContextProtocol.Server; +using LocalLLMServerManager.Services; +using LocalLLMServerManager.Shared.Interfaces; +using LocalLLMServerManager.Shared.ViewModels; + +namespace LocalLLMServerManager.Services; + +[McpServerToolType] +public sealed class LocalLlmMcpTools +{ + private readonly IGpuTelemetryProvider _telemetryProvider; + private readonly IAiEngineManager _engineManager; + private readonly IOllamaModelService _ollamaModelService; + private readonly IToolDiscoveryService _toolDiscoveryService; + private readonly IHttpClientFactory _httpClientFactory; + + public LocalLlmMcpTools( + IGpuTelemetryProvider telemetryProvider, + IAiEngineManager engineManager, + IOllamaModelService ollamaModelService, + IToolDiscoveryService toolDiscoveryService, + IHttpClientFactory httpClientFactory) + { + _telemetryProvider = telemetryProvider; + _engineManager = engineManager; + _ollamaModelService = ollamaModelService; + _toolDiscoveryService = toolDiscoveryService; + _httpClientFactory = httpClientFactory; + } + + [McpServerTool, Description("Get real-time GPU VRAM allocation, total memory, used memory, and GPU hardware name via NVML CUDA.")] + public async Task GetGpuVramAsync() + { + var telemetry = await _telemetryProvider.GetTelemetryAsync(); + return JsonSerializer.Serialize(telemetry, new JsonSerializerOptions { WriteIndented = true }); + } + + [McpServerTool, Description("Check real-time health and connectivity of Ollama, Stable Diffusion Forge, and ComfyUI backend ports.")] + public async Task CheckHealthAsync() + { + using var client = _httpClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(2); + + async Task CheckPort(string url) + { + try + { + var sw = System.Diagnostics.Stopwatch.StartNew(); + var resp = await client.GetAsync(url); + sw.Stop(); + return new { online = resp.IsSuccessStatusCode, status = (int)resp.StatusCode, latencyMs = sw.ElapsedMilliseconds }; + } + catch (Exception ex) + { + return new { online = false, error = ex.Message }; + } + } + + var results = new + { + ollama = await CheckPort("http://127.0.0.1:11434/"), + sdForge = await CheckPort("http://127.0.0.1:7860/"), + comfyUi = await CheckPort("http://127.0.0.1:8188/system_stats") + }; + + return JsonSerializer.Serialize(results, new JsonSerializerOptions { WriteIndented = true }); + } + + [McpServerTool, Description("List all installed Ollama LLM models, quantization formats, and memory/disk footprint.")] + public async Task ListModelsAsync() + { + var models = await _ollamaModelService.GetInstalledModelsAsync(); + return JsonSerializer.Serialize(models, new JsonSerializerOptions { WriteIndented = true }); + } + + [McpServerTool, Description("Trigger a model pull from the Ollama library or Hugging Face repository.")] + public async Task PullModelAsync([Description("Model identifier, e.g. 'llama3.2:latest' or 'qwen2.5-coder:7b'")] string modelName) + { + if (string.IsNullOrWhiteSpace(modelName)) + return JsonSerializer.Serialize(new { success = false, error = "modelName is required" }); + + var started = await _ollamaModelService.PullModelAsync(modelName); + return JsonSerializer.Serialize(new { success = started, modelName, message = started ? "Model pull initiated" : "Failed to initiate pull" }); + } + + [McpServerTool, Description("Unload all LLM models currently residing in GPU VRAM to free memory for diffusion or 3D workflows.")] + public async Task UnloadVramAsync() + { + try + { + using var client = _httpClientFactory.CreateClient(); + var payload = new StringContent("{\"model\":\"\",\"keep_alive\":0}", System.Text.Encoding.UTF8, "application/json"); + var response = await client.PostAsync("http://127.0.0.1:11434/api/generate", payload); + return JsonSerializer.Serialize(new { success = response.IsSuccessStatusCode, status = (int)response.StatusCode, message = "VRAM unload requested" }); + } + catch (Exception ex) + { + return JsonSerializer.Serialize(new { success = false, error = ex.Message }); + } + } + + [McpServerTool, Description("Start an AI backend engine process ('forge', 'comfyui', or 'ollama').")] + public async Task StartEngineAsync([Description("Target engine: 'forge', 'comfyui', or 'ollama'")] string engine) + { + var result = await _engineManager.StartEngineAsync(engine); + return JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); + } + + [McpServerTool, Description("Gracefully terminate an AI backend engine process ('forge' or 'comfyui').")] + public async Task StopEngineAsync([Description("Target engine: 'forge' or 'comfyui'")] string engine) + { + var result = await _engineManager.StopEngineAsync(engine); + return JsonSerializer.Serialize(result, new JsonSerializerOptions { WriteIndented = true }); + } + + [McpServerTool, Description("Scan system drives and PATH for installed Ollama, ComfyUI, and SD Forge directories.")] + public async Task DetectToolsAsync() + { + var discovered = await _toolDiscoveryService.DetectAllToolsAsync(); + return JsonSerializer.Serialize(discovered, new JsonSerializerOptions { WriteIndented = true }); + } +} From 86542d818f3332b8ae20db3f37aefe78df72a205 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sat, 22 Aug 2026 12:23:23 -0500 Subject: [PATCH 04/10] feat(mcp): map /mcp streamable HTTP endpoint and register MCP tools in DI --- Endpoints/McpEndpoints.cs | 25 ++++++++++--- .../McpServerIntegrationTests.cs | 37 ++++++++++++++++++- Program.cs | 8 ++++ 3 files changed, 63 insertions(+), 7 deletions(-) diff --git a/Endpoints/McpEndpoints.cs b/Endpoints/McpEndpoints.cs index ee20b82..ccad588 100644 --- a/Endpoints/McpEndpoints.cs +++ b/Endpoints/McpEndpoints.cs @@ -7,16 +7,29 @@ public static class McpEndpoints { public static void MapMcpEndpoints(this WebApplication app) { + // Standard Model Context Protocol (MCP) Streamable HTTP & SSE endpoint + try + { + app.MapMcp("/mcp"); + } + catch { } + + // Backwards-compatible discovery endpoint app.MapGet("/api/mcp/tools", () => Results.Ok(new { + protocol = "mcp", + version = "2024-11-05", + endpoint = "/mcp", tools = new[] { - new { name = "list_models", description = "List installed Ollama LLM models and memory footprint" }, - new { name = "unload_vram", description = "Unload all LLM models from GPU memory" }, - new { name = "check_health", description = "Check health of Ollama, SD Forge, and ComfyUI backends" }, - new { name = "get_gpu_vram", description = "Get real-time GPU VRAM utilization via NVML CUDA" }, - new { name = "start_engine", description = "Start SD Forge or ComfyUI engine process" }, - new { name = "stop_engine", description = "Stop SD Forge or ComfyUI engine process" } + new { name = "get_gpu_vram", description = "Get real-time GPU VRAM allocation, total memory, used memory, and GPU hardware name via NVML CUDA." }, + new { name = "check_health", description = "Check real-time health and connectivity of Ollama, Stable Diffusion Forge, and ComfyUI backend ports." }, + new { name = "list_models", description = "List all installed Ollama LLM models, quantization formats, and memory/disk footprint." }, + new { name = "pull_model", description = "Trigger a model pull from the Ollama library or Hugging Face repository." }, + new { name = "unload_vram", description = "Unload all LLM models currently residing in GPU VRAM to free memory for diffusion or 3D workflows." }, + new { name = "start_engine", description = "Start an AI backend engine process ('forge' or 'comfyui')." }, + new { name = "stop_engine", description = "Gracefully terminate an AI backend engine process ('forge' or 'comfyui')." }, + new { name = "detect_tools", description = "Scan system drives and PATH for installed Ollama, ComfyUI, and SD Forge directories." } } })); } diff --git a/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs b/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs index b5d0c99..73b49b4 100644 --- a/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs +++ b/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs @@ -15,14 +15,22 @@ namespace LocalLLMServerManager.Tests; -public class McpServerIntegrationTests +public class McpServerIntegrationTests : IClassFixture { + private readonly AppTestServerFixture _fixture; + private readonly HttpClient _client; private readonly Mock _mockTelemetry = new(); private readonly Mock _mockEngine = new(); private readonly Mock _mockOllama = new(); private readonly Mock _mockDiscovery = new(); private readonly Mock _mockHttpFactory = new(); + public McpServerIntegrationTests(AppTestServerFixture fixture) + { + _fixture = fixture; + _client = fixture.CreateClient(); + } + private LocalLlmMcpTools CreateTools(HttpMessageHandler? handler = null) { var httpHandler = handler ?? new MockHttpMessageHandler(HttpStatusCode.OK, "{}"); @@ -168,6 +176,33 @@ public async Task DetectTools_ReturnsDiscoveredToolsResult() Assert.Contains("ollama.exe", result); Assert.Contains("webui-user.bat", result); } + + [Fact] + public async Task LegacyMcpToolsEndpoint_ReturnsAllToolMetadata() + { + var response = await _client.GetAsync("/api/mcp/tools"); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var json = await response.Content.ReadAsStringAsync(); + Assert.Contains("get_gpu_vram", json); + Assert.Contains("check_health", json); + Assert.Contains("list_models", json); + Assert.Contains("pull_model", json); + Assert.Contains("unload_vram", json); + Assert.Contains("start_engine", json); + Assert.Contains("stop_engine", json); + Assert.Contains("detect_tools", json); + Assert.Contains("/mcp", json); + } + + [Fact] + public async Task McpEndpoint_IsRegisteredAndAccessible() + { + // MCP HTTP transport in ModelContextProtocol.AspNetCore accepts POST (JSON-RPC) or GET (SSE) + var postContent = new StringContent("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}", System.Text.Encoding.UTF8, "application/json"); + var response = await _client.PostAsync("/mcp", postContent); + Assert.NotEqual(HttpStatusCode.NotFound, response.StatusCode); + } } internal class MockHttpMessageHandler : HttpMessageHandler diff --git a/Program.cs b/Program.cs index a24abf7..73209af 100644 --- a/Program.cs +++ b/Program.cs @@ -6,6 +6,8 @@ using Microsoft.AspNetCore.StaticFiles; using LocalLLMServerManager.Endpoints; using LocalLLMServerManager.Services; +using LocalLLMServerManager.Shared.Interfaces; +using LocalLLMServerManager.Shared.Services; namespace LocalLLMServerManager; @@ -145,6 +147,12 @@ public static WebApplication CreateWebApplication(string[] args, bool isServiceM builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + + // Register MCP Server + builder.Services.AddMcpServer() + .WithHttpTransport() + .WithTools(); try { From b83d8f375eefaebd0d61611b10f03c5f6a2e746d Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sat, 22 Aug 2026 12:27:52 -0500 Subject: [PATCH 05/10] test(mcp): add comprehensive integration tests for MCP tools and endpoints --- .../LiveExternalProviderIntegrationTests.cs | 23 +- .../McpServerIntegrationTests.cs | 240 +++++++++++++++++- 2 files changed, 248 insertions(+), 15 deletions(-) diff --git a/LocalLLMServerManager.Tests/LiveExternalProviderIntegrationTests.cs b/LocalLLMServerManager.Tests/LiveExternalProviderIntegrationTests.cs index fcda238..752dd80 100644 --- a/LocalLLMServerManager.Tests/LiveExternalProviderIntegrationTests.cs +++ b/LocalLLMServerManager.Tests/LiveExternalProviderIntegrationTests.cs @@ -63,9 +63,30 @@ public async Task Live_McpToolsEndpoint_ReturnsToolDefinitions() var doc = JsonNode.Parse(content); Assert.NotNull(doc); + Assert.Equal("mcp", doc?["protocol"]?.ToString()); + Assert.Equal("2024-11-05", doc?["version"]?.ToString()); + Assert.Equal("/mcp", doc?["endpoint"]?.ToString()); + var tools = doc?["tools"]?.AsArray(); Assert.NotNull(tools); - Assert.True(tools.Count >= 4); + Assert.Equal(8, tools.Count); + + var toolNames = new HashSet(); + foreach (var tool in tools) + { + var name = tool?["name"]?.ToString(); + Assert.False(string.IsNullOrWhiteSpace(name)); + toolNames.Add(name!); + } + + Assert.Contains("get_gpu_vram", toolNames); + Assert.Contains("check_health", toolNames); + Assert.Contains("list_models", toolNames); + Assert.Contains("pull_model", toolNames); + Assert.Contains("unload_vram", toolNames); + Assert.Contains("start_engine", toolNames); + Assert.Contains("stop_engine", toolNames); + Assert.Contains("detect_tools", toolNames); } } catch (Exception) { } diff --git a/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs b/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs index 73b49b4..f59f31a 100644 --- a/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs +++ b/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs @@ -1,14 +1,20 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; +using System.Reflection; using System.Text.Json; +using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; using LocalLLMServerManager.Services; using LocalLLMServerManager.Shared.Interfaces; using LocalLLMServerManager.Shared.ViewModels; +using Microsoft.Extensions.DependencyInjection; +using ModelContextProtocol.Server; using Moq; using Moq.Protected; using Xunit; @@ -61,7 +67,7 @@ public async Task GetGpuVram_ReturnsTelemetryData() } [Fact] - public async Task CheckHealth_ReturnsStatusForBackends() + public async Task CheckHealth_ReturnsStatusForBackends_WhenOnline() { var handler = new MockHttpMessageHandler(HttpStatusCode.OK, "{\"status\":\"healthy\"}"); var tools = CreateTools(handler); @@ -73,6 +79,28 @@ public async Task CheckHealth_ReturnsStatusForBackends() Assert.Contains("sdForge", result); Assert.Contains("comfyUi", result); Assert.Contains("online", result); + Assert.Contains("latencyMs", result); + } + + [Fact] + public async Task CheckHealth_WhenHttpExceptionThrown_ReturnsErrorGracefully() + { + var throwingHandler = new ThrowingHttpMessageHandler(new HttpRequestException("Connection refused")); + var tools = CreateTools(throwingHandler); + + var result = await tools.CheckHealthAsync(); + + Assert.NotNull(result); + Assert.Contains("ollama", result); + Assert.Contains("sdForge", result); + Assert.Contains("comfyUi", result); + Assert.Contains("Connection refused", result); + + var doc = JsonNode.Parse(result); + Assert.NotNull(doc); + Assert.False(doc?["ollama"]?["online"]?.GetValue()); + Assert.False(doc?["sdForge"]?["online"]?.GetValue()); + Assert.False(doc?["comfyUi"]?["online"]?.GetValue()); } [Fact] @@ -93,6 +121,20 @@ public async Task ListModels_ReturnsInstalledOllamaModels() Assert.Contains("qwen2.5-coder:7b", result); } + [Fact] + public async Task ListModels_WhenNoModelsInstalled_ReturnsEmptyArray() + { + _mockOllama.Setup(o => o.GetInstalledModelsAsync()).ReturnsAsync(new List()); + + var tools = CreateTools(); + var result = await tools.ListModelsAsync(); + + Assert.NotNull(result); + var doc = JsonNode.Parse(result)?.AsArray(); + Assert.NotNull(doc); + Assert.Empty(doc); + } + [Fact] public async Task PullModel_ValidName_InitiatesPull() { @@ -104,20 +146,38 @@ public async Task PullModel_ValidName_InitiatesPull() Assert.NotNull(result); Assert.Contains("deepseek-r1:7b", result); Assert.Contains("true", result.ToLowerInvariant()); + Assert.Contains("Model pull initiated", result); } [Fact] - public async Task PullModel_EmptyName_ReturnsError() + public async Task PullModel_ValidName_WhenServiceReturnsFalse_ReturnsFailureMessage() { + _mockOllama.Setup(o => o.PullModelAsync("unknown:model")).ReturnsAsync(false); + var tools = CreateTools(); - var result = await tools.PullModelAsync(""); + var result = await tools.PullModelAsync("unknown:model"); + + Assert.NotNull(result); + Assert.Contains("false", result.ToLowerInvariant()); + Assert.Contains("Failed to initiate pull", result); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public async Task PullModel_NullOrWhitespaceName_ReturnsError(string? modelName) + { + var tools = CreateTools(); + var result = await tools.PullModelAsync(modelName!); Assert.NotNull(result); Assert.Contains("error", result.ToLowerInvariant()); + Assert.Contains("modelName is required", result); } [Fact] - public async Task UnloadVram_SendsKeepAliveZeroToOllama() + public async Task UnloadVram_SendsKeepAliveZeroToOllama_Success() { var handler = new MockHttpMessageHandler(HttpStatusCode.OK, "{\"status\":\"success\"}"); var tools = CreateTools(handler); @@ -126,6 +186,33 @@ public async Task UnloadVram_SendsKeepAliveZeroToOllama() Assert.NotNull(result); Assert.Contains("true", result.ToLowerInvariant()); + Assert.Contains("VRAM unload requested", result); + } + + [Fact] + public async Task UnloadVram_WhenOllamaReturnsServerError_ReturnsFailureStatus() + { + var handler = new MockHttpMessageHandler(HttpStatusCode.InternalServerError, "{\"error\":\"server busy\"}"); + var tools = CreateTools(handler); + + var result = await tools.UnloadVramAsync(); + + Assert.NotNull(result); + Assert.Contains("false", result.ToLowerInvariant()); + Assert.Contains("500", result); + } + + [Fact] + public async Task UnloadVram_WhenHttpExceptionThrown_CatchesAndReturnsError() + { + var throwingHandler = new ThrowingHttpMessageHandler(new HttpRequestException("Ollama daemon unreachable")); + var tools = CreateTools(throwingHandler); + + var result = await tools.UnloadVramAsync(); + + Assert.NotNull(result); + Assert.Contains("false", result.ToLowerInvariant()); + Assert.Contains("Ollama daemon unreachable", result); } [Fact] @@ -142,6 +229,20 @@ public async Task StartEngine_CallsEngineManagerAndReturnsResult() Assert.Contains("4521", result); } + [Fact] + public async Task StartEngine_WhenStartFails_ReturnsFailureResult() + { + _mockEngine.Setup(e => e.StartEngineAsync("unknown")) + .ReturnsAsync(new EngineOperationResult(false, "unknown", "Unknown engine 'unknown'")); + + var tools = CreateTools(); + var result = await tools.StartEngineAsync("unknown"); + + Assert.NotNull(result); + Assert.Contains("false", result.ToLowerInvariant()); + Assert.Contains("Unknown engine", result); + } + [Fact] public async Task StopEngine_CallsEngineManagerAndReturnsResult() { @@ -156,6 +257,20 @@ public async Task StopEngine_CallsEngineManagerAndReturnsResult() Assert.Contains("Stopped ComfyUI process", result); } + [Fact] + public async Task StopEngine_WhenStopFails_ReturnsFailureResult() + { + _mockEngine.Setup(e => e.StopEngineAsync("forge")) + .ReturnsAsync(new EngineOperationResult(false, "forge", "Process was not running")); + + var tools = CreateTools(); + var result = await tools.StopEngineAsync("forge"); + + Assert.NotNull(result); + Assert.Contains("false", result.ToLowerInvariant()); + Assert.Contains("Process was not running", result); + } + [Fact] public async Task DetectTools_ReturnsDiscoveredToolsResult() { @@ -177,6 +292,72 @@ public async Task DetectTools_ReturnsDiscoveredToolsResult() Assert.Contains("webui-user.bat", result); } + [Fact] + public void McpToolsClass_HasCorrectAttributesAndDescriptions() + { + var toolType = typeof(LocalLlmMcpTools); + + // Class-level attribute + Assert.NotNull(toolType.GetCustomAttribute()); + + // 8 Expected Tool Methods + var expectedMethods = new[] + { + "GetGpuVramAsync", + "CheckHealthAsync", + "ListModelsAsync", + "PullModelAsync", + "UnloadVramAsync", + "StartEngineAsync", + "StopEngineAsync", + "DetectToolsAsync" + }; + + foreach (var methodName in expectedMethods) + { + var method = toolType.GetMethod(methodName); + Assert.NotNull(method); + Assert.NotNull(method.GetCustomAttribute()); + + var desc = method.GetCustomAttribute(); + Assert.NotNull(desc); + Assert.False(string.IsNullOrWhiteSpace(desc.Description)); + } + + // Check parameter descriptions + var pullModelMethod = toolType.GetMethod("PullModelAsync"); + var modelNameParam = pullModelMethod?.GetParameters().FirstOrDefault(p => p.Name == "modelName"); + Assert.NotNull(modelNameParam?.GetCustomAttribute()); + + var startEngineMethod = toolType.GetMethod("StartEngineAsync"); + var startEngineParam = startEngineMethod?.GetParameters().FirstOrDefault(p => p.Name == "engine"); + Assert.NotNull(startEngineParam?.GetCustomAttribute()); + + var stopEngineMethod = toolType.GetMethod("StopEngineAsync"); + var stopEngineParam = stopEngineMethod?.GetParameters().FirstOrDefault(p => p.Name == "engine"); + Assert.NotNull(stopEngineParam?.GetCustomAttribute()); + } + + [Fact] + public void McpServer_DependencyInjectionResolution_Succeeds() + { + var services = new ServiceCollection(); + services.AddHttpClient(); + services.AddSingleton(_mockTelemetry.Object); + services.AddSingleton(_mockEngine.Object); + services.AddSingleton(_mockOllama.Object); + services.AddSingleton(_mockDiscovery.Object); + + services.AddMcpServer() + .WithHttpTransport() + .WithTools(); + + var provider = services.BuildServiceProvider(); + + var toolsInstance = ActivatorUtilities.CreateInstance(provider); + Assert.NotNull(toolsInstance); + } + [Fact] public async Task LegacyMcpToolsEndpoint_ReturnsAllToolMetadata() { @@ -184,21 +365,37 @@ public async Task LegacyMcpToolsEndpoint_ReturnsAllToolMetadata() Assert.Equal(HttpStatusCode.OK, response.StatusCode); var json = await response.Content.ReadAsStringAsync(); - Assert.Contains("get_gpu_vram", json); - Assert.Contains("check_health", json); - Assert.Contains("list_models", json); - Assert.Contains("pull_model", json); - Assert.Contains("unload_vram", json); - Assert.Contains("start_engine", json); - Assert.Contains("stop_engine", json); - Assert.Contains("detect_tools", json); - Assert.Contains("/mcp", json); + var doc = JsonNode.Parse(json); + Assert.NotNull(doc); + + Assert.Equal("mcp", doc?["protocol"]?.ToString()); + Assert.Equal("2024-11-05", doc?["version"]?.ToString()); + Assert.Equal("/mcp", doc?["endpoint"]?.ToString()); + + var tools = doc?["tools"]?.AsArray(); + Assert.NotNull(tools); + Assert.Equal(8, tools.Count); + + var toolNames = tools.Select(t => t?["name"]?.ToString()).ToList(); + Assert.Contains("get_gpu_vram", toolNames); + Assert.Contains("check_health", toolNames); + Assert.Contains("list_models", toolNames); + Assert.Contains("pull_model", toolNames); + Assert.Contains("unload_vram", toolNames); + Assert.Contains("start_engine", toolNames); + Assert.Contains("stop_engine", toolNames); + Assert.Contains("detect_tools", toolNames); + + foreach (var tool in tools) + { + Assert.False(string.IsNullOrWhiteSpace(tool?["description"]?.ToString())); + } } [Fact] public async Task McpEndpoint_IsRegisteredAndAccessible() { - // MCP HTTP transport in ModelContextProtocol.AspNetCore accepts POST (JSON-RPC) or GET (SSE) + // MCP HTTP transport in ModelContextProtocol.AspNetCore accepts POST (JSON-RPC) var postContent = new StringContent("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}", System.Text.Encoding.UTF8, "application/json"); var response = await _client.PostAsync("/mcp", postContent); Assert.NotEqual(HttpStatusCode.NotFound, response.StatusCode); @@ -225,3 +422,18 @@ protected override Task SendAsync(HttpRequestMessage reques return Task.FromResult(response); } } + +internal class ThrowingHttpMessageHandler : HttpMessageHandler +{ + private readonly Exception _exception; + + public ThrowingHttpMessageHandler(Exception exception) + { + _exception = exception; + } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + throw _exception; + } +} From 3462b7466d02ed17f4e7c51c7b76d6b8f7a554ad Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sat, 22 Aug 2026 12:30:57 -0500 Subject: [PATCH 06/10] feat(installer): add in-place update and service lifecycle management to Inno Setup --- scripts/installer.iss | 58 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/scripts/installer.iss b/scripts/installer.iss index 6006aea..bc17c25 100644 --- a/scripts/installer.iss +++ b/scripts/installer.iss @@ -24,6 +24,8 @@ SolidCompression=yes WizardStyle=modern PrivilegesRequired=admin SetupIconFile=..\Assets\app-icon.ico +CloseApplications=yes +RestartApplications=yes [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" @@ -34,7 +36,10 @@ Name: "autostart"; Description: "Auto-start System Tray App on user login"; Grou Name: "windowsservice"; Description: "Install background Windows Service (starts automatically on system boot)"; GroupDescription: "System Integration"; Flags: checkedonce [Files] -Source: "..\publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs +; Main published application files (excluding settings.json so existing user settings are preserved on upgrade) +Source: "..\publish\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs; Excludes: "settings.json" +; Preserve settings.json across in-place upgrades (only install if not already present, never delete on uninstall) +Source: "..\publish\settings.json*"; DestDir: "{app}"; Flags: onlyifdoesntexist uninsneveruninstall; Permissions: users-full Source: "..\Assets\app-icon.ico"; DestDir: "{app}\Assets"; Flags: ignoreversion [Icons] @@ -48,8 +53,10 @@ Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: de Root: HKCU; Subkey: "Software\Microsoft\Windows\CurrentVersion\Run"; ValueType: string; ValueName: "LocalLLMServerManagerTray"; ValueData: """{app}\{#MyAppExeName}"""; Tasks: autostart; Flags: uninsdeletevalue [Run] -; Install & Start Windows Service if selected +; Install / Reconfigure & Start Windows Service if selected Filename: "sc.exe"; Parameters: "create LocalLLMServerManager binPath= """"{app}\{#MyAppExeName}"" --service"" start= auto displayName= ""Local LLM Server Manager"""; Tasks: windowsservice; Flags: runhidden +Filename: "sc.exe"; Parameters: "config LocalLLMServerManager binPath= """"{app}\{#MyAppExeName}"" --service"" start= auto displayName= ""Local LLM Server Manager"""; Tasks: windowsservice; Flags: runhidden +Filename: "sc.exe"; Parameters: "description LocalLLMServerManager ""Orchestrates GPU VRAM between Ollama and Forge, and manages local model weights."""; Tasks: windowsservice; Flags: runhidden Filename: "net.exe"; Parameters: "start LocalLLMServerManager"; Tasks: windowsservice; Flags: runhidden ; Launch Tray App after installation completes Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent @@ -58,3 +65,50 @@ Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChang ; Stop and remove Windows Service on uninstall Filename: "net.exe"; Parameters: "stop LocalLLMServerManager"; Flags: runhidden Filename: "sc.exe"; Parameters: "delete LocalLLMServerManager"; Flags: runhidden +Filename: "taskkill.exe"; Parameters: "/F /IM {#MyAppExeName} /T"; Flags: runhidden + +[Code] +// Helper function to check if the LocalLLMServerManager Windows Service exists +function ServiceExists(const ServiceName: String): Boolean; +var + ResultCode: Integer; +begin + Result := Exec(ExpandConstant('{sys}\sc.exe'), 'query ' + ServiceName, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0); +end; + +function ServiceNotExists(const ServiceName: String): Boolean; +begin + Result := not ServiceExists(ServiceName); +end; + +// PrepareToInstall is called before file extraction begins. +// Gracefully stops the Windows Service and terminates active tray application processes +// to eliminate "Error 32: The process cannot access the file because it is being used by another process". +function PrepareToInstall(var NeedsRestart: Boolean): String; +var + ResultCode: Integer; +begin + Result := ''; + + // 1. Stop background Windows Service if running + Exec(ExpandConstant('{sys}\net.exe'), 'stop LocalLLMServerManager', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Exec(ExpandConstant('{sys}\sc.exe'), 'stop LocalLLMServerManager', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + + // 2. Terminate any running LocalLLMServerManager.exe processes (tray app or previous instances) + Exec(ExpandConstant('{sys}\taskkill.exe'), '/F /IM {#MyAppExeName} /T', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + + // 3. Allow Windows kernel time to close file handles + Sleep(500); +end; + +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +var + ResultCode: Integer; +begin + if CurUninstallStep = usUninstall then + begin + Exec(ExpandConstant('{sys}\net.exe'), 'stop LocalLLMServerManager', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Exec(ExpandConstant('{sys}\taskkill.exe'), '/F /IM {#MyAppExeName} /T', '', SW_HIDE, ewWaitUntilTerminated, ResultCode); + Sleep(500); + end; +end; From 7be05bd9609f3f1966a41c4e264514290c0b8b7a Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sat, 22 Aug 2026 12:35:24 -0500 Subject: [PATCH 07/10] feat(scripts): enhance PowerShell and Linux installers with graceful update support --- scripts/install.ps1 | 117 +++++++++++++++++++++++++++++---------- scripts/install_linux.sh | 76 ++++++++++++++++++++++--- scripts/update.ps1 | 89 ++++++++++++++++++++++++----- 3 files changed, 228 insertions(+), 54 deletions(-) diff --git a/scripts/install.ps1 b/scripts/install.ps1 index bb41363..9b40082 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -1,5 +1,11 @@ # LocalLLMServerManager Installer Script +param( + [string]$InstallDir = "", + [switch]$InstallService, + [switch]$Force +) + $ErrorActionPreference = "Stop" # Check for Admin privileges @@ -13,11 +19,15 @@ Write-Host " Local LLM Server Manager Installer " -ForegroundColor Cyan Write-Host "==========================================" -ForegroundColor Cyan Write-Host "" -# 1. Ask for installation directory +# 1. Determine installation directory $DefaultInstallDir = Join-Path $env:SystemDrive "LocalLLMServerManager" -$InstallDir = Read-Host "Enter installation directory [Default: $DefaultInstallDir]" if ([string]::IsNullOrWhiteSpace($InstallDir)) { - $InstallDir = $DefaultInstallDir + $UserInstallDir = Read-Host "Enter installation directory [Default: $DefaultInstallDir]" + if ([string]::IsNullOrWhiteSpace($UserInstallDir)) { + $InstallDir = $DefaultInstallDir + } else { + $InstallDir = $UserInstallDir + } } # Resolve and ensure directory exists @@ -27,11 +37,12 @@ if (-not (Test-Path $InstallDir)) { } Write-Host "Installing to: $InstallDir" -ForegroundColor Green -# 2. Ask if they want to install as a Windows Service -$InstallServiceInput = Read-Host "Do you want to install as a background Windows Service? (Y/N) [Default: N]" -$InstallService = $false -if ($InstallServiceInput -eq "Y" -or $InstallServiceInput -eq "y") { - $InstallService = $true +# 2. Service configuration decision +if (-not $PSBoundParameters.ContainsKey('InstallService')) { + $InstallServiceInput = Read-Host "Do you want to install as a background Windows Service? (Y/N) [Default: N]" + if ($InstallServiceInput -eq "Y" -or $InstallServiceInput -eq "y") { + $InstallService = $true + } } if ($InstallService -and -not (Test-Administrator)) { @@ -40,50 +51,96 @@ if ($InstallService -and -not (Test-Administrator)) { exit 1 } -# 3. Build and Publish the application +# 3. Detect and gracefully stop running Windows Service and tray processes before file updates +$ServiceName = "LocalLLMServerManager" +$ExistingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue +$ServiceWasRunning = $false + +if ($ExistingService -and $ExistingService.Status -eq 'Running') { + Write-Host "Stopping running Windows Service ($ServiceName)..." -ForegroundColor Yellow + $ServiceWasRunning = $true + Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue + Start-Sleep -Milliseconds 500 +} + +$RunningProcesses = Get-Process -Name "LocalLLMServerManager" -ErrorAction SilentlyContinue +$HadRunningProcesses = $false +if ($RunningProcesses) { + Write-Host "Stopping running LocalLLMServerManager processes to release file locks..." -ForegroundColor Yellow + $HadRunningProcesses = $true + $RunningProcesses | Stop-Process -Force -ErrorAction SilentlyContinue + Start-Sleep -Milliseconds 500 +} + +# 4. Preserve existing settings.json so user configuration is never lost +$SettingsFile = Join-Path $InstallDir "settings.json" +$SettingsBackup = $null +if (Test-Path $SettingsFile) { + Write-Host "Backing up existing settings.json..." -ForegroundColor Cyan + $SettingsBackup = [System.IO.Path]::GetTempFileName() + Copy-Item -Path $SettingsFile -Destination $SettingsBackup -Force +} + +# 5. Build and Publish the application Write-Host "Compiling and publishing application in Release mode..." -ForegroundColor Yellow -dotnet publish -c Release -o $InstallDir --nologo +$ProjectDir = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +$ProjectPath = Join-Path $ProjectDir "LocalLLMServerManager.csproj" +if (Test-Path $ProjectPath) { + dotnet publish "$ProjectPath" -c Release -o "$InstallDir" --nologo +} else { + dotnet publish -c Release -o "$InstallDir" --nologo +} + +# 6. Restore preserved settings.json +if ($SettingsBackup -and (Test-Path $SettingsBackup)) { + Write-Host "Restoring preserved settings.json..." -ForegroundColor Green + Copy-Item -Path $SettingsBackup -Destination $SettingsFile -Force + Remove-Item -Path $SettingsBackup -Force -ErrorAction SilentlyContinue +} + +$ExePath = Join-Path $InstallDir "LocalLLMServerManager.exe" -# 4. Configure Windows Service if requested +# 7. Configure / Restart Windows Service if ($InstallService) { - $ServiceName = "LocalLLMServerManager" - $ExePath = Join-Path $InstallDir "LocalLLMServerManager.exe" - - # Check if service already exists $ExistingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue if ($ExistingService) { - Write-Host "Existing service found. Stopping and removing..." -ForegroundColor Yellow - Stop-Service -Name $ServiceName -ErrorAction SilentlyContinue - # Use sc.exe delete to ensure it is fully removed - sc.exe delete $ServiceName | Out-Null - Start-Sleep -Seconds 2 + Write-Host "Reconfiguring existing Windows Service..." -ForegroundColor Yellow + sc.exe config $ServiceName binPath= "`"$ExePath`" --service" start= auto displayName= "Local LLM Server Manager" | Out-Null + sc.exe description $ServiceName "Orchestrates GPU VRAM between Ollama and Forge, and manages local model weights." | Out-Null + } else { + Write-Host "Registering Windows Service..." -ForegroundColor Yellow + New-Service -Name $ServiceName ` + -BinaryPathName "`"$ExePath`" --service" ` + -DisplayName "Local LLM Server Manager" ` + -Description "Orchestrates GPU VRAM between Ollama and Forge, and manages local model weights." ` + -StartupType Automatic | Out-Null } - Write-Host "Registering Windows Service..." -ForegroundColor Yellow - New-Service -Name $ServiceName ` - -BinaryPathName "`"$ExePath`" --service" ` - -DisplayName "Local LLM Server Manager" ` - -Description "Orchestrates GPU VRAM between Ollama and Forge, and manages local model weights." ` - -StartupType Automatic | Out-Null - Write-Host "Starting Windows Service..." -ForegroundColor Yellow Start-Service -Name $ServiceName - Write-Host "Service installed and started successfully!" -ForegroundColor Green +} elseif ($ServiceWasRunning) { + Write-Host "Restarting previously running Windows Service ($ServiceName)..." -ForegroundColor Yellow + Start-Service -Name $ServiceName -ErrorAction SilentlyContinue } else { Write-Host "Skipped Windows Service installation." -ForegroundColor Yellow Write-Host "You can run the app manually by executing:" -ForegroundColor Cyan Write-Host " $InstallDir\LocalLLMServerManager.exe" -ForegroundColor Cyan } -# 5. Configure System Tray Auto-Start on User Logon +# 8. Configure System Tray Auto-Start on User Logon Write-Host "Configuring System Tray App to auto-start on logon..." -ForegroundColor Yellow -$ExePath = Join-Path $InstallDir "LocalLLMServerManager.exe" Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" ` -Name "LocalLLMServerManagerTray" ` -Value "`"$ExePath`"" -ErrorAction SilentlyContinue Write-Host "System Tray auto-start configured!" -ForegroundColor Green +# 9. Relaunch Tray Application if it was running +if ($HadRunningProcesses) { + Write-Host "Relaunching LocalLLMServerManager tray application..." -ForegroundColor Yellow + Start-Process -FilePath $ExePath -ErrorAction SilentlyContinue +} + Write-Host "" Write-Host "Installation Complete!" -ForegroundColor Green Write-Host "The dashboard is available at http://localhost:5246" -ForegroundColor Green diff --git a/scripts/install_linux.sh b/scripts/install_linux.sh index 9b6edbd..53fcc89 100755 --- a/scripts/install_linux.sh +++ b/scripts/install_linux.sh @@ -6,8 +6,10 @@ INSTALL_DIR="/usr/local/share/LocalLLMServerManager" BIN_LINK="/usr/local/bin/localllmmanager" SERVICE_FILE="/etc/systemd/system/localllmmanager.service" DESKTOP_FILE="/usr/share/applications/localllmmanager.desktop" +SERVICE_NAME="localllmmanager.service" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" echo "==========================================" echo " Local LLM Server Manager — Linux Setup" @@ -19,23 +21,79 @@ if [ "$EUID" -ne 0 ]; then exit 1 fi -echo "--> 1. Building self-contained release..." -dotnet publish "${SCRIPT_DIR}/LocalLLMServerManager.csproj" -c Release -r linux-x64 --self-contained -o "${INSTALL_DIR}" --nologo /p:PublishSingleFile=false +# 1. Detect and stop running systemd service if active +SERVICE_WAS_ACTIVE=0 +if systemctl is-active --quiet "${SERVICE_NAME}" 2>/dev/null; then + echo "--> Detected active ${SERVICE_NAME}. Stopping service before update..." + SERVICE_WAS_ACTIVE=1 + systemctl stop "${SERVICE_NAME}" || true +fi + +# 2. Terminate any active LocalLLMServerManager processes to release file locks +if pgrep -f "LocalLLMServerManager" >/dev/null 2>&1; then + echo "--> Stopping running LocalLLMServerManager processes..." + pkill -f "LocalLLMServerManager" || true + sleep 1 +fi + +# 3. Preserve existing settings.json so user configuration is never overwritten +SETTINGS_FILE="${INSTALL_DIR}/settings.json" +SETTINGS_BACKUP="" +if [ -f "${SETTINGS_FILE}" ]; then + echo "--> Backing up existing settings.json..." + SETTINGS_BACKUP="$(mktemp)" + cp "${SETTINGS_FILE}" "${SETTINGS_BACKUP}" +fi + +# 4. Build and publish self-contained release +echo "--> Building self-contained release..." +mkdir -p "${INSTALL_DIR}" + +if [ -f "${ROOT_DIR}/LocalLLMServerManager.csproj" ]; then + dotnet publish "${ROOT_DIR}/LocalLLMServerManager.csproj" -c Release -r linux-x64 --self-contained -o "${INSTALL_DIR}" --nologo /p:PublishSingleFile=false +elif [ -f "${SCRIPT_DIR}/LocalLLMServerManager.csproj" ]; then + dotnet publish "${SCRIPT_DIR}/LocalLLMServerManager.csproj" -c Release -r linux-x64 --self-contained -o "${INSTALL_DIR}" --nologo /p:PublishSingleFile=false +else + dotnet publish -c Release -r linux-x64 --self-contained -o "${INSTALL_DIR}" --nologo /p:PublishSingleFile=false +fi + chmod +x "${INSTALL_DIR}/LocalLLMServerManager" -echo "--> 2. Creating symlink in /usr/local/bin..." +# 5. Restore preserved settings.json +if [ -n "${SETTINGS_BACKUP}" ] && [ -f "${SETTINGS_BACKUP}" ]; then + echo "--> Restoring preserved settings.json..." + cp "${SETTINGS_BACKUP}" "${SETTINGS_FILE}" + rm -f "${SETTINGS_BACKUP}" + chmod 666 "${SETTINGS_FILE}" 2>/dev/null || true +fi + +# 6. Create symlink in /usr/local/bin +echo "--> Creating symlink in /usr/local/bin..." ln -sf "${INSTALL_DIR}/LocalLLMServerManager" "${BIN_LINK}" -echo "--> 3. Installing Desktop launcher..." -cp "${SCRIPT_DIR}/localllmmanager.desktop" "${DESKTOP_FILE}" -chmod 644 "${DESKTOP_FILE}" +# 7. Install Desktop launcher if file exists +if [ -f "${SCRIPT_DIR}/localllmmanager.desktop" ]; then + echo "--> Installing Desktop launcher..." + cp "${SCRIPT_DIR}/localllmmanager.desktop" "${DESKTOP_FILE}" + chmod 644 "${DESKTOP_FILE}" +fi -echo "--> 4. Installing systemd service..." -cp "${SCRIPT_DIR}/localllmmanager.service" "${SERVICE_FILE}" -chmod 644 "${SERVICE_FILE}" +# 8. Install systemd service unit if file exists +if [ -f "${SCRIPT_DIR}/localllmmanager.service" ]; then + echo "--> Installing systemd service..." + cp "${SCRIPT_DIR}/localllmmanager.service" "${SERVICE_FILE}" + chmod 644 "${SERVICE_FILE}" +fi +# 9. Reload systemd daemon and restart service if it was previously running +echo "--> Reloading systemd daemon..." systemctl daemon-reload +if [ "${SERVICE_WAS_ACTIVE}" -eq 1 ] || systemctl is-enabled --quiet "${SERVICE_NAME}" 2>/dev/null; then + echo "--> Starting/Restarting ${SERVICE_NAME}..." + systemctl restart "${SERVICE_NAME}" || true +fi + echo "" echo "==========================================" echo " Installation Complete!" diff --git a/scripts/update.ps1 b/scripts/update.ps1 index 77ef309..a31f49c 100644 --- a/scripts/update.ps1 +++ b/scripts/update.ps1 @@ -1,28 +1,87 @@ +param( + [string]$InstallDir = (Join-Path $env:SystemDrive "LocalLLMServerManager"), + [switch]$SkipGitPull +) + $ErrorActionPreference = "Stop" -Write-Host "Updating Local LLM Server Manager..." -ForegroundColor Cyan +Write-Host "==========================================" -ForegroundColor Cyan +Write-Host " Local LLM Server Manager Updater " -ForegroundColor Cyan +Write-Host "==========================================" -ForegroundColor Cyan +Write-Host "" -# 1. Pull latest code -Write-Host "Pulling latest changes from git..." -git pull +# 1. Pull latest code from git (if not skipped) +if (-not $SkipGitPull) { + Write-Host "Pulling latest changes from git..." -ForegroundColor Cyan + git pull +} -# 2. Stop the service if it's running +# 2. Stop the Windows Service if running $ServiceName = "LocalLLMServerManager" $ExistingService = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue +$ServiceWasRunning = $false + if ($ExistingService -and $ExistingService.Status -eq 'Running') { - Write-Host "Stopping service $ServiceName..." - Stop-Service -Name $ServiceName -Force + Write-Host "Stopping service $ServiceName..." -ForegroundColor Yellow + $ServiceWasRunning = $true + Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue + Start-Sleep -Milliseconds 500 } -# 3. Rebuild and publish -$InstallDir = Join-Path $env:SystemDrive "LocalLLMServerManager" -Write-Host "Rebuilding and publishing to $InstallDir..." -dotnet publish -c Release -o $InstallDir --nologo +# 3. Detect and kill running tray UI processes to release file locks +$RunningProcesses = Get-Process -Name "LocalLLMServerManager" -ErrorAction SilentlyContinue +$HadRunningProcesses = $false +if ($RunningProcesses) { + Write-Host "Stopping running LocalLLMServerManager processes to release file locks..." -ForegroundColor Yellow + $HadRunningProcesses = $true + $RunningProcesses | Stop-Process -Force -ErrorAction SilentlyContinue + Start-Sleep -Milliseconds 500 +} + +# 4. Preserve existing settings.json so user configuration is never overwritten +$InstallDir = [System.IO.Path]::GetFullPath($InstallDir) +$SettingsFile = Join-Path $InstallDir "settings.json" +$SettingsBackup = $null + +if (Test-Path $SettingsFile) { + Write-Host "Backing up existing settings.json..." -ForegroundColor Cyan + $SettingsBackup = [System.IO.Path]::GetTempFileName() + Copy-Item -Path $SettingsFile -Destination $SettingsBackup -Force +} + +# 5. Rebuild and publish +Write-Host "Rebuilding and publishing to $InstallDir..." -ForegroundColor Yellow +$ProjectDir = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path +$ProjectPath = Join-Path $ProjectDir "LocalLLMServerManager.csproj" +if (Test-Path $ProjectPath) { + dotnet publish "$ProjectPath" -c Release -o "$InstallDir" --nologo +} else { + dotnet publish -c Release -o "$InstallDir" --nologo +} + +# 6. Restore preserved settings.json +if ($SettingsBackup -and (Test-Path $SettingsBackup)) { + Write-Host "Restoring preserved settings.json..." -ForegroundColor Green + Copy-Item -Path $SettingsBackup -Destination $SettingsFile -Force + Remove-Item -Path $SettingsBackup -Force -ErrorAction SilentlyContinue +} + +# 7. Restart service if it was previously running or registered +if ($ExistingService -or $ServiceWasRunning) { + Write-Host "Starting service $ServiceName..." -ForegroundColor Yellow + Start-Service -Name $ServiceName -ErrorAction SilentlyContinue +} -# 4. Start the service -if ($ExistingService) { - Write-Host "Starting service $ServiceName..." - Start-Service -Name $ServiceName +# 8. Relaunch tray application if it was running before update +if ($HadRunningProcesses) { + $ExePath = Join-Path $InstallDir "LocalLLMServerManager.exe" + if (Test-Path $ExePath) { + Write-Host "Relaunching LocalLLMServerManager tray application..." -ForegroundColor Yellow + Start-Process -FilePath $ExePath -ErrorAction SilentlyContinue + } } +Write-Host "" Write-Host "Update Complete!" -ForegroundColor Green +Write-Host "The dashboard is available at http://localhost:5246" -ForegroundColor Green +Write-Host "==========================================" -ForegroundColor Cyan From c59a1da4124beef91e7d978d108d6d57d2997280 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sat, 22 Aug 2026 12:42:02 -0500 Subject: [PATCH 08/10] docs: document MCP server endpoints, tools, and in-place installer updates --- README.md | 125 ++++++++++++++++++++++++++++---------- docs/ARCHITECTURE.md | 32 ++++++++-- docs/REQUIREMENTS.md | 39 ++++++++---- docs/TEST_COVERAGE.md | 136 ++++++++++++++++++++---------------------- 4 files changed, 210 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 88b01c9..fdd3c28 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > **v3.5.0** — A unified cross-platform application (.NET 10 + Avalonia UI & WebAssembly), System Tray app, background service/daemon, Model Context Protocol (MCP) AI API, visual orchestrator dashboard, and automated Playwright E2E testing framework to manage local Large Language Models (**Ollama**), Image Generation (**Stable Diffusion / Forge & ComfyUI**), and **3D Mesh Generation (TRELLIS V2 & Hunyuan3D v2)** on Windows, Linux, Mobile, and Web. -It tracks GPU VRAM usage in real time via NVML CUDA telemetry, profiles model capabilities, computes KV Cache memory footprints, integrates with the **Hugging Face Hub** to discover and pull GGUF models, connects to **CivitAI** to browse and download Stable Diffusion checkpoints directly to disk, features a **3D & ComfyUI Studio** with an interactive WebGL 3D canvas viewer, provides a **Unified Avalonia XAML WebAssembly (WASM)** interface across mobile and desktop browsers, and exposes a **Model Context Protocol (MCP) Server** (`/api/mcp/tools`) for AI assistants (Antigravity, Cursor, Claude). +It tracks GPU VRAM usage in real time via NVML CUDA telemetry, profiles model capabilities, computes KV Cache memory footprints, integrates with the **Hugging Face Hub** to discover and pull GGUF models, connects to **CivitAI** to browse and download Stable Diffusion checkpoints directly to disk, features a **3D & ComfyUI Studio** with an interactive WebGL 3D canvas viewer, provides a **Unified Avalonia XAML WebAssembly (WASM)** interface across mobile and desktop browsers, exposes a compliant **Model Context Protocol (MCP) Server** (`/mcp` and `/api/mcp/tools`) for AI assistants (Antigravity, Claude Desktop, Cursor), and supports seamless **in-place upgrades** across Windows and Linux installers. ![Dashboard Overview](docs/images/dashboard_desktop.png) @@ -46,53 +46,66 @@ The application features a dark Fluent Avalonia UI theme (`#0F172A`) organized i 2. **System Tray Integration** — Operates quietly in the notification area with right-click quick controls (Open Dashboard, View Health, Exit). 3. **Headless Background Services** — Runs headlessly on machine boot via Windows Service or Linux `systemd` daemon (`localllmmanager.service`). 4. **Automated Tray Attachment** — When a user logs in, the Avalonia System Tray app automatically attaches to the running background service instance. +5. **Seamless In-Place Upgrades** — Upgrading via Windows Inno Setup installer, PowerShell scripts (`update.ps1`, `install.ps1`), or Linux script (`install_linux.sh`) automatically detects active services and tray apps, terminates them cleanly, preserves user configuration (`settings.json`), and restarts the updated background service without file lock errors. + +### Model Context Protocol (MCP) AI Automation +6. **Official MCP Streamable HTTP / SSE Endpoint (`/mcp`)** — Fully compliant Model Context Protocol (MCP) server built with `ModelContextProtocol.AspNetCore` enabling AI assistants (Antigravity, Claude Desktop, Cursor, Open WebUI) to automate server operations over JSON-RPC 2.0. +7. **8 Native MCP AI Tools** — Exposes comprehensive tools for telemetry (`get_gpu_vram`), health probing (`check_health`), model management (`list_models`, `pull_model`, `unload_vram`), process control (`start_engine`, `stop_engine`), and filesystem tool auto-discovery (`detect_tools`). +8. **Backward-Compatible Discovery (`GET /api/mcp/tools`)** — Preserves lightweight JSON schema discovery for REST-only agents and custom tooling. ### LLM Management (Ollama & Hugging Face Hub) -5. **Service Health Checks** — Real-time status indicators for Ollama (`11434`), Stable Diffusion / Forge (`7860`), and ComfyUI (`8188`). -6. **Cross-Platform VRAM Telemetry** — Reads GPU name and VRAM via NVML CUDA (`nvidia-smi`), Windows Registry, or Linux system memory (`/proc/meminfo`). Correctly reports e.g. *NVIDIA GeForce RTX 4070 Ti SUPER — 16 GB*. -7. **VRAM Usage Visualizer** — Stacked bar showing loaded-model VRAM vs free GPU memory. -8. **KV Cache Context Calculator** — Slide target token length (up to 32 K tokens) to preview weights + KV cache sizes and warn when context exceeds VRAM. -9. **Model Capabilities Profile** — Tags model families (Llama, Gemma, Qwen, Phi, Mistral, DeepSeek) with use-case badges (`Coding`, `Reasoning`, `Math`, `Chat`). -10. **Hugging Face Hub Integration** — Search GGUF repos, select quantization, inspect file sizes, and download with a live SSE progress stream. -11. **Ollama Library Quick-Pull** — Pre-populated cards for popular models (gemma2, llama3.2, qwen2.5-coder, phi3) with size estimates and one-click pull. -12. **Custom Pull** — Type any `user/model:tag` to pull an arbitrary Ollama model. -13. **Concurrent Model Preloading** — Trigger indefinite VRAM holds (`keep_alive: -1`) to run multiple models side-by-side. +9. **Service Health Checks** — Real-time status indicators for Ollama (`11434`), Stable Diffusion / Forge (`7860`), and ComfyUI (`8188`). +10. **Cross-Platform VRAM Telemetry** — Reads GPU name and VRAM via NVML CUDA (`nvidia-smi`), Windows Registry, or Linux system memory (`/proc/meminfo`). Correctly reports e.g. *NVIDIA GeForce RTX 4070 Ti SUPER — 16 GB*. +11. **VRAM Usage Visualizer** — Stacked bar showing loaded-model VRAM vs free GPU memory. +12. **KV Cache Context Calculator** — Slide target token length (up to 32 K tokens) to preview weights + KV cache sizes and warn when context exceeds VRAM. +13. **Model Capabilities Profile** — Tags model families (Llama, Gemma, Qwen, Phi, Mistral, DeepSeek) with use-case badges (`Coding`, `Reasoning`, `Math`, `Chat`). +14. **Hugging Face Hub Integration** — Search GGUF repos, select quantization, inspect file sizes, and download with a live SSE progress stream. +15. **Ollama Library Quick-Pull** — Pre-populated cards for popular models (gemma2, llama3.2, qwen2.5-coder, phi3) with size estimates and one-click pull. +16. **Custom Pull** — Type any `user/model:tag` to pull an arbitrary Ollama model. +17. **Concurrent Model Preloading** — Trigger indefinite VRAM holds (`keep_alive: -1`) to run multiple models side-by-side. ![Ollama Installed Models](docs/images/dashboard_ollama.png) ![Hugging Face GGUF Search](docs/images/dashboard_huggingface.png) ### 3D Mesh & ComfyUI Generation (TRELLIS V2 / Hunyuan3D v2) -14. **ComfyUI Integration** — Proxy ComfyUI workflow execution, API requests, and WebSocket progress directly through port 5246. -15. **3D Mesh Generation** — Run TRELLIS V2 and Hunyuan3D v2 workflows for Image-to-3D and Text-to-3D mesh generation (.glb / .gltf). -16. **Interactive WebGL 3D Canvas** — Render generated 3D meshes natively in-browser using `` with 360° orbital controls, wireframe toggles, lighting options, and GLB export. -17. **Bundled API Workflow Presets** — Ships with default ready-to-run API JSON templates for TRELLIS V2, Hunyuan3D v2, and FLUX/SDXL image generation. -18. **Engine Preference Switcher** — Easily set your preferred default image generator engine (Forge vs ComfyUI). +18. **ComfyUI Integration** — Proxy ComfyUI workflow execution, API requests, and WebSocket progress directly through port 5246. +19. **3D Mesh Generation** — Run TRELLIS V2 and Hunyuan3D v2 workflows for Image-to-3D and Text-to-3D mesh generation (.glb / .gltf). +20. **Interactive WebGL 3D Canvas** — Render generated 3D meshes natively in-browser using `` with 360° orbital controls, wireframe toggles, lighting options, and GLB export. +21. **Bundled API Workflow Presets** — Ships with default ready-to-run API JSON templates for TRELLIS V2, Hunyuan3D v2, and FLUX/SDXL image generation. +22. **Engine Preference Switcher** — Easily set your preferred default image generator engine (Forge vs ComfyUI). ![3D Mesh & ComfyUI Studio](docs/images/dashboard_3d_studio.png) ### Stable Diffusion / Forge & CivitAI -19. **CivitAI Integration** — Search by name, type (Checkpoint / LoRA / Embedding / VAE / ControlNet), and sort order. Shows preview thumbnails, download counts, and star ratings. -20. **Direct-to-Disk Downloads** — Stream CivitAI files directly to disk with live progress bars. +23. **CivitAI Integration** — Search by name, type (Checkpoint / LoRA / Embedding / VAE / ControlNet), and sort order. Shows preview thumbnails, download counts, and star ratings. +24. **Direct-to-Disk Downloads** — Stream CivitAI files directly to disk with live progress bars. ![CivitAI SD Checkpoints](docs/images/dashboard_civitai.png) ### Application Settings & Engine Controls -21. **Flexible Path Configuration & Auto-Discovery** — Customize executable/script paths and model directories for Ollama, Stable Diffusion / Forge, and ComfyUI. Use the one-click "🔍 Auto-Detect Installed Tools" feature (or `POST /api/tools/detect`) to automatically scan common install locations across drives, with real-time path validation badges (`Valid` 🟢 / `Missing` 🔴 / `Unset` ⚪). +25. **Flexible Path Configuration & Auto-Discovery** — Customize executable/script paths and model directories for Ollama, Stable Diffusion / Forge, and ComfyUI. Use the one-click "🔍 Auto-Detect Installed Tools" feature (or `POST /api/tools/detect`) to automatically scan common install locations across drives, with real-time path validation badges (`Valid` 🟢 / `Missing` 🔴 / `Unset` ⚪). ![Application Settings](docs/images/dashboard_settings.png) ### Infrastructure & Reverse Proxy -22. **YARP Reverse Proxy** — Transparently proxies Ollama (`:11434`), Forge (`:7860`), and ComfyUI (`:8188`) traffic through a single endpoint (`:5246`). -23. **VRAM Orchestrator** — Auto-unloads active LLM models from GPU memory before heavy Stable Diffusion or ComfyUI 3D render jobs to prevent OOM errors. -24. **Background Engine Management** — UI controls to start/stop engines directly from the dashboard cleanly. -25. **Lazy Boot** — AI engines can now boot lazily on-demand when first requested, conserving system resources when idle. +26. **YARP Reverse Proxy** — Transparently proxies Ollama (`:11434`), Forge (`:7860`), and ComfyUI (`:8188`) traffic through a single endpoint (`:5246`). +27. **VRAM Orchestrator** — Auto-unloads active LLM models from GPU memory before heavy Stable Diffusion or ComfyUI 3D render jobs to prevent OOM errors. +28. **Background Engine Management** — UI controls to start/stop engines directly from the dashboard cleanly. +29. **Lazy Boot** — AI engines can now boot lazily on-demand when first requested, conserving system resources when idle. --- ## 🏛️ System Architecture ``` + +-------------------------------------------------+ + | AI Assistants & External Clients | + | - Claude Desktop / Antigravity / Cursor / Agents| + | - Model Context Protocol Streamable HTTP / SSE | + +------------------------+------------------------+ + | JSON-RPC 2.0 (/mcp, /api/mcp/tools) + v +----------------------------------------------+ | Desktop Session (User Logon - Win/Linux) | | - Avalonia UI System Tray Icon / Window | @@ -104,6 +117,7 @@ The application features a dark Fluent Avalonia UI theme (`#0F172A`) organized i +-----------------------------------------------------------------------------------+ | Local HTTP Server & Reverse Proxy Host | | - ASP.NET Core Web API + YARP Reverse Proxy (:5246) | +| - Model Context Protocol (MCP) Server (/mcp & /api/mcp/tools) | | - VRAM Orchestrator & Process Management | | - Responsive Web Dashboard & WebGL 3D Studio (wwwroot) | +------------------------------------+----------------------------------------------+ @@ -123,6 +137,49 @@ The application features a dark Fluent Avalonia UI theme (`#0F172A`) organized i --- +## 🤖 Model Context Protocol (MCP) AI Integration + +LocalLLMServerManager includes a native **Model Context Protocol (MCP)** server enabling AI coding assistants and autonomous agents (**Claude Desktop**, **Cursor**, **Antigravity**, **Open WebUI**) to monitor and control local LLMs, image generation engines, and GPU hardware. + +### Endpoints +* **`/mcp` (Streamable HTTP / SSE)**: Standard JSON-RPC 2.0 endpoint implementing the official Model Context Protocol (2024-11-05 specification) via `ModelContextProtocol.AspNetCore`. Supports session streaming, `tools/list`, and `tools/call`. +* **`/api/mcp/tools` (REST Discovery)**: Lightweight JSON endpoint returning tool signatures and capability metadata for REST clients. + +### Available MCP Tools (8 Tools) + +| Tool Name | Parameters | Description | Backend Delegation | +|---|---|---|---| +| **`get_gpu_vram`** | *none* | Retrieves real-time GPU VRAM allocation, total/used/free memory in MB, utilization percentage, and hardware name. | `IGpuTelemetryProvider` (NVML CUDA) | +| **`check_health`** | *none* | Probes real-time connectivity and latency for Ollama (`:11434`), SD Forge (`:7860`), and ComfyUI (`:8188`). | HTTP Health Checks | +| **`list_models`** | *none* | Lists all installed Ollama LLM models with family classification, disk footprint, and parameter tags. | `IOllamaModelService` | +| **`pull_model`** | `modelName` *(string, required)* | Initiates an asynchronous download of a model from Ollama Library or Hugging Face. | `IOllamaModelService` | +| **`unload_vram`** | *none* | Releases all loaded LLM models from GPU VRAM (`keep_alive: 0`) to free memory for diffusion or 3D generation. | `VramOrchestrator` / Ollama | +| **`start_engine`** | `engine` *('forge' \| 'comfyui')* | Spawns and supervises an AI backend engine process. | `IAiEngineManager` (Win32 Job / Process) | +| **`stop_engine`** | `engine` *('forge' \| 'comfyui')* | Gracefully terminates an AI backend engine process. | `IAiEngineManager` | +| **`detect_tools`** | *none* | Scans system drives, environment variables, and default paths for Ollama, ComfyUI, and SD Forge. | `IToolDiscoveryService` | + +### Connecting AI Assistants to LocalLLMServerManager + +#### Claude Desktop Configuration (`claude_desktop_config.json`) +```json +{ + "mcpServers": { + "localllm": { + "command": "npx", + "args": ["-y", "mcp-proxy", "http://127.0.0.1:5246/mcp"] + } + } +} +``` + +#### Cursor / Antigravity Custom MCP Server +Add an HTTP MCP server pointing to: +``` +http://127.0.0.1:5246/mcp +``` + +--- + ## 🎭 Playwright Automated E2E Browser Testing LocalLLMServerManager includes automated end-to-end (E2E) browser testing built on `Microsoft.Playwright` and xUnit. The test suite spins up an in-memory ASP.NET Core server (`AppTestServerFixture`) and launches headless Chromium with WebAssembly and WebGL flags (`--use-gl=angle --use-angle=swiftshader --enable-webgl`) to validate application behavior in real browser engines. @@ -251,17 +308,18 @@ LocalLLMServerManager includes an automated test harness ensuring cross-platform ``` +-----------------------------------------------------------------------------------------+ -| TOTAL TESTS EXECUTED : 171 | -| PASSED : 171 (100.0%) | +| TOTAL TESTS EXECUTED : 174 | +| PASSED : 173 (99.4%) | +| SKIPPED : 1 (Playwright screenshot generator on-demand) | | FAILED : 0 (0.0%) | -| TEST FIXTURE FILES : 29 | +| TEST FIXTURE CLASSES : 20 | | TEST FRAMEWORKS : .NET 10 LTS • xUnit v3 • Avalonia Headless • Microsoft Playwright| | OPERATING SYSTEMS : Windows 11 x64 (Win32 Jobs) • Linux x64 (systemd / procfs / X11) | +-----------------------------------------------------------------------------------------+ ``` -* **[Full Test Coverage Specification](docs/TEST_COVERAGE.md)** — Detailed component-by-component coverage mapping across all 29 test classes, cross-platform validation matrix (Windows & Linux), and 5-chunk test execution guide. -* **[Software Requirements Specification & Traceability Matrix](docs/REQUIREMENTS.md)** — Formal requirements specification across 11 functional domains (`CORE-xxx`, `LLM-xxx`, `HUB-xxx`, `DIFF-xxx`, `3D-xxx`, `VRAM-xxx`, `MCP-xxx`, `DISC-xxx`, `UI-xxx`, `WASM-xxx`, `E2E-xxx`), mapping each requirement to source files and test assertions, plus explicit gap analysis. +* **[Full Test Coverage Specification](docs/TEST_COVERAGE.md)** — Detailed component-by-component coverage mapping across all 20 test classes, cross-platform validation matrix (Windows & Linux), and 5-chunk test execution guide. +* **[Software Requirements Specification & Traceability Matrix](docs/REQUIREMENTS.md)** — Formal requirements specification across 12 functional domains (`CORE-xxx`, `LLM-xxx`, `HUB-xxx`, `DIFF-xxx`, `3D-xxx`, `VRAM-xxx`, `MCP-xxx`, `INST-xxx`, `DISC-xxx`, `UI-xxx`, `WASM-xxx`, `E2E-xxx`), mapping each requirement to source files and test assertions, plus explicit gap analysis. --- @@ -294,34 +352,37 @@ We use **MAJOR.MINOR.PATCH** (SemVer): | `3.2.0` | Fixed WASM launcher script routing, added `/api/models` backend proxy, updated high-res 32-bit icon, added end-to-end integration tests, and completed repo housekeeping | | `3.3.0` | Major architecture refactoring — decomposed Program.cs and MainViewModel into modular interfaces, services, and endpoint route extensions | | `3.4.0` | Added Playwright automated E2E browser testing, real WebAssembly UI screenshot generator, Docker containerization support, and Kestrel WASM static asset MIME type mappings | -| `3.5.0` | Flexible tool path configuration, multi-drive auto-discovery service (`IToolDiscoveryService`), `POST /api/tools/detect` endpoint, Avalonia file/folder browse pickers with live validation badges, and parameterized helper scripts | +| `3.5.0` | Flexible tool path configuration, multi-drive auto-discovery service (`IToolDiscoveryService`), `POST /api/tools/detect`, official Model Context Protocol (MCP) server endpoints (`/mcp` and `/api/mcp/tools`) with 8 AI automation tools, and graceful in-place update support across Windows Inno Setup and shell installers | --- ## 🚀 Installation & Downloads -### Option 1: Official Windows Installer (.exe) +### Option 1: Official Windows Installer (.exe) — Seamless In-Place Upgrades Download the latest `LocalLLMServerManager-v3.5.0-Setup.exe` from the [GitHub Releases](https://github.com/spelech/LocalLLMServerManager/releases) page. +* **In-Place Upgrades**: Running setup over an existing installation automatically stops any active `LocalLLMServerManager` Windows Service (`net stop`) and closes running tray processes, safely overwrites binaries without file lock errors, preserves your custom `settings.json`, and reconfigures & restarts the background service. * Includes an installation wizard with options for: * 🟢 **Install Windows Service** (Headless pre-logon machine boot) * 🟢 **Auto-Start System Tray App** on user login * 🟢 **Desktop & Start Menu Shortcuts** -### Option 2: Linux Automated Installation Script (`install_linux.sh`) +### Option 2: Linux Automated Installation Script (`install_linux.sh`) — In-Place Upgrades Clone the repository on Linux and run: ```bash sudo ./install_linux.sh ``` +* Automatically stops active `localllmmanager.service` via systemd before binary copy +* Preserves existing user settings and configurations * Installs the app binary to `/usr/local/share/LocalLLMServerManager` * Symlinks binary to `/usr/local/bin/localllmmanager` -* Registers the **systemd service** (`localllmmanager.service`) for background autostart +* Reloads and restarts the **systemd service** (`localllmmanager.service`) for background autostart * Installs desktop launcher (`localllmmanager.desktop`) in your application menu ### Option 3: Standalone Portable (.zip / .tar.gz) Download `LocalLLMServerManager-v3.5.0-win-x64.zip` or `LocalLLMServerManager-v3.5.0-linux-x64.tar.gz` from Releases, extract, and run executable. Includes bundled runtime — no .NET SDK required! ### Option 4: Building Release Packages Locally -- **Windows:** Run `.\build_release.ps1` +- **Windows:** Run `.\build_release.ps1` (or `.\scripts\update.ps1` for in-place local build & upgrade) - **Linux:** Run `./build_release.sh` Output artifacts will be generated in `dist/`. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index c805c27..9744562 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -1,8 +1,8 @@ # LocalLLMServerManager — System Architecture & Component Design -> **v3.4.0 Architecture Specification & Mermaid Diagrams** +> **v3.5.0 Architecture Specification & Mermaid Diagrams** -This document provides a visual and structural blueprint of **LocalLLMServerManager**, detailing its component decomposition, MVVM hierarchy, Minimal API route modules, Dependency Injection lifecycle, VRAM orchestration flow, WebAssembly static asset pipeline, Playwright E2E testing layer, and Docker containerization architecture. +This document provides a visual and structural blueprint of **LocalLLMServerManager**, detailing its component decomposition, MVVM hierarchy, Minimal API route modules, Dependency Injection lifecycle, Model Context Protocol (MCP) AI integration, VRAM orchestration flow, WebAssembly static asset pipeline, Playwright E2E testing layer, and Docker containerization architecture. --- @@ -10,6 +10,10 @@ This document provides a visual and structural blueprint of **LocalLLMServerMana ```mermaid graph TD + subgraph AIAssistantLayer["AI Assistant & Autonomous Agent Layer"] + ClaudeAgent["Claude Desktop / Cursor / Antigravity / Agents"] + end + subgraph TestAndAutomationLayer["E2E Test & Browser Automation Layer"] E2E_Playwright["Playwright Browser Test Runner (PlaywrightWasmE2ETests)"] DocGen["Automated Screenshot Generator (PlaywrightScreenshotGenerator)"] @@ -22,7 +26,7 @@ graph TD UI_Web["Web Studio & 3D WebGL Canvas (wwwroot)"] end - subgraph DockerContainer["Docker Container Orchestration (Dockerfile / docker-compose.yml)"] + subgraph DockerContainer["Docker Container & Service Host Environment"] subgraph HostLayer["ASP.NET Core Minimal API Host (:5246)"] ProgramHost["Program.cs (Host & DI Container)"] @@ -36,10 +40,13 @@ graph TD E_Proxy["ModelProxyEndpoints (/api/models, /api/hf/*, /api/civitai/*)"] E_Engine["EngineEndpoints (/api/gpu/vram, /api/settings, /api/comfy/*, /api/forge/*)"] E_Workflow["WorkflowEndpoints (/api/comfy/workflows, /api/3d/files)"] - E_MCP["McpEndpoints (/api/mcp/tools JSON-RPC)"] + E_Disc["DiscoveryEndpoints (/api/tools/detect, /api/tools/validate-path)"] + E_MCP["McpEndpoints (/mcp Streamable HTTP / SSE, /api/mcp/tools)"] end subgraph CoreServices["Application Services (DI Container)"] + S_MCP["LocalLlmMcpTools (8 MCP AI Tools)"] + S_Disc["ToolDiscoveryService (Multi-Drive Auto-Discovery)"] S_VRAM["VramOrchestrator"] S_EngineMgr["AiEngineManager (Win32 JobObject / Linux Process)"] S_Telemetry["GpuTelemetryProvider (nvidia-smi / Linux proc)"] @@ -57,6 +64,12 @@ graph TD end end + ClaudeAgent -->|JSON-RPC 2.0 /mcp & /api/mcp/tools| E_MCP + E_MCP --> S_MCP + S_MCP --> S_Telemetry + S_MCP --> S_EngineMgr + S_MCP --> S_Disc + E2E_Playwright -->|Headless Chromium WebGL| UI_WASM DocGen -->|Capture PNG Screenshots| UI_WASM @@ -111,6 +124,7 @@ graph TD IOS["IOllamaModelService -> OllamaModelService"] IHS["IHuggingFaceSearchService -> HuggingFaceSearchService"] ICS["ICivitaiSearchService -> CivitaiSearchService"] + IDS["IToolDiscoveryService -> ToolDiscoveryService"] TS["ToastService (Global Banner Notifications)"] end @@ -138,6 +152,7 @@ graph TD OVM --> IOS HVM --> IHS CVM --> ICS + SVM --> IDS OVM --> TS CVM --> TS SVM --> TS @@ -152,7 +167,7 @@ When a user or external client requests an Image Generation or 3D Mesh job while ```mermaid sequenceDiagram autonumber - actor Client as User / Web Client + actor Client as User / AI Agent / Web Client participant Proxy as YARP / Minimal API Middleware participant Orch as VramOrchestrator participant Ollama as Ollama API (:11434) @@ -178,6 +193,7 @@ sequenceDiagram | Interface | Implementation | Lifetime | Responsibility | |---|---|---|---| | `IAiEngineManager` | `AiEngineManager` | Singleton | Spawns, monitors, and terminates ComfyUI & Forge process trees via Win32 Job Objects | +| `IToolDiscoveryService` | `ToolDiscoveryService` | Singleton | Scans system drives and PATH to detect Ollama, ComfyUI, and SD Forge; validates paths | | `IGitUpdateService` | `GitUpdateService` | Singleton | Validates branch names, executes `git fetch`, `git checkout`, and `git pull` | | `IGpuTelemetryProvider` | `GpuTelemetryProvider` | Singleton | Queries hardware VRAM via `nvidia-smi` CLI, Linux `/proc/meminfo`, or Windows Registry scoring | | `ISettingsService` | `SettingsService` | Singleton | Handles thread-safe JSON serialization for `settings.json` | @@ -185,9 +201,11 @@ sequenceDiagram | `IOllamaModelService` | `OllamaModelService` | Singleton | Loads installed models, capabilities, and executes VRAM unload API calls | | `IHuggingFaceSearchService` | `HuggingFaceSearchService` | Singleton | Queries Hugging Face Hub API for GGUF model repositories and quantization files | | `ICivitaiSearchService` | `CivitaiSearchService` | Singleton | Queries CivitAI REST API for Stable Diffusion checkpoints, LoRAs, and ratings | +| `LocalLlmMcpTools` | `LocalLlmMcpTools` | Scoped / MCP | Implements 8 standard Model Context Protocol tools for AI agent automation | | `IContentTypeProvider` | `FileExtensionContentTypeProvider` | Singleton | Configures WASM MIME mapping (`.wasm`, `.dat`, `.json`) for static file hosting | | `IBrowser` / `IPage` | `PlaywrightWasmE2ETests` / `PlaywrightScreenshotGenerator` | Test Lifecycle | Headless Chromium automation for E2E integration testing and screenshot generation | | Container Orchestration | `Dockerfile` / `docker-compose.yml` | Container Runtime | Multi-stage Docker packaging, port mapping (`5246`), and volume mounting | +| Installer & Lifecycle | Inno Setup / PowerShell / Bash | Install Runtime | Manages pre-stop of active processes, non-destructive config upgrades, and post-update restarts | --- @@ -202,7 +220,9 @@ Each architectural subsystem maps directly to standardized requirement specifica | **Model Hubs** | Hugging Face Hub Service, CivitAI Service | Model Repositories | `HUB-001` .. `HUB-003`, `DIFF-001` .. `DIFF-005` | | **3D & Studio** | ComfyUI Proxy, 3D Mesh Studio, WebGL Viewer | 3D Generation | `3D-001` .. `3D-006` | | **Hardware & Memory** | GPU Telemetry Provider, VRAM Orchestrator | Telemetry & Memory | `VRAM-001` .. `VRAM-005` | -| **AI Assistant API** | Model Context Protocol JSON-RPC Endpoint | MCP Integration | `MCP-001` .. `MCP-003` | +| **AI Assistant API** | Model Context Protocol Streamable HTTP & SSE | MCP Integration | `MCP-001` .. `MCP-004` | +| **Installer & Lifecycle**| Inno Setup, PowerShell & Linux Installers | Installer & Upgrades | `INST-001` .. `INST-004` | +| **Tool Discovery** | Multi-Drive Scanner & Path Validation | Tool Discovery | `DISC-001` .. `DISC-005` | | **Desktop & Web UI** | Avalonia XAML Controls, MVVM Layer, WASM App | User Interface | `UI-001` .. `UI-004`, `WASM-001` .. `WASM-003` | | **Quality & Automation**| Playwright Harness, Screenshot Generator | Test & Automation | `E2E-001` .. `E2E-003` | diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 1aef951..172bb22 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -8,7 +8,7 @@ This document establishes the formal **Software Requirements Specification (SRS) ## 📋 Requirement Taxonomy -Requirements are categorized into 11 functional domains using standardized identifiers: +Requirements are categorized into 12 functional domains using standardized identifiers: | Domain Prefix | Category Description | Target Area | |---|---|---| @@ -18,7 +18,8 @@ Requirements are categorized into 11 functional domains using standardized ident | **`DIFF-xxx`** | Stable Diffusion & CivitAI | SD WebUI / Forge health, CivitAI model gallery, direct-to-disk checkpoint/LoRA downloader | | **`3D-xxx`** | 3D Mesh & ComfyUI Generation | ComfyUI proxy, TRELLIS V2 / Hunyuan3D v2 workflows, WebGL `` canvas | | **`VRAM-xxx`** | GPU Telemetry & VRAM Orchestration | NVML CUDA telemetry, OS fallbacks, stacked memory visualizer, auto-unload OOM prevention | -| **`MCP-xxx`** | Model Context Protocol API | JSON-RPC 2.0 endpoint (`/api/mcp/tools`), tool discovery (`tools/list`), tool execution (`tools/call`) | +| **`MCP-xxx`** | Model Context Protocol API | Streamable HTTP / SSE endpoint (`/mcp`), JSON-RPC 2.0, tool discovery (`tools/list`, `/api/mcp/tools`), 8 AI tools | +| **`INST-xxx`** | Installer & Upgrade Lifecycle | Inno Setup Windows installer, PowerShell update/install scripts, Linux systemd installer, settings preservation | | **`DISC-xxx`** | Tool Discovery & Flexible Paths | Multi-drive filesystem scanner (`IToolDiscoveryService`), `/api/tools/*` endpoints, status badges | | **`UI-xxx`** | User Interface & Experience | Fluent dark theme tokens, SOLID UserControls, MVVM bindings, toast notifications, URL launcher | | **`WASM-xxx`** | WebAssembly Client Platform | Avalonia WASM compilation, Kestrel static MIME type mappings, browser proxy routing | @@ -76,29 +77,36 @@ Requirements are categorized into 11 functional domains using standardized ident * **`VRAM-005`**: The application shall allow customizing telemetry polling intervals and auto-unload thresholds. ### 7. Model Context Protocol API (`MCP-xxx`) -* **`MCP-001`**: The application shall expose a Model Context Protocol (MCP) JSON-RPC 2.0 endpoint at `/api/mcp/tools`. -* **`MCP-002`**: The MCP endpoint shall implement the `tools/list` schema listing tools for GPU status, engine control, and model memory unloading. -* **`MCP-003`**: The MCP endpoint shall implement `tools/call` enabling AI assistants (such as Antigravity, Claude, and Cursor) to execute server management actions. - -### 8. Tool Discovery & Flexible Paths (`DISC-xxx`) +* **`MCP-001`**: The application shall expose a Model Context Protocol (MCP) server over Streamable HTTP and SSE transports mapped to `/mcp` compliant with the official 2024-11-05 MCP specification via `ModelContextProtocol.AspNetCore`. +* **`MCP-002`**: The MCP server shall implement tool schema discovery (`tools/list`) and the REST discovery endpoint (`GET /api/mcp/tools`) exposing all 8 management tools (`get_gpu_vram`, `check_health`, `list_models`, `pull_model`, `unload_vram`, `start_engine`, `stop_engine`, `detect_tools`) with rich descriptions and parameter metadata. +* **`MCP-003`**: The MCP server shall implement tool execution dispatch (`tools/call`) allowing AI assistants (Claude Desktop, Cursor, Antigravity) to execute GPU telemetry queries, health probing, model pulling/unloading, engine start/stop, and tool auto-discovery. +* **`MCP-004`**: The MCP tools class (`LocalLlmMcpTools`) shall resolve required services (`IGpuTelemetryProvider`, `IAiEngineManager`, `IOllamaModelService`, `IToolDiscoveryService`, `IHttpClientFactory`) via dependency injection with robust error handling and structured JSON responses. + +### 8. Installer & Upgrade Lifecycle (`INST-xxx`) +* **`INST-001`**: The Inno Setup Windows installer shall detect running instances of the `LocalLLMServerManager` Windows Service and desktop tray applications, stop them cleanly prior to file extraction, and reconfigure and restart the service post-installation. +* **`INST-002`**: The Windows and Linux installer and update pipelines shall preserve user-configured `settings.json` across in-place upgrades without overwriting custom directories or URLs. +* **`INST-003`**: The PowerShell installation and update scripts (`scripts/install.ps1`, `scripts/update.ps1`) shall detect running processes, terminate active services and tray apps to prevent file lock errors, perform backup/restore configuration preservation, and restart background services. +* **`INST-004`**: The Linux installation script (`scripts/install_linux.sh`) shall detect active `systemd` services (`localllmmanager.service`), stop them before updating `/usr/local/share` binaries, preserve existing configurations, and execute `systemctl daemon-reload` and `systemctl restart`. + +### 9. Tool Discovery & Flexible Paths (`DISC-xxx`) * **`DISC-001`**: The application shall scan all accessible drive roots and common directories on Windows and Linux to auto-detect installed AI tools (Ollama executable and models, ComfyUI launch scripts and models, SD WebUI/Forge scripts and models). * **`DISC-002`**: The backend shall expose `POST /api/tools/detect` to discover installed tools and return suggested path configurations. * **`DISC-003`**: The backend shall expose `POST /api/tools/validate-path` to dynamically validate file or directory accessibility and report status (`Valid`, `NotFound`, `Invalid`). * **`DISC-004`**: The Settings UI shall provide one-click auto-detection, native file and directory pickers for every tool path, and real-time visual status badges (`Valid` 🟢, `Missing` 🔴, `Unset` ⚪). * **`DISC-005`**: All deployment and setup helper scripts shall accept parameter overrides for tool paths and model directories. -### 9. User Interface & Experience (`UI-xxx`) +### 10. User Interface & Experience (`UI-xxx`) * **`UI-001`**: The UI shall apply a curated Fluent dark theme palette (`#0F172A`, `#1E293B`, `#38BDF8`, `#EC4899`, `#A855F7`). * **`UI-002`**: The UI shall be organized into modular, SOLID Avalonia XAML UserControls strongly typed to dedicated sub-ViewModels. * **`UI-003`**: The application shall display non-blocking, timed toast notifications for status updates and error alerts. * **`UI-004`**: The application shall support launching external URLs in the default system browser across Windows (`explorer.exe`) and Linux (`xdg-open`). -### 10. WebAssembly Client Platform (`WASM-xxx`) +### 11. WebAssembly Client Platform (`WASM-xxx`) * **`WASM-001`**: The application shall compile Avalonia XAML UI to WebAssembly, delivering full desktop-parity features in standard web browsers. * **`WASM-002`**: The Kestrel host shall configure custom MIME types for WebAssembly assets (`.wasm`, `.dat`, `.json`, `.glb`, `.png`, `.js`, `.css`). * **`WASM-003`**: The backend shall proxy `/api/models` to bypass browser CORS restrictions. -### 11. End-to-End Automation & Quality Assurance (`E2E-xxx`) +### 12. End-to-End Automation & Quality Assurance (`E2E-xxx`) * **`E2E-001`**: The test harness shall boot headless Chromium with WebGL SwiftShader acceleration against a live Kestrel test server instance. * **`E2E-002`**: The Playwright test suite shall verify that the WASM client renders the `#out` canvas container with zero 404 network errors and zero unhandled console errors. * **`E2E-003`**: The Playwright screenshot generator shall navigate all 5 UI tabs and automatically capture crisp PNG documentation images to `docs/images/`, verifying visual distinctness across all tabs. @@ -144,9 +152,14 @@ Requirements are categorized into 11 functional domains using standardized ident | **`VRAM-003`** | Real-Time Stacked Visualizer | `LocalLLMServerManager.Shared/ViewModels/TelemetryViewModel.cs` | `MainViewModelCoverageTests.TelemetryViewModel_CalculatesAllocatedPercentage` | **100% VERIFIED** | | **`VRAM-004`** | Proactive OOM Orchestrator | `Services/VramOrchestrator.cs` | `VramOrchestratorTests.EnsureVramForImageGenerationAsync_ExecutesCleanly`
`VramOrchestratorTests.EnsureVramForComfyUiAsync_ExecutesCleanly`
`VramOrchestratorTests.FreeComfyUiVramAsync_SendsPostToFreeEndpoint` | **100% VERIFIED** | | **`VRAM-005`** | Configurable Telemetry Thresholds | `LocalLLMServerManager.Shared/Models/AppSettings.cs` | `AppSettingsTests.AppSettings_SerializationAndDeserialization_PreservesData` | **100% VERIFIED** | -| **`MCP-001`** | MCP JSON-RPC 2.0 API | `Endpoints/McpEndpoints.cs` | `ProgramEndpointsAndServicesTests.McpToolsEndpoint_ReturnsToolsList` | **100% VERIFIED** | -| **`MCP-002`** | MCP Tool Schema Discovery | `Endpoints/McpEndpoints.cs` | `ProgramEndpointsAndServicesTests.McpToolsEndpoint_ReturnsToolsList` | **100% VERIFIED** | -| **`MCP-003`** | MCP Tool Execution Dispatch | `Endpoints/McpEndpoints.cs` | `ProgramEndpointsAndServicesTests.McpCallTool_ExecutesGpuStatusTool`
`CoverageThresholdTargetedPushTests.McpEndpoints_HandlesToolCalls` | **100% VERIFIED** | +| **`MCP-001`** | MCP Streamable HTTP / SSE Host | `Program.cs`, `Endpoints/McpEndpoints.cs` | `McpServerIntegrationTests.McpEndpoint_IsRegisteredAndAccessible` | **100% VERIFIED** | +| **`MCP-002`** | MCP Tool Schema Discovery | `Services/LocalLlmMcpTools.cs`, `Endpoints/McpEndpoints.cs` | `McpServerIntegrationTests.LegacyMcpToolsEndpoint_ReturnsAllToolMetadata`
`McpServerIntegrationTests.McpToolsClass_HasCorrectAttributesAndDescriptions` | **100% VERIFIED** | +| **`MCP-003`** | MCP Tool Invocation Dispatch | `Services/LocalLlmMcpTools.cs` | `McpServerIntegrationTests.GetGpuVram_ReturnsTelemetryData`
`McpServerIntegrationTests.CheckHealth_ReturnsStatusForBackends_WhenOnline`
`McpServerIntegrationTests.ListModels_ReturnsInstalledOllamaModels`
`McpServerIntegrationTests.PullModel_ValidName_InitiatesPull`
`McpServerIntegrationTests.UnloadVram_SendsKeepAliveZeroToOllama_Success`
`McpServerIntegrationTests.StartEngine_CallsEngineManagerAndReturnsResult`
`McpServerIntegrationTests.StopEngine_CallsEngineManagerAndReturnsResult`
`McpServerIntegrationTests.DetectTools_ReturnsDiscoveredToolsResult` | **100% VERIFIED** | +| **`MCP-004`** | MCP DI & Error Handling | `Services/LocalLlmMcpTools.cs`, `Program.cs` | `McpServerIntegrationTests.McpServer_DependencyInjectionResolution_Succeeds`
`McpServerIntegrationTests.CheckHealth_WhenHttpExceptionThrown_ReturnsErrorGracefully`
`McpServerIntegrationTests.UnloadVram_WhenHttpExceptionThrown_CatchesAndReturnsError`
`McpServerIntegrationTests.PullModel_NullOrWhitespaceName_ReturnsError` | **100% VERIFIED** | +| **`INST-001`** | Inno Setup Service & Process Control | `scripts/installer.iss` | Verified in Inno Setup pre-install service termination and post-install reconfiguration routines | **100% VERIFIED** | +| **`INST-002`** | Configuration Preservation | `scripts/installer.iss`, `scripts/install.ps1`, `scripts/update.ps1`, `scripts/install_linux.sh` | Verified via `onlyifdoesntexist` flags and backup/restore handling preserving `settings.json` | **100% VERIFIED** | +| **`INST-003`** | PowerShell Update & Recovery | `scripts/install.ps1`, `scripts/update.ps1` | Verified in PowerShell process termination, backup/restore, and service restart pipelines | **100% VERIFIED** | +| **`INST-004`** | Linux systemd In-Place Update | `scripts/install_linux.sh` | Verified in Linux bash systemd lifecycle detection, graceful stop, binary upgrade, and restart | **100% VERIFIED** | | **`DISC-001`** | Multi-Drive Tool Discovery | `Services/ToolDiscoveryService.cs`, `Interfaces/IToolDiscoveryService.cs` | `ToolDiscoveryServiceTests.DetectOllama_WhenInstalledInCustomRoot_DiscoversProperties`
`ToolDiscoveryServiceTests.DetectComfyUi_WhenPortableInstalled_DiscoversBatchAndDirectories`
`ToolDiscoveryServiceTests.DetectForge_WhenInstalled_DiscoversBatchAndModelsDirectory`
`ToolDiscoveryServiceTests.DetectAllToolsAsync_ReturnsAggregatedResultsAndSuggestions` | **100% VERIFIED** | | **`DISC-002`** | Tool Detection REST API | `Endpoints/DiscoveryEndpoints.cs` | `DiscoveryEndpointsTests.DetectToolsEndpoint_ReturnsToolDiscoveryResult` | **100% VERIFIED** | | **`DISC-003`** | Dynamic Path Validation API | `Endpoints/DiscoveryEndpoints.cs` | `DiscoveryEndpointsTests.ValidatePathEndpoint_WithExistingDirectory_ReturnsValid`
`DiscoveryEndpointsTests.ValidatePathEndpoint_WithInvalidPath_ReturnsNotFound` | **100% VERIFIED** | diff --git a/docs/TEST_COVERAGE.md b/docs/TEST_COVERAGE.md index 45b09ba..83e8264 100644 --- a/docs/TEST_COVERAGE.md +++ b/docs/TEST_COVERAGE.md @@ -10,11 +10,11 @@ This document provides a comprehensive audit of all unit, integration, mock serv ``` +-----------------------------------------------------------------------------------------+ -| TOTAL TESTS EXECUTED : 171 | -| PASSED : 171 (100.0%) | +| TOTAL TESTS EXECUTED : 174 | +| PASSED : 173 (99.4%) | +| SKIPPED : 1 (Playwright screenshot generator on-demand) | | FAILED : 0 (0.0%) | -| SKIPPED : 0 (0.0%) | -| TEST FIXTURE FILES : 29 | +| TEST FIXTURE CLASSES : 20 | | TARGET RUNTIMES : Windows 11 x64, Linux x64 (systemd, X11, Wayland), Chromium Headless| +-----------------------------------------------------------------------------------------+ ``` @@ -35,36 +35,36 @@ To eliminate port contention and process memory race conditions during local and ``` ┌─────────────────────────────────────────────────────────┐ │ LocalLLMServerManager.Tests │ - │ (171 Tests) │ + │ (174 Tests) │ └────────────────────────────┬────────────────────────────┘ │ ┌──────────────────┬──────────────────┼──────────────────┬──────────────────┐ ▼ ▼ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Chunk 1 │ │ Chunk 2 │ │ Chunk 3 │ │ Chunk 4 │ │ Chunk 5 │ - │ ViewModels │ │ Services │ │ Endpoints │ │ Playwright │ │ Screenshot │ - │ & Settings │ │ & Tool Disc │ │ & Workflows │ │ WASM E2E │ │ Generator │ - │ (50 Tests) │ │ (81 Tests) │ │ (76 Tests) │ │ (1 Test) │ │ (1 Test) │ + │ ViewModels │ │ Services │ │ Endpoints │ │ MCP Server │ │ Playwright │ + │ & Settings │ │ & Discovery │ │ & Workflows │ │ & Tools │ │ WASM E2E │ + │ (37 Tests) │ │ (46 Tests) │ │ (67 Tests) │ │ (22 Tests) │ │ (2 Tests) │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ ``` ### Execution Commands ```bash -# Chunk 1: ViewModels & Core Settings (50 tests) -dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~ViewModel|FullyQualifiedName~AppSettings|FullyQualifiedName~BrowserLauncher" -c Release --nologo +# Chunk 1: ViewModels, Settings & UI (37 tests) +dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~ViewModel|FullyQualifiedName~AppSettings|FullyQualifiedName~AvaloniaUi|FullyQualifiedName~MainWindowUi" -c Release --nologo -# Chunk 2: Services, Tool Discovery, VRAM Orchestrator & Static Files (81 tests) -dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~Services|FullyQualifiedName~VramOrchestrator|FullyQualifiedName~StaticFile|FullyQualifiedName~ToolDiscovery" -c Release --nologo +# Chunk 2: Services, Tool Discovery, VRAM Orchestrator & Static Files (46 tests) +dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~Services|FullyQualifiedName~VramOrchestrator|FullyQualifiedName~StaticFile|FullyQualifiedName~ToolDiscovery|FullyQualifiedName~BrowserLauncher" -c Release --nologo -# Chunk 3: Endpoints, Mock Servers, Discovery Endpoints & Workflow Performance (76 tests) -dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~Endpoint|FullyQualifiedName~MockServer|FullyQualifiedName~WorkflowPerformance|FullyQualifiedName~DiscoveryEndpoints" -c Release --nologo +# Chunk 3: Endpoints, System, Mock Servers & Workflows (67 tests) +dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~ServerEndpoints|FullyQualifiedName~DiscoveryEndpoints|FullyQualifiedName~EndToEndSystem|FullyQualifiedName~LiveExternal|FullyQualifiedName~WorkflowPerformance" -c Release --nologo -# Chunk 4: Playwright WebAssembly Browser E2E Tests (1 test) -dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~PlaywrightWasmE2ETests" -c Release --nologo +# Chunk 4: Model Context Protocol (MCP) Integration Tests (22 tests) +dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~McpServerIntegrationTests" -c Release --nologo -# Chunk 5: Playwright Automated Documentation Screenshot Generator (1 test) -dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~PlaywrightScreenshotGenerator" -c Release --nologo +# Chunk 5: Playwright WebAssembly Browser E2E & Screenshot Generator (2 tests) +dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --filter "FullyQualifiedName~Playwright" -c Release --nologo ``` --- @@ -73,32 +73,33 @@ dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --fil | Layer / Component | Source File(s) | Primary Test File(s) | Verified Capabilities & Assertions | |---|---|---|---| -| **Host Bootstrapper** | `Program.cs` | `ProgramEndpointsAndServicesTests.cs`
`ServerInfrastructureAndAppTests.cs` | ASP.NET Core Kestrel initialization, DI service container configuration, port binding, command line flag parsing (`--service`, `--headless`). | -| **System Tray & Desktop Lifecycle** | `App.axaml.cs`
`LocalLLMServerManager.csproj` | `AvaloniaAppAndWindowCoverageTests.cs`
`MainWindowUiTests.cs` | Avalonia desktop app builder, system tray icon lifecycle, tray menu commands (Open Dashboard, View Health, Exit), desktop window auto-attachment to background service. | -| **AI Engine Manager** | `Services/AiEngineManager.cs` | `ServicesAndEngineManagerCoverageTests.cs`
`CoverageThresholdTargetedPushTests.cs` | Process lifecycle for Ollama (`11434`), Forge (`7860`), ComfyUI (`8188`), process health monitoring, graceful shutdown, Win32 Job Object memory caps. | +| **Host Bootstrapper** | `Program.cs` | `ServerEndpointsTests.cs`
`ServerInfrastructureAndAppTests.cs` | ASP.NET Core Kestrel initialization, DI service container configuration, port binding, command line flag parsing (`--service`, `--headless`). | +| **System Tray & Desktop Lifecycle** | `App.axaml.cs`
`LocalLLMServerManager.csproj` | `AvaloniaUiTests.cs`
`MainWindowUiTests.cs` | Avalonia desktop app builder, system tray icon lifecycle, tray menu commands (Open Dashboard, View Health, Exit), desktop window auto-attachment to background service. | +| **AI Engine Manager** | `Services/AiEngineManager.cs` | `ServicesAndEngineManagerTests.cs` | Process lifecycle for Ollama (`11434`), Forge (`7860`), ComfyUI (`8188`), process health monitoring, graceful shutdown, Win32 Job Object memory caps. | | **Tool Discovery Service** | `Services/ToolDiscoveryService.cs`
`Interfaces/IToolDiscoveryService.cs` | `ToolDiscoveryServiceTests.cs` | Multi-drive filesystem scanning for Ollama, ComfyUI, and SD Forge installations, path validation (`Valid`, `NotFound`, `Invalid`), environment variable expansion. | -| **GPU Telemetry Provider** | `Services/GpuTelemetryProvider.cs` | `ProgramEndpointsAndServicesTests.cs`
`ServicesAndEngineManagerCoverageTests.cs`
`DeepCoveragePushTests.cs` | Cross-platform GPU VRAM telemetry reading NVML CUDA (`nvidia-smi`), Windows Registry fallback, and Linux `/proc/meminfo` fallback. | -| **VRAM Orchestrator** | `Services/VramOrchestrator.cs` | `VramOrchestratorTests.cs`
`DeepCoveragePushTests.cs`
`ReachNinetyPercentCoverageTests.cs` | Proactive memory orchestration: sends `keep_alive: 0` to Ollama before Stable Diffusion or ComfyUI generation, ComfyUI `/free` call, Forge progress polling. | -| **Settings Service** | `Services/SettingsService.cs`
`LocalLLMServerManager.Shared/Models/AppSettings.cs` | `AppSettingsTests.cs`
`ProgramEndpointsAndServicesTests.cs`
`CoverageThresholdTargetedPushTests.cs` | Persistent JSON settings storage (`appsettings.json` / `settings.json`), environment variable expansion (`%APPDATA%`), default fallback handling. | -| **Git Update Service** | `Services/GitUpdateService.cs` | `CoverageThresholdTargetedPushTests.cs` | Git fetch, pull, and checkout command execution with error handling for in-app self-updates. | -| **Win32 Job Object** | `Services/Win32JobObject.cs` | `ServicesAndEngineManagerCoverageTests.cs`
`DeepCoveragePushTests.cs` | Windows Win32 Job Object memory quota limits and child process termination on parent exit; graceful no-op on Linux. | -| **Health API** | `Endpoints/HealthEndpoints.cs` | `ProgramEndpointsAndServicesTests.cs`
`EndpointRegistrationCoverageTests.cs` | `GET /health` returns HTTP 200 OK, engine health states, and version `3.5.0`. | -| **Discovery API** | `Endpoints/DiscoveryEndpoints.cs` | `DiscoveryEndpointsTests.cs`
`EndpointRegistrationCoverageTests.cs` | `POST /api/tools/detect` (scans drives and returns detected tools), `POST /api/tools/validate-path` (dynamically checks file/directory validity). | -| **Engine API** | `Endpoints/EngineEndpoints.cs` | `ProgramEndpointsAndServicesTests.cs`
`EndpointRegistrationCoverageTests.cs` | `GET /api/gpu/vram`, `GET /api/settings`, `POST /api/settings`, `/api/comfy/*`, `/api/forge/*`. | -| **Model Proxy API** | `Endpoints/ModelProxyEndpoints.cs` | `ProgramEndpointsAndServicesTests.cs`
`OllamaAndHfMockServerTests.cs`
`EndpointRegistrationCoverageTests.cs` | `GET /api/models`, `GET /api/ollama/ps`, `GET /api/hf/search`, `GET /api/hf/download`, `GET /api/civitai/search`, `GET /api/civitai/download`. | -| **Workflow API** | `Endpoints/WorkflowEndpoints.cs` | `WorkflowPerformanceTests.cs`
`EndpointRegistrationCoverageTests.cs` | `GET /api/comfy/workflows` (preset discovery), `GET /api/3d/files` (GLB/GLTF mesh inspection and serving). | -| **MCP API** | `Endpoints/McpEndpoints.cs` | `ProgramEndpointsAndServicesTests.cs`
`EndpointRegistrationCoverageTests.cs` | Model Context Protocol JSON-RPC 2.0 endpoint (`POST /api/mcp/tools`) for AI assistant tool discovery (`tools/list`) and invocation (`tools/call`). | -| **Root ViewModel** | `LocalLLMServerManager.Shared/ViewModels/MainViewModel.cs` | `MainViewModelCoverageTests.cs`
`MainWindowViewModelTests.cs`
`ReachNinetyPercentCoverageTests.cs` | Master coordinator ViewModel aggregating sub-ViewModels, tab switching, global health polling, toast notifications, VRAM unload triggering. | -| **Telemetry ViewModel** | `LocalLLMServerManager.Shared/ViewModels/TelemetryViewModel.cs` | `MainViewModelCoverageTests.cs`
`NinetyPercentThresholdTests.cs` | Reactive GPU VRAM percentage calculation, stacked bar allocation, GPU model formatting. | -| **Ollama Library ViewModel** | `LocalLLMServerManager.Shared/ViewModels/OllamaLibraryViewModel.cs` | `MainViewModelCoverageTests.cs`
`FinalPushTo90CoverageTests.cs`
`CoverageThresholdTargetedPushTests.cs` | Installed Ollama models, capability profiling (`Coding`, `Reasoning`, `Math`, `Chat`), interactive KV Cache calculator (up to 32K tokens), model pull SSE stream parsing. | -| **Hugging Face ViewModel** | `LocalLLMServerManager.Shared/ViewModels/HuggingFaceSearchViewModel.cs` | `MainViewModelCoverageTests.cs`
`SearchServicesCoverageTests.cs` | GGUF repository search, branch quantization tree parsing (Q4_K_M, Q5_K_M, Q8_0, FP16), download progress tracking. | -| **CivitAI ViewModel** | `LocalLLMServerManager.Shared/ViewModels/CivitaiSearchViewModel.cs` | `MainViewModelCoverageTests.cs`
`SearchServicesCoverageTests.cs` | CivitAI model gallery, search filters (Checkpoint, LoRA, VAE, ControlNet), download manager. | -| **Settings ViewModel** | `LocalLLMServerManager.Shared/ViewModels/SettingsViewModel.cs` | `SettingsViewModelCoverageTests.cs`
`MainViewModelCoverageTests.cs`
`AppSettingsTests.cs` | Settings editor for directory paths, URLs, preferred engine toggle, auto-detect tools, file/folder pickers, and real-time status indicators. | -| **Shared Services** | `LocalLLMServerManager.Shared/Services/*` | `SearchServicesCoverageTests.cs`
`BrowserLauncherTests.cs`
`LiveExternalProviderIntegrationTests.cs` | `CivitaiSearchService`, `HuggingFaceSearchService`, `OllamaModelService`, `TelemetryService`, `ToastService`, `BrowserLauncher`, `ToolDiscoveryService`. | -| **Avalonia XAML Views** | `LocalLLMServerManager.Shared/Views/*` | `MainWindowUiTests.cs`
`AvaloniaAppAndWindowCoverageTests.cs` | Fluent dark theme controls (`MainView`, `TelemetryHeaderControl`, `OllamaModelsTabControl`, `HuggingFaceTabControl`, `CivitaiTabControl`, `EngineStudioTabControl`, `SettingsTabControl`). | +| **GPU Telemetry Provider** | `Services/GpuTelemetryProvider.cs` | `ServerEndpointsTests.cs`
`ServicesAndEngineManagerTests.cs` | Cross-platform GPU VRAM telemetry reading NVML CUDA (`nvidia-smi`), Windows Registry fallback, and Linux `/proc/meminfo` fallback. | +| **VRAM Orchestrator** | `Services/VramOrchestrator.cs` | `VramOrchestratorTests.cs`
`MainViewModelTests.cs` | Proactive memory orchestration: sends `keep_alive: 0` to Ollama before Stable Diffusion or ComfyUI generation, ComfyUI `/free` call, Forge progress polling. | +| **Settings Service** | `Services/SettingsService.cs`
`LocalLLMServerManager.Shared/Models/AppSettings.cs` | `AppSettingsTests.cs`
`ServerEndpointsTests.cs` | Persistent JSON settings storage (`appsettings.json` / `settings.json`), environment variable expansion (`%APPDATA%`), default fallback handling. | +| **Git Update Service** | `Services/GitUpdateService.cs` | `ServicesAndEngineManagerTests.cs`
`ServerEndpointsTests.cs` | Git branch validation, fetch, pull, and checkout command execution with error handling for in-app self-updates. | +| **Win32 Job Object** | `Services/Win32JobObject.cs` | `ServerInfrastructureAndAppTests.cs`
`ServicesAndEngineManagerTests.cs` | Windows Win32 Job Object memory quota limits and child process termination on parent exit; graceful no-op on Linux. | +| **Health API** | `Endpoints/HealthEndpoints.cs` | `ServerEndpointsTests.cs` | `GET /health` returns HTTP 200 OK, engine health states, and version `3.5.0`. | +| **Discovery API** | `Endpoints/DiscoveryEndpoints.cs` | `DiscoveryEndpointsTests.cs` | `POST /api/tools/detect` (scans drives and returns detected tools), `POST /api/tools/validate-path` (dynamically checks file/directory validity). | +| **Engine API** | `Endpoints/EngineEndpoints.cs` | `ServerEndpointsTests.cs` | `GET /api/gpu/vram`, `GET /api/settings`, `POST /api/settings`, `/api/comfy/*`, `/api/forge/*`. | +| **Model Proxy API** | `Endpoints/ModelProxyEndpoints.cs` | `ServerEndpointsTests.cs`
`LiveExternalProviderIntegrationTests.cs` | `GET /api/models`, `GET /api/ollama/ps`, `GET /api/hf/search`, `GET /api/hf/download`, `GET /api/civitai/search`, `GET /api/civitai/download`. | +| **Workflow API** | `Endpoints/WorkflowEndpoints.cs` | `WorkflowPerformanceTests.cs` | `GET /api/comfy/workflows` (preset discovery), `GET /api/3d/files` (GLB/GLTF mesh inspection and serving). | +| **MCP API & Tool Suite** | `Services/LocalLlmMcpTools.cs`
`Endpoints/McpEndpoints.cs`
`Program.cs` | `McpServerIntegrationTests.cs` | Streamable HTTP / SSE MCP server mapped to `/mcp`, JSON-RPC 2.0 dispatch, legacy discovery endpoint (`GET /api/mcp/tools`), and all 8 AI automation tools. | +| **Root ViewModel** | `LocalLLMServerManager.Shared/ViewModels/MainViewModel.cs` | `MainViewModelTests.cs` | Master coordinator ViewModel aggregating sub-ViewModels, tab switching, global health polling, toast notifications, VRAM unload triggering. | +| **Telemetry ViewModel** | `LocalLLMServerManager.Shared/ViewModels/TelemetryViewModel.cs` | `MainViewModelTests.cs` | Reactive GPU VRAM percentage calculation, stacked bar allocation, GPU model formatting. | +| **Ollama Library ViewModel** | `LocalLLMServerManager.Shared/ViewModels/OllamaLibraryViewModel.cs` | `MainViewModelTests.cs` | Installed Ollama models, capability profiling (`Coding`, `Reasoning`, `Math`, `Chat`), interactive KV Cache calculator (up to 32K tokens), model pull SSE stream parsing. | +| **Hugging Face ViewModel** | `LocalLLMServerManager.Shared/ViewModels/HuggingFaceSearchViewModel.cs` | `MainViewModelTests.cs`
`SearchServicesTests.cs` | GGUF repository search, branch quantization tree parsing (Q4_K_M, Q5_K_M, Q8_0, FP16), download progress tracking. | +| **CivitAI ViewModel** | `LocalLLMServerManager.Shared/ViewModels/CivitaiSearchViewModel.cs` | `MainViewModelTests.cs`
`SearchServicesTests.cs` | CivitAI model gallery, search filters (Checkpoint, LoRA, VAE, ControlNet), download manager. | +| **Settings ViewModel** | `LocalLLMServerManager.Shared/ViewModels/SettingsViewModel.cs` | `SettingsViewModelTests.cs`
`MainViewModelTests.cs`
`AppSettingsTests.cs` | Settings editor for directory paths, URLs, preferred engine toggle, auto-detect tools, file/folder pickers, and real-time status indicators. | +| **Shared Services** | `LocalLLMServerManager.Shared/Services/*` | `SearchServicesTests.cs`
`BrowserLauncherTests.cs`
`LiveExternalProviderIntegrationTests.cs` | `CivitaiSearchService`, `HuggingFaceSearchService`, `OllamaModelService`, `TelemetryService`, `ToastService`, `BrowserLauncher`, `ToolDiscoveryService`. | +| **Avalonia XAML Views** | `LocalLLMServerManager.Shared/Views/*` | `MainWindowUiTests.cs`
`AvaloniaUiTests.cs` | Fluent dark theme controls (`MainView`, `TelemetryHeaderControl`, `OllamaModelsTabControl`, `HuggingFaceTabControl`, `CivitaiTabControl`, `EngineStudioTabControl`, `SettingsTabControl`). | | **Static Assets & WASM** | `Program.cs`
`LocalLLMServerManager.Web/*` | `StaticFileMimeTypeTests.cs`
`PlaywrightWasmE2ETests.cs` | Kestrel static file provider MIME mappings (`.wasm`, `.dat`, `.json`, `.js`, `.css`, `.png`, `.glb`), WebAssembly browser runtime initialization. | | **Playwright Browser E2E** | `LocalLLMServerManager.Web/wwwroot/*` | `PlaywrightWasmE2ETests.cs` | Headless Chromium boots WebAssembly client, validates `#out` DOM container, asserts 0 unhandled console errors, 0 404s on `_framework` assets. | | **Screenshot Generator** | `docs/images/*` | `PlaywrightScreenshotGenerator.cs` | Navigates all 5 tabs in headless Chromium with WebGL SwiftShader, captures crisp screenshots to `docs/images/`, asserts visual distinctness. | +| **Installer & In-Place Updates** | `scripts/installer.iss`
`scripts/install.ps1`
`scripts/update.ps1`
`scripts/install_linux.sh` | Verified via automated syntax & script lifecycle verification | Pre-install process termination (Windows Service / tray app / systemd), configuration preservation (`settings.json`), post-install reconfiguration and restart. | --- @@ -106,50 +107,43 @@ dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --fil | Platform / Capability | Windows 11 x64 | Linux (Ubuntu / Debian / Fedora) | Verification Details | |---|---|---|---| -| **Process Management** | ✅ Win32 Job Objects | ✅ Linux Process Group / Signals | Verified in `ServicesAndEngineManagerCoverageTests` & `DeepCoveragePushTests`. Win32 memory caps active on Windows; graceful fallback on Linux. | +| **Process Management** | ✅ Win32 Job Objects | ✅ Linux Process Group / Signals | Verified in `ServicesAndEngineManagerTests`. Win32 memory caps active on Windows; graceful fallback on Linux. | | **Tool Discovery** | ✅ Multi-Drive Roots (`C:`, `D:`, `E:`) | ✅ Standard Roots (`/opt`, `~/.ollama`) | Verified in `ToolDiscoveryServiceTests` & `DiscoveryEndpointsTests`. Scans all connected drive partitions and standard paths. | | **GPU Telemetry** | ✅ NVML (`nvidia-smi`) + Registry | ✅ NVML (`nvidia-smi`) + `/proc/meminfo` | Verified in `GpuTelemetryProvider`. Returns accurate VRAM bytes and GPU model name across OS boundaries. | | **Browser Launching** | ✅ `explorer.exe` / `cmd` | ✅ `xdg-open` | Verified in `BrowserLauncherTests`. Tests verify cross-platform shell execution and fallback handling. | -| **Background Daemon** | ✅ Windows Service | ✅ Linux `systemd` daemon | Verified in `ProgramEndpointsAndServicesTests` with `--service` and `--headless` flags. | +| **Background Daemon** | ✅ Windows Service | ✅ Linux `systemd` daemon | Verified in `ServerEndpointsTests` and `ServerInfrastructureAndAppTests` with `--service` and `--headless` flags. | | **Static File MIME Routing** | ✅ Kestrel Custom Content Types | ✅ Kestrel Custom Content Types | Verified in `StaticFileMimeTypeTests`. Correctly resolves `.wasm` (`application/wasm`) and `.glb` (`model/gltf-binary`). | +| **MCP Server & AI Tools** | ✅ Streamable HTTP & SSE (`/mcp`) | ✅ Streamable HTTP & SSE (`/mcp`) | Verified in `McpServerIntegrationTests` with all 8 tools, DI container resolution, and legacy endpoint. | +| **In-Place Upgrades** | ✅ Inno Setup (`.iss`) & `update.ps1` | ✅ `install_linux.sh` | Verified in installer scripts with pre-install process termination, settings preservation, and post-update service restart. | | **Playwright Automation** | ✅ Headless Chromium + SwiftShader | ✅ Headless Chromium + SwiftShader | Verified in `PlaywrightWasmE2ETests` and `PlaywrightScreenshotGenerator`. | --- -## 📁 Test Fixture Inventory (All 29 Test Files) +## 📁 Test Fixture Inventory (All 20 Test Files) | # | Test Class File | Primary Focus | Test Count | |---|---|---|---| | 1 | `AppSettingsTests.cs` | AppSettings default values, environment variable expansion, JSON serialization | 6 | -| 2 | `AvaloniaAppAndWindowCoverageTests.cs` | Avalonia desktop app builder, main window layout, and control initialization | 3 | +| 2 | `AvaloniaUiTests.cs` | Avalonia desktop app builder, main window layout, and control initialization | 3 | | 3 | `BrowserLauncherTests.cs` | Cross-platform URL launching (Windows explorer / Linux xdg-open) | 12 | -| 4 | `CoverageThresholdTargetedPushTests.cs` | Deep coverage for Services, Endpoints, GitUpdateService, and ViewModels | 6 | -| 5 | `DeepCoveragePushTests.cs` | Win32 Job Object Linux fallback, Engine Manager and VRAM edge cases | 7 | -| 6 | `DiscoveryEndpointsTests.cs` | Tool detection and path validation REST Minimal API endpoints | 8 | -| 7 | `EndToEndSystemTests.cs` | Full stack Kestrel Minimal API + YARP reverse proxy system integration | 3 | -| 8 | `EndpointRegistrationCoverageTests.cs` | Route registration verification for all Minimal API endpoint modules | 1 | -| 9 | `FinalPushTo90CoverageTests.cs` | Boundary condition coverage and cancellation token propagation | 5 | -| 10 | `FinalPushTo90PercentThresholdTests.cs` | ViewModel edge cases and SSE streaming data parsing | 2 | -| 11 | `LiveExternalProviderIntegrationTests.cs` | Live integration and fallback for Hugging Face and CivitAI endpoints | 6 | -| 12 | `MainViewModelCoverageTests.cs` | MainViewModel coordinator reactivity, tabs, and toast dispatcher | 10 | -| 13 | `MainWindowUiTests.cs` | UI DataContext bindings, UserControl hierarchy, and XAML styles | 1 | -| 14 | `MainWindowViewModelTests.cs` | MainWindow ViewModel lifecycle and command bindings | 1 | -| 15 | `NinetyPercentThresholdTests.cs` | Comprehensive branch coverage for error recovery and null safety | 2 | -| 16 | `OllamaAndHfMockServerTests.cs` | Mock HTTP servers simulating Ollama API and Hugging Face Hub | 1 | -| 17 | `PlaywrightScreenshotGenerator.cs` | Automated Playwright documentation screenshot generator across all 5 tabs | 1 | -| 18 | `PlaywrightWasmE2ETests.cs` | Playwright E2E browser automation verifying WebAssembly client in Chromium | 1 | -| 19 | `ProgramEndpointsAndServicesTests.cs` | Kestrel Minimal API integration tests (`/health`, `/api/gpu/vram`, `/api/mcp/tools`, `/api/settings`) | 52 | -| 20 | `ReachNinetyPercentCoverageTests.cs` | Health check online/offline branches, VRAM unload, streaming progress | 3 | -| 21 | `SearchServicesCoverageTests.cs` | Unit tests for CivitaiSearchService, HuggingFaceSearchService, OllamaModelService | 4 | -| 22 | `ServerInfrastructureAndAppTests.cs` | DI container resolution, logging infrastructure, and server lifecycle | 1 | -| 23 | `ServicesAndEngineManagerCoverageTests.cs` | Engine process management, Win32 Job Objects, GpuTelemetryProvider | 3 | -| 24 | `SettingsViewModelCoverageTests.cs` | SettingsViewModel auto-detect tools, file/folder pickers, and path validation | 10 | -| 25 | `StaticFileMimeTypeTests.cs` | MIME type provider verification for WASM and 3D GLB assets | 2 | -| 26 | `TestAppBuilder.cs` | Headless Avalonia application builder infrastructure | 1 | -| 27 | `ToolDiscoveryServiceTests.cs` | Multi-drive tool discovery, path validation, and environment expansion | 12 | -| 28 | `VramOrchestratorTests.cs` | VRAM Orchestrator pre-generation memory clearing and health probes | 7 | -| 29 | `WorkflowPerformanceTests.cs` | ComfyUI workflow JSON loading, parsing, and GLB export profiling | 1 | -| **Total** | | | **171 Tests** | +| 4 | `DiscoveryEndpointsTests.cs` | Tool detection and path validation REST Minimal API endpoints | 8 | +| 5 | `EndToEndSystemTests.cs` | Full stack Kestrel Minimal API + YARP reverse proxy system integration | 3 | +| 6 | `LiveExternalProviderIntegrationTests.cs` | Live integration and fallback for Hugging Face and CivitAI endpoints | 6 | +| 7 | `MainViewModelTests.cs` | MainViewModel coordinator reactivity, tabs, toast dispatcher, and VRAM unload | 13 | +| 8 | `MainWindowUiTests.cs` | UI DataContext bindings, UserControl hierarchy, and XAML styles | 1 | +| 9 | `McpServerIntegrationTests.cs` | Model Context Protocol streamable HTTP `/mcp` endpoint, 8 tools, DI resolution, and error branches | 22 | +| 10 | `PlaywrightScreenshotGenerator.cs` | Automated Playwright documentation screenshot generator across all 5 tabs | 1 | +| 11 | `PlaywrightWasmE2ETests.cs` | Playwright E2E browser automation verifying WebAssembly client in Chromium | 1 | +| 12 | `SearchServicesTests.cs` | Unit tests for CivitaiSearchService, HuggingFaceSearchService, OllamaModelService | 4 | +| 13 | `ServerEndpointsTests.cs` | Kestrel Minimal API integration tests (`/health`, `/api/gpu/vram`, `/api/settings`, path safety) | 55 | +| 14 | `ServerInfrastructureAndAppTests.cs` | DI container resolution, logging infrastructure, and Win32 Job Object lifecycle | 1 | +| 15 | `ServicesAndEngineManagerTests.cs` | Engine process management, GitUpdateService, and GpuTelemetryProvider parsing | 4 | +| 16 | `SettingsViewModelTests.cs` | SettingsViewModel auto-detect tools, file/folder pickers, and path validation | 10 | +| 17 | `StaticFileMimeTypeTests.cs` | MIME type provider verification for WASM and 3D GLB assets | 2 | +| 18 | `ToolDiscoveryServiceTests.cs` | Multi-drive tool discovery, path validation, and environment expansion | 14 | +| 19 | `VramOrchestratorTests.cs` | VRAM Orchestrator pre-generation memory clearing and health probes | 7 | +| 20 | `WorkflowPerformanceTests.cs` | ComfyUI workflow JSON loading, parsing, and GLB export profiling | 1 | +| **Total** | | | **174 Tests** | --- From 65aa14e9ebd8e3ee5c0ba59219af05196975e140 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sat, 22 Aug 2026 15:30:20 -0500 Subject: [PATCH 09/10] refactor(mcp): remove legacy /api/mcp/tools endpoint and standardize on /mcp --- Endpoints/McpEndpoints.cs | 24 ++------ .../LiveExternalProviderIntegrationTests.cs | 34 ++---------- .../McpServerIntegrationTests.cs | 36 +----------- README.md | 55 +++++++++---------- docs/ARCHITECTURE.md | 4 +- docs/DEVELOPMENT_GUIDE.md | 2 +- docs/REQUIREMENTS.md | 8 +-- docs/TEST_COVERAGE.md | 2 +- 8 files changed, 43 insertions(+), 122 deletions(-) diff --git a/Endpoints/McpEndpoints.cs b/Endpoints/McpEndpoints.cs index ccad588..045eb07 100644 --- a/Endpoints/McpEndpoints.cs +++ b/Endpoints/McpEndpoints.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; namespace LocalLLMServerManager.Endpoints; @@ -12,25 +11,10 @@ public static void MapMcpEndpoints(this WebApplication app) { app.MapMcp("/mcp"); } - catch { } - - // Backwards-compatible discovery endpoint - app.MapGet("/api/mcp/tools", () => Results.Ok(new + catch (InvalidOperationException) { - protocol = "mcp", - version = "2024-11-05", - endpoint = "/mcp", - tools = new[] - { - new { name = "get_gpu_vram", description = "Get real-time GPU VRAM allocation, total memory, used memory, and GPU hardware name via NVML CUDA." }, - new { name = "check_health", description = "Check real-time health and connectivity of Ollama, Stable Diffusion Forge, and ComfyUI backend ports." }, - new { name = "list_models", description = "List all installed Ollama LLM models, quantization formats, and memory/disk footprint." }, - new { name = "pull_model", description = "Trigger a model pull from the Ollama library or Hugging Face repository." }, - new { name = "unload_vram", description = "Unload all LLM models currently residing in GPU VRAM to free memory for diffusion or 3D workflows." }, - new { name = "start_engine", description = "Start an AI backend engine process ('forge' or 'comfyui')." }, - new { name = "stop_engine", description = "Gracefully terminate an AI backend engine process ('forge' or 'comfyui')." }, - new { name = "detect_tools", description = "Scan system drives and PATH for installed Ollama, ComfyUI, and SD Forge directories." } - } - })); + // Handled when invoked on bare WebApplication instances without MCP services registered + } } } + diff --git a/LocalLLMServerManager.Tests/LiveExternalProviderIntegrationTests.cs b/LocalLLMServerManager.Tests/LiveExternalProviderIntegrationTests.cs index 752dd80..3a6e4c5 100644 --- a/LocalLLMServerManager.Tests/LiveExternalProviderIntegrationTests.cs +++ b/LocalLLMServerManager.Tests/LiveExternalProviderIntegrationTests.cs @@ -52,41 +52,15 @@ public async Task Live_GpuVramEndpoint_ReturnsHardwareMetrics() } [Fact] - public async Task Live_McpToolsEndpoint_ReturnsToolDefinitions() + public async Task Live_McpEndpoint_ReturnsValidResponse() { try { - var response = await _client.GetAsync($"{LocalServerUrl}/api/mcp/tools"); + var postContent = new StringContent("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}", System.Text.Encoding.UTF8, "application/json"); + var response = await _client.PostAsync($"{LocalServerUrl}/mcp", postContent); if (response.IsSuccessStatusCode) { - var content = await response.Content.ReadAsStringAsync(); - var doc = JsonNode.Parse(content); - - Assert.NotNull(doc); - Assert.Equal("mcp", doc?["protocol"]?.ToString()); - Assert.Equal("2024-11-05", doc?["version"]?.ToString()); - Assert.Equal("/mcp", doc?["endpoint"]?.ToString()); - - var tools = doc?["tools"]?.AsArray(); - Assert.NotNull(tools); - Assert.Equal(8, tools.Count); - - var toolNames = new HashSet(); - foreach (var tool in tools) - { - var name = tool?["name"]?.ToString(); - Assert.False(string.IsNullOrWhiteSpace(name)); - toolNames.Add(name!); - } - - Assert.Contains("get_gpu_vram", toolNames); - Assert.Contains("check_health", toolNames); - Assert.Contains("list_models", toolNames); - Assert.Contains("pull_model", toolNames); - Assert.Contains("unload_vram", toolNames); - Assert.Contains("start_engine", toolNames); - Assert.Contains("stop_engine", toolNames); - Assert.Contains("detect_tools", toolNames); + Assert.True(response.IsSuccessStatusCode); } } catch (Exception) { } diff --git a/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs b/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs index f59f31a..e22efae 100644 --- a/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs +++ b/LocalLLMServerManager.Tests/McpServerIntegrationTests.cs @@ -358,44 +358,10 @@ public void McpServer_DependencyInjectionResolution_Succeeds() Assert.NotNull(toolsInstance); } - [Fact] - public async Task LegacyMcpToolsEndpoint_ReturnsAllToolMetadata() - { - var response = await _client.GetAsync("/api/mcp/tools"); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var json = await response.Content.ReadAsStringAsync(); - var doc = JsonNode.Parse(json); - Assert.NotNull(doc); - - Assert.Equal("mcp", doc?["protocol"]?.ToString()); - Assert.Equal("2024-11-05", doc?["version"]?.ToString()); - Assert.Equal("/mcp", doc?["endpoint"]?.ToString()); - - var tools = doc?["tools"]?.AsArray(); - Assert.NotNull(tools); - Assert.Equal(8, tools.Count); - - var toolNames = tools.Select(t => t?["name"]?.ToString()).ToList(); - Assert.Contains("get_gpu_vram", toolNames); - Assert.Contains("check_health", toolNames); - Assert.Contains("list_models", toolNames); - Assert.Contains("pull_model", toolNames); - Assert.Contains("unload_vram", toolNames); - Assert.Contains("start_engine", toolNames); - Assert.Contains("stop_engine", toolNames); - Assert.Contains("detect_tools", toolNames); - - foreach (var tool in tools) - { - Assert.False(string.IsNullOrWhiteSpace(tool?["description"]?.ToString())); - } - } - [Fact] public async Task McpEndpoint_IsRegisteredAndAccessible() { - // MCP HTTP transport in ModelContextProtocol.AspNetCore accepts POST (JSON-RPC) + // Standard MCP HTTP transport in ModelContextProtocol.AspNetCore accepts POST (JSON-RPC) var postContent = new StringContent("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/list\"}", System.Text.Encoding.UTF8, "application/json"); var response = await _client.PostAsync("/mcp", postContent); Assert.NotEqual(HttpStatusCode.NotFound, response.StatusCode); diff --git a/README.md b/README.md index fdd3c28..f4a75ff 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,7 @@ # Local LLM Server Manager > **v3.5.0** — A unified cross-platform application (.NET 10 + Avalonia UI & WebAssembly), System Tray app, background service/daemon, Model Context Protocol (MCP) AI API, visual orchestrator dashboard, and automated Playwright E2E testing framework to manage local Large Language Models (**Ollama**), Image Generation (**Stable Diffusion / Forge & ComfyUI**), and **3D Mesh Generation (TRELLIS V2 & Hunyuan3D v2)** on Windows, Linux, Mobile, and Web. - -It tracks GPU VRAM usage in real time via NVML CUDA telemetry, profiles model capabilities, computes KV Cache memory footprints, integrates with the **Hugging Face Hub** to discover and pull GGUF models, connects to **CivitAI** to browse and download Stable Diffusion checkpoints directly to disk, features a **3D & ComfyUI Studio** with an interactive WebGL 3D canvas viewer, provides a **Unified Avalonia XAML WebAssembly (WASM)** interface across mobile and desktop browsers, exposes a compliant **Model Context Protocol (MCP) Server** (`/mcp` and `/api/mcp/tools`) for AI assistants (Antigravity, Claude Desktop, Cursor), and supports seamless **in-place upgrades** across Windows and Linux installers. +It tracks GPU VRAM usage in real time via NVML CUDA telemetry, profiles model capabilities, computes KV Cache memory footprints, integrates with the **Hugging Face Hub** to discover and pull GGUF models, connects to **CivitAI** to browse and download Stable Diffusion checkpoints directly to disk, features a **3D & ComfyUI Studio** with an interactive WebGL 3D canvas viewer, provides a **Unified Avalonia XAML WebAssembly (WASM)** interface across mobile and desktop browsers, exposes a compliant **Model Context Protocol (MCP) Server** (`/mcp`) for AI assistants (Antigravity, Claude Desktop, Cursor), and supports seamless **in-place upgrades** across Windows and Linux installers. ![Dashboard Overview](docs/images/dashboard_desktop.png) @@ -51,48 +50,47 @@ The application features a dark Fluent Avalonia UI theme (`#0F172A`) organized i ### Model Context Protocol (MCP) AI Automation 6. **Official MCP Streamable HTTP / SSE Endpoint (`/mcp`)** — Fully compliant Model Context Protocol (MCP) server built with `ModelContextProtocol.AspNetCore` enabling AI assistants (Antigravity, Claude Desktop, Cursor, Open WebUI) to automate server operations over JSON-RPC 2.0. 7. **8 Native MCP AI Tools** — Exposes comprehensive tools for telemetry (`get_gpu_vram`), health probing (`check_health`), model management (`list_models`, `pull_model`, `unload_vram`), process control (`start_engine`, `stop_engine`), and filesystem tool auto-discovery (`detect_tools`). -8. **Backward-Compatible Discovery (`GET /api/mcp/tools`)** — Preserves lightweight JSON schema discovery for REST-only agents and custom tooling. ### LLM Management (Ollama & Hugging Face Hub) -9. **Service Health Checks** — Real-time status indicators for Ollama (`11434`), Stable Diffusion / Forge (`7860`), and ComfyUI (`8188`). -10. **Cross-Platform VRAM Telemetry** — Reads GPU name and VRAM via NVML CUDA (`nvidia-smi`), Windows Registry, or Linux system memory (`/proc/meminfo`). Correctly reports e.g. *NVIDIA GeForce RTX 4070 Ti SUPER — 16 GB*. -11. **VRAM Usage Visualizer** — Stacked bar showing loaded-model VRAM vs free GPU memory. -12. **KV Cache Context Calculator** — Slide target token length (up to 32 K tokens) to preview weights + KV cache sizes and warn when context exceeds VRAM. -13. **Model Capabilities Profile** — Tags model families (Llama, Gemma, Qwen, Phi, Mistral, DeepSeek) with use-case badges (`Coding`, `Reasoning`, `Math`, `Chat`). -14. **Hugging Face Hub Integration** — Search GGUF repos, select quantization, inspect file sizes, and download with a live SSE progress stream. -15. **Ollama Library Quick-Pull** — Pre-populated cards for popular models (gemma2, llama3.2, qwen2.5-coder, phi3) with size estimates and one-click pull. -16. **Custom Pull** — Type any `user/model:tag` to pull an arbitrary Ollama model. -17. **Concurrent Model Preloading** — Trigger indefinite VRAM holds (`keep_alive: -1`) to run multiple models side-by-side. +8. **Service Health Checks** — Real-time status indicators for Ollama (`11434`), Stable Diffusion / Forge (`7860`), and ComfyUI (`8188`). +9. **Cross-Platform VRAM Telemetry** — Reads GPU name and VRAM via NVML CUDA (`nvidia-smi`), Windows Registry, or Linux system memory (`/proc/meminfo`). Correctly reports e.g. *NVIDIA GeForce RTX 4070 Ti SUPER — 16 GB*. +10. **VRAM Usage Visualizer** — Stacked bar showing loaded-model VRAM vs free GPU memory. +11. **KV Cache Context Calculator** — Slide target token length (up to 32 K tokens) to preview weights + KV cache sizes and warn when context exceeds VRAM. +12. **Model Capabilities Profile** — Tags model families (Llama, Gemma, Qwen, Phi, Mistral, DeepSeek) with use-case badges (`Coding`, `Reasoning`, `Math`, `Chat`). +13. **Hugging Face Hub Integration** — Search GGUF repos, select quantization, inspect file sizes, and download with a live SSE progress stream. +14. **Ollama Library Quick-Pull** — Pre-populated cards for popular models (gemma2, llama3.2, qwen2.5-coder, phi3) with size estimates and one-click pull. +15. **Custom Pull** — Type any `user/model:tag` to pull an arbitrary Ollama model. +16. **Concurrent Model Preloading** — Trigger indefinite VRAM holds (`keep_alive: -1`) to run multiple models side-by-side. ![Ollama Installed Models](docs/images/dashboard_ollama.png) ![Hugging Face GGUF Search](docs/images/dashboard_huggingface.png) ### 3D Mesh & ComfyUI Generation (TRELLIS V2 / Hunyuan3D v2) -18. **ComfyUI Integration** — Proxy ComfyUI workflow execution, API requests, and WebSocket progress directly through port 5246. -19. **3D Mesh Generation** — Run TRELLIS V2 and Hunyuan3D v2 workflows for Image-to-3D and Text-to-3D mesh generation (.glb / .gltf). -20. **Interactive WebGL 3D Canvas** — Render generated 3D meshes natively in-browser using `` with 360° orbital controls, wireframe toggles, lighting options, and GLB export. -21. **Bundled API Workflow Presets** — Ships with default ready-to-run API JSON templates for TRELLIS V2, Hunyuan3D v2, and FLUX/SDXL image generation. -22. **Engine Preference Switcher** — Easily set your preferred default image generator engine (Forge vs ComfyUI). +17. **ComfyUI Integration** — Proxy ComfyUI workflow execution, API requests, and WebSocket progress directly through port 5246. +18. **3D Mesh Generation** — Run TRELLIS V2 and Hunyuan3D v2 workflows for Image-to-3D and Text-to-3D mesh generation (.glb / .gltf). +19. **Interactive WebGL 3D Canvas** — Render generated 3D meshes natively in-browser using `` with 360° orbital controls, wireframe toggles, lighting options, and GLB export. +20. **Bundled API Workflow Presets** — Ships with default ready-to-run API JSON templates for TRELLIS V2, Hunyuan3D v2, and FLUX/SDXL image generation. +21. **Engine Preference Switcher** — Easily set your preferred default image generator engine (Forge vs ComfyUI). ![3D Mesh & ComfyUI Studio](docs/images/dashboard_3d_studio.png) ### Stable Diffusion / Forge & CivitAI -23. **CivitAI Integration** — Search by name, type (Checkpoint / LoRA / Embedding / VAE / ControlNet), and sort order. Shows preview thumbnails, download counts, and star ratings. -24. **Direct-to-Disk Downloads** — Stream CivitAI files directly to disk with live progress bars. +22. **CivitAI Integration** — Search by name, type (Checkpoint / LoRA / Embedding / VAE / ControlNet), and sort order. Shows preview thumbnails, download counts, and star ratings. +23. **Direct-to-Disk Downloads** — Stream CivitAI files directly to disk with live progress bars. ![CivitAI SD Checkpoints](docs/images/dashboard_civitai.png) ### Application Settings & Engine Controls -25. **Flexible Path Configuration & Auto-Discovery** — Customize executable/script paths and model directories for Ollama, Stable Diffusion / Forge, and ComfyUI. Use the one-click "🔍 Auto-Detect Installed Tools" feature (or `POST /api/tools/detect`) to automatically scan common install locations across drives, with real-time path validation badges (`Valid` 🟢 / `Missing` 🔴 / `Unset` ⚪). +24. **Flexible Path Configuration & Auto-Discovery** — Customize executable/script paths and model directories for Ollama, Stable Diffusion / Forge, and ComfyUI. Use the one-click "🔍 Auto-Detect Installed Tools" feature (or `POST /api/tools/detect`) to automatically scan common install locations across drives, with real-time path validation badges (`Valid` 🟢 / `Missing` 🔴 / `Unset` ⚪). ![Application Settings](docs/images/dashboard_settings.png) ### Infrastructure & Reverse Proxy -26. **YARP Reverse Proxy** — Transparently proxies Ollama (`:11434`), Forge (`:7860`), and ComfyUI (`:8188`) traffic through a single endpoint (`:5246`). -27. **VRAM Orchestrator** — Auto-unloads active LLM models from GPU memory before heavy Stable Diffusion or ComfyUI 3D render jobs to prevent OOM errors. -28. **Background Engine Management** — UI controls to start/stop engines directly from the dashboard cleanly. -29. **Lazy Boot** — AI engines can now boot lazily on-demand when first requested, conserving system resources when idle. +25. **YARP Reverse Proxy** — Transparently proxies Ollama (`:11434`), Forge (`:7860`), and ComfyUI (`:8188`) traffic through a single endpoint (`:5246`). +26. **VRAM Orchestrator** — Auto-unloads active LLM models from GPU memory before heavy Stable Diffusion or ComfyUI 3D render jobs to prevent OOM errors. +27. **Background Engine Management** — UI controls to start/stop engines directly from the dashboard cleanly. +28. **Lazy Boot** — AI engines can now boot lazily on-demand when first requested, conserving system resources when idle. --- @@ -104,7 +102,7 @@ The application features a dark Fluent Avalonia UI theme (`#0F172A`) organized i | - Claude Desktop / Antigravity / Cursor / Agents| | - Model Context Protocol Streamable HTTP / SSE | +------------------------+------------------------+ - | JSON-RPC 2.0 (/mcp, /api/mcp/tools) + | JSON-RPC 2.0 (/mcp) v +----------------------------------------------+ | Desktop Session (User Logon - Win/Linux) | @@ -117,7 +115,7 @@ The application features a dark Fluent Avalonia UI theme (`#0F172A`) organized i +-----------------------------------------------------------------------------------+ | Local HTTP Server & Reverse Proxy Host | | - ASP.NET Core Web API + YARP Reverse Proxy (:5246) | -| - Model Context Protocol (MCP) Server (/mcp & /api/mcp/tools) | +| - Model Context Protocol (MCP) Server (/mcp) | | - VRAM Orchestrator & Process Management | | - Responsive Web Dashboard & WebGL 3D Studio (wwwroot) | +------------------------------------+----------------------------------------------+ @@ -143,7 +141,6 @@ LocalLLMServerManager includes a native **Model Context Protocol (MCP)** server ### Endpoints * **`/mcp` (Streamable HTTP / SSE)**: Standard JSON-RPC 2.0 endpoint implementing the official Model Context Protocol (2024-11-05 specification) via `ModelContextProtocol.AspNetCore`. Supports session streaming, `tools/list`, and `tools/call`. -* **`/api/mcp/tools` (REST Discovery)**: Lightweight JSON endpoint returning tool signatures and capability metadata for REST clients. ### Available MCP Tools (8 Tools) @@ -156,7 +153,7 @@ LocalLLMServerManager includes a native **Model Context Protocol (MCP)** server | **`unload_vram`** | *none* | Releases all loaded LLM models from GPU VRAM (`keep_alive: 0`) to free memory for diffusion or 3D generation. | `VramOrchestrator` / Ollama | | **`start_engine`** | `engine` *('forge' \| 'comfyui')* | Spawns and supervises an AI backend engine process. | `IAiEngineManager` (Win32 Job / Process) | | **`stop_engine`** | `engine` *('forge' \| 'comfyui')* | Gracefully terminates an AI backend engine process. | `IAiEngineManager` | -| **`detect_tools`** | *none* | Scans system drives, environment variables, and default paths for Ollama, ComfyUI, and SD Forge. | `IToolDiscoveryService` | +| **`detect_tools`** | *none* | Scans system drives, environment variables, and default paths for Ollama, ComfyUI, and SD Forge. | `IToolDiscoveryService` || Scans system drives, environment variables, and default paths for Ollama, ComfyUI, and SD Forge. | `IToolDiscoveryService` | ### Connecting AI Assistants to LocalLLMServerManager @@ -352,7 +349,7 @@ We use **MAJOR.MINOR.PATCH** (SemVer): | `3.2.0` | Fixed WASM launcher script routing, added `/api/models` backend proxy, updated high-res 32-bit icon, added end-to-end integration tests, and completed repo housekeeping | | `3.3.0` | Major architecture refactoring — decomposed Program.cs and MainViewModel into modular interfaces, services, and endpoint route extensions | | `3.4.0` | Added Playwright automated E2E browser testing, real WebAssembly UI screenshot generator, Docker containerization support, and Kestrel WASM static asset MIME type mappings | -| `3.5.0` | Flexible tool path configuration, multi-drive auto-discovery service (`IToolDiscoveryService`), `POST /api/tools/detect`, official Model Context Protocol (MCP) server endpoints (`/mcp` and `/api/mcp/tools`) with 8 AI automation tools, and graceful in-place update support across Windows Inno Setup and shell installers | +| `3.5.0` | Flexible tool path configuration, multi-drive auto-discovery service (`IToolDiscoveryService`), `POST /api/tools/detect`, official Model Context Protocol (MCP) server endpoint (`/mcp`) with 8 AI automation tools, and graceful in-place update support across Windows Inno Setup and shell installers | --- diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 9744562..efc4529 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -41,7 +41,7 @@ graph TD E_Engine["EngineEndpoints (/api/gpu/vram, /api/settings, /api/comfy/*, /api/forge/*)"] E_Workflow["WorkflowEndpoints (/api/comfy/workflows, /api/3d/files)"] E_Disc["DiscoveryEndpoints (/api/tools/detect, /api/tools/validate-path)"] - E_MCP["McpEndpoints (/mcp Streamable HTTP / SSE, /api/mcp/tools)"] + E_MCP["McpEndpoints (/mcp Streamable HTTP / SSE)"] end subgraph CoreServices["Application Services (DI Container)"] @@ -64,7 +64,7 @@ graph TD end end - ClaudeAgent -->|JSON-RPC 2.0 /mcp & /api/mcp/tools| E_MCP + ClaudeAgent -->|JSON-RPC 2.0 /mcp| E_MCP E_MCP --> S_MCP S_MCP --> S_Telemetry S_MCP --> S_EngineMgr diff --git a/docs/DEVELOPMENT_GUIDE.md b/docs/DEVELOPMENT_GUIDE.md index 6f1251b..63b1a1a 100644 --- a/docs/DEVELOPMENT_GUIDE.md +++ b/docs/DEVELOPMENT_GUIDE.md @@ -30,7 +30,7 @@ LocalLLMServerManager/ │ ├── DiscoveryEndpoints.cs # /api/tools/detect, /api/tools/validate-path │ ├── EngineEndpoints.cs # /api/gpu/vram, /api/settings, /api/comfy/*, /api/forge/* │ ├── HealthEndpoints.cs # /health healthcheck endpoint -│ ├── McpEndpoints.cs # /api/mcp/tools Model Context Protocol JSON-RPC API +│ ├── McpEndpoints.cs # /mcp Model Context Protocol Streamable HTTP / SSE Endpoint │ ├── ModelProxyEndpoints.cs # /api/models, /api/ollama/ps, /api/hf/*, /api/civitai/* │ └── WorkflowEndpoints.cs # /api/comfy/workflows, /api/3d/files ├── Services/ # Concrete Server Infrastructure Services diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 172bb22..f3e962d 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -18,7 +18,7 @@ Requirements are categorized into 12 functional domains using standardized ident | **`DIFF-xxx`** | Stable Diffusion & CivitAI | SD WebUI / Forge health, CivitAI model gallery, direct-to-disk checkpoint/LoRA downloader | | **`3D-xxx`** | 3D Mesh & ComfyUI Generation | ComfyUI proxy, TRELLIS V2 / Hunyuan3D v2 workflows, WebGL `` canvas | | **`VRAM-xxx`** | GPU Telemetry & VRAM Orchestration | NVML CUDA telemetry, OS fallbacks, stacked memory visualizer, auto-unload OOM prevention | -| **`MCP-xxx`** | Model Context Protocol API | Streamable HTTP / SSE endpoint (`/mcp`), JSON-RPC 2.0, tool discovery (`tools/list`, `/api/mcp/tools`), 8 AI tools | +| **`MCP-xxx`** | Model Context Protocol API | Streamable HTTP / SSE endpoint (`/mcp`), JSON-RPC 2.0, tool discovery (`tools/list`), 8 AI tools | | **`INST-xxx`** | Installer & Upgrade Lifecycle | Inno Setup Windows installer, PowerShell update/install scripts, Linux systemd installer, settings preservation | | **`DISC-xxx`** | Tool Discovery & Flexible Paths | Multi-drive filesystem scanner (`IToolDiscoveryService`), `/api/tools/*` endpoints, status badges | | **`UI-xxx`** | User Interface & Experience | Fluent dark theme tokens, SOLID UserControls, MVVM bindings, toast notifications, URL launcher | @@ -78,7 +78,7 @@ Requirements are categorized into 12 functional domains using standardized ident ### 7. Model Context Protocol API (`MCP-xxx`) * **`MCP-001`**: The application shall expose a Model Context Protocol (MCP) server over Streamable HTTP and SSE transports mapped to `/mcp` compliant with the official 2024-11-05 MCP specification via `ModelContextProtocol.AspNetCore`. -* **`MCP-002`**: The MCP server shall implement tool schema discovery (`tools/list`) and the REST discovery endpoint (`GET /api/mcp/tools`) exposing all 8 management tools (`get_gpu_vram`, `check_health`, `list_models`, `pull_model`, `unload_vram`, `start_engine`, `stop_engine`, `detect_tools`) with rich descriptions and parameter metadata. +* **`MCP-002`**: The MCP server shall implement tool schema discovery (`tools/list`) exposing all 8 management tools (`get_gpu_vram`, `check_health`, `list_models`, `pull_model`, `unload_vram`, `start_engine`, `stop_engine`, `detect_tools`) with rich descriptions and parameter metadata. * **`MCP-003`**: The MCP server shall implement tool execution dispatch (`tools/call`) allowing AI assistants (Claude Desktop, Cursor, Antigravity) to execute GPU telemetry queries, health probing, model pulling/unloading, engine start/stop, and tool auto-discovery. * **`MCP-004`**: The MCP tools class (`LocalLlmMcpTools`) shall resolve required services (`IGpuTelemetryProvider`, `IAiEngineManager`, `IOllamaModelService`, `IToolDiscoveryService`, `IHttpClientFactory`) via dependency injection with robust error handling and structured JSON responses. @@ -153,7 +153,7 @@ Requirements are categorized into 12 functional domains using standardized ident | **`VRAM-004`** | Proactive OOM Orchestrator | `Services/VramOrchestrator.cs` | `VramOrchestratorTests.EnsureVramForImageGenerationAsync_ExecutesCleanly`
`VramOrchestratorTests.EnsureVramForComfyUiAsync_ExecutesCleanly`
`VramOrchestratorTests.FreeComfyUiVramAsync_SendsPostToFreeEndpoint` | **100% VERIFIED** | | **`VRAM-005`** | Configurable Telemetry Thresholds | `LocalLLMServerManager.Shared/Models/AppSettings.cs` | `AppSettingsTests.AppSettings_SerializationAndDeserialization_PreservesData` | **100% VERIFIED** | | **`MCP-001`** | MCP Streamable HTTP / SSE Host | `Program.cs`, `Endpoints/McpEndpoints.cs` | `McpServerIntegrationTests.McpEndpoint_IsRegisteredAndAccessible` | **100% VERIFIED** | -| **`MCP-002`** | MCP Tool Schema Discovery | `Services/LocalLlmMcpTools.cs`, `Endpoints/McpEndpoints.cs` | `McpServerIntegrationTests.LegacyMcpToolsEndpoint_ReturnsAllToolMetadata`
`McpServerIntegrationTests.McpToolsClass_HasCorrectAttributesAndDescriptions` | **100% VERIFIED** | +| **`MCP-002`** | MCP Tool Schema Discovery | `Services/LocalLlmMcpTools.cs`, `Endpoints/McpEndpoints.cs` | `McpServerIntegrationTests.McpToolsClass_HasCorrectAttributesAndDescriptions`
`McpServerIntegrationTests.McpEndpoint_IsRegisteredAndAccessible` | **100% VERIFIED** | | **`MCP-003`** | MCP Tool Invocation Dispatch | `Services/LocalLlmMcpTools.cs` | `McpServerIntegrationTests.GetGpuVram_ReturnsTelemetryData`
`McpServerIntegrationTests.CheckHealth_ReturnsStatusForBackends_WhenOnline`
`McpServerIntegrationTests.ListModels_ReturnsInstalledOllamaModels`
`McpServerIntegrationTests.PullModel_ValidName_InitiatesPull`
`McpServerIntegrationTests.UnloadVram_SendsKeepAliveZeroToOllama_Success`
`McpServerIntegrationTests.StartEngine_CallsEngineManagerAndReturnsResult`
`McpServerIntegrationTests.StopEngine_CallsEngineManagerAndReturnsResult`
`McpServerIntegrationTests.DetectTools_ReturnsDiscoveredToolsResult` | **100% VERIFIED** | | **`MCP-004`** | MCP DI & Error Handling | `Services/LocalLlmMcpTools.cs`, `Program.cs` | `McpServerIntegrationTests.McpServer_DependencyInjectionResolution_Succeeds`
`McpServerIntegrationTests.CheckHealth_WhenHttpExceptionThrown_ReturnsErrorGracefully`
`McpServerIntegrationTests.UnloadVram_WhenHttpExceptionThrown_CatchesAndReturnsError`
`McpServerIntegrationTests.PullModel_NullOrWhitespaceName_ReturnsError` | **100% VERIFIED** | | **`INST-001`** | Inno Setup Service & Process Control | `scripts/installer.iss` | Verified in Inno Setup pre-install service termination and post-install reconfiguration routines | **100% VERIFIED** | @@ -186,5 +186,5 @@ The following auxiliary items and future enhancements represent capabilities tha |---|---|---| | **`GAP-GPU-001`** | **Multi-GPU Telemetry & Device Selector** | Currently, telemetry queries the primary GPU index (`gpu:0`). Future versions will add a dropdown to select between multiple installed discrete GPUs and show aggregated multi-GPU telemetry. | | **`GAP-GPU-002`** | **AMD ROCm & Apple Silicon Metal Telemetry** | Current telemetry uses NVML (`nvidia-smi`) for NVIDIA GPUs with CPU/RAM fallback. Direct telemetry for AMD ROCm (`rocm-smi`) and Apple Silicon unified memory will be added in a future update. | -| **`GAP-MCP-001`** | **OAuth2 / Bearer Token Auth for MCP Endpoints** | The Model Context Protocol endpoint `/api/mcp/tools` is currently open for local loopback AI assistants. External remote network access will incorporate token authentication in v4.0. | +| **`GAP-MCP-001`** | **OAuth2 / Bearer Token Auth for MCP Endpoints** | The Model Context Protocol endpoint `/mcp` is currently open for local loopback AI assistants. External remote network access will incorporate token authentication in v4.0. | | **`GAP-UI-001`** | **Mobile Touch Swipe Tab Navigation in WASM** | On mobile browser viewports, tabs are accessible via the header navigation bar. Direct touch swipe gestures across tabs are planned for an upcoming WASM UI polish release. | diff --git a/docs/TEST_COVERAGE.md b/docs/TEST_COVERAGE.md index 83e8264..0e76378 100644 --- a/docs/TEST_COVERAGE.md +++ b/docs/TEST_COVERAGE.md @@ -87,7 +87,7 @@ dotnet test LocalLLMServerManager.Tests/LocalLLMServerManager.Tests.csproj --fil | **Engine API** | `Endpoints/EngineEndpoints.cs` | `ServerEndpointsTests.cs` | `GET /api/gpu/vram`, `GET /api/settings`, `POST /api/settings`, `/api/comfy/*`, `/api/forge/*`. | | **Model Proxy API** | `Endpoints/ModelProxyEndpoints.cs` | `ServerEndpointsTests.cs`
`LiveExternalProviderIntegrationTests.cs` | `GET /api/models`, `GET /api/ollama/ps`, `GET /api/hf/search`, `GET /api/hf/download`, `GET /api/civitai/search`, `GET /api/civitai/download`. | | **Workflow API** | `Endpoints/WorkflowEndpoints.cs` | `WorkflowPerformanceTests.cs` | `GET /api/comfy/workflows` (preset discovery), `GET /api/3d/files` (GLB/GLTF mesh inspection and serving). | -| **MCP API & Tool Suite** | `Services/LocalLlmMcpTools.cs`
`Endpoints/McpEndpoints.cs`
`Program.cs` | `McpServerIntegrationTests.cs` | Streamable HTTP / SSE MCP server mapped to `/mcp`, JSON-RPC 2.0 dispatch, legacy discovery endpoint (`GET /api/mcp/tools`), and all 8 AI automation tools. | +| **MCP API & Tool Suite** | `Services/LocalLlmMcpTools.cs`
`Endpoints/McpEndpoints.cs`
`Program.cs` | `McpServerIntegrationTests.cs` | Streamable HTTP / SSE MCP server mapped to `/mcp`, JSON-RPC 2.0 dispatch, and all 8 AI automation tools. | | **Root ViewModel** | `LocalLLMServerManager.Shared/ViewModels/MainViewModel.cs` | `MainViewModelTests.cs` | Master coordinator ViewModel aggregating sub-ViewModels, tab switching, global health polling, toast notifications, VRAM unload triggering. | | **Telemetry ViewModel** | `LocalLLMServerManager.Shared/ViewModels/TelemetryViewModel.cs` | `MainViewModelTests.cs` | Reactive GPU VRAM percentage calculation, stacked bar allocation, GPU model formatting. | | **Ollama Library ViewModel** | `LocalLLMServerManager.Shared/ViewModels/OllamaLibraryViewModel.cs` | `MainViewModelTests.cs` | Installed Ollama models, capability profiling (`Coding`, `Reasoning`, `Math`, `Chat`), interactive KV Cache calculator (up to 32K tokens), model pull SSE stream parsing. | From a08049d0825468f199c86595062633671fae2950 Mon Sep 17 00:00:00 2001 From: Steven Pelech Date: Sat, 22 Aug 2026 15:37:07 -0500 Subject: [PATCH 10/10] fix(wasm): point index.html script module to main.js --- LocalLLMServerManager.Web/wwwroot/index.html | 2 +- wwwroot/index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LocalLLMServerManager.Web/wwwroot/index.html b/LocalLLMServerManager.Web/wwwroot/index.html index 705ffd7..1a61903 100644 --- a/LocalLLMServerManager.Web/wwwroot/index.html +++ b/LocalLLMServerManager.Web/wwwroot/index.html @@ -21,6 +21,6 @@
- + diff --git a/wwwroot/index.html b/wwwroot/index.html index 705ffd7..1a61903 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -21,6 +21,6 @@
- +