feat(mcp): Dabit.Mcp 초기 공개 (리네임 + colors 수정 + scroll 검증)

- refactor: DabitChe.Mcp → Dabit.Mcp (namespace, csproj, exe 모두)
- fix: display_message colors를 쉼표 구분 문자열로 변경 (ModelContextProtocol 1.3.0 배열 바인딩 버그 우회)
- docs: scroll 4방향(left/right/up/down) 실기 검증 완료 (DIBD600T V3.6.1)

20 도구 (읽기 4 + 콘텐츠 2 + 설정 14), stdio 전송, Windows exe.
실기 검증: scroll/fill_screen 8색/display_message 글자별색 모두 정상.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
insulee
2026-06-02 11:20:49 +09:00
parent 94dd74bd5c
commit 640c9a3380
109 changed files with 7146 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
using DibdProtocol.Transport;
namespace Dabit.Mcp.Config;
/// <summary>
/// MCP 서버가 붙을 전광판 연결 설정. 환경변수(DABIT_*)에서 읽는다.
/// Claude Desktop이 claude_desktop_config.json의 env로 주입하는 것을 기본으로 한다
/// (서버 프로세스의 작업 디렉터리가 불확실하므로 파일보다 환경변수가 견고).
/// </summary>
public sealed record McpConnectionSettings
{
public TransportType ConnectType { get; init; } = TransportType.TcpClient;
public string PortName { get; init; } = "COM1";
public int BaudRate { get; init; } = 9600;
public string Host { get; init; } = "192.168.0.201";
public int Port { get; init; } = 5000;
public int ResponseLatencyMs { get; init; } = 1000;
/// <summary>환경변수에서 설정을 읽는다. 누락 항목은 기본값.</summary>
public static McpConnectionSettings FromEnvironment()
{
static string? Env(string key) => Environment.GetEnvironmentVariable(key);
var transport = (Env("DABIT_TRANSPORT") ?? "").Trim().ToLowerInvariant() switch
{
"serial" => TransportType.Serial,
"tcp" or "tcpclient" => TransportType.TcpClient,
_ => TransportType.TcpClient,
};
return new McpConnectionSettings
{
ConnectType = transport,
PortName = Env("DABIT_COM") ?? "COM1",
BaudRate = int.TryParse(Env("DABIT_BAUD"), out var b) ? b : 9600,
Host = Env("DABIT_HOST") ?? "192.168.0.201",
Port = int.TryParse(Env("DABIT_PORT"), out var p) ? p : 5000,
ResponseLatencyMs = int.TryParse(Env("DABIT_RESPONSE_LATENCY"), out var r) ? r : 1000,
};
}
/// <summary>사람이 읽을 연결 요약 문자열.</summary>
public string Describe() => ConnectType == TransportType.Serial
? $"Serial {PortName} @ {BaudRate}bps (timeout {ResponseLatencyMs}ms)"
: $"TcpClient {Host}:{Port} (timeout {ResponseLatencyMs}ms)";
}

View File

@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ModelContextProtocol" Version="1.3.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.0" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DibdProtocol\DibdProtocol.csproj" />
</ItemGroup>
</Project>

40
Dabit.Mcp/Program.cs Normal file
View File

@@ -0,0 +1,40 @@
using System.Text;
using Dabit.Mcp.Config;
using Dabit.Mcp.Services;
using DibdProtocol.Transport;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
// EUC-KR 인코딩 등록 — PacketBuilder/PacketParser가 의존한다. 누락 시 런타임 예외.
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var builder = Host.CreateApplicationBuilder(args);
// ★ stdio transport는 stdout을 JSON-RPC 채널로 쓴다.
// 기본 로깅 provider(Windows EventLog 포함)를 모두 제거한 뒤 콘솔 로그를 전부 stderr로 보낸다.
// EventLog는 표준 사용자 계정에서 '.NET Runtime' 소스 쓰기 실패로 예외를 던져 MCP 요청을 깨뜨릴 수 있다(codex 교차 리뷰 P2).
builder.Logging.ClearProviders();
builder.Logging.AddConsole(o => o.LogToStandardErrorThreshold = LogLevel.Trace);
// 전광판 연결 설정 (환경변수 DABIT_*)
builder.Services.AddSingleton(McpConnectionSettings.FromEnvironment());
// Transport 레지스트리 — Serial + TcpClient 등록 (AutoSend과 동일 범위)
builder.Services.AddSingleton(_ =>
{
var registry = new TransportRegistry();
registry.Register(TransportType.Serial, new SerialTransport());
registry.Register(TransportType.TcpClient, new TcpClientTransport());
return registry;
});
builder.Services.AddSingleton<LedControlService>();
// MCP 서버 — stdio transport + 어셈블리에서 [McpServerToolType] 도구 자동 검색
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
await builder.Build().RunAsync();

130
Dabit.Mcp/README.md Normal file
View File

@@ -0,0 +1,130 @@
# Dabit.Mcp — AI로 LED 전광판을 제어하는 MCP 서버
Claude Desktop 같은 AI 클라이언트가 자연어로 LED 전광판을 **읽고(상태 조회) + 표시하고(텍스트/색) + 설정하는(화면·밝기·시간·보드)** MCP(Model Context Protocol) 서버다.
> 예) "정문 전광판 밝기 50%로", "화면 빨강으로 채워", "시간 맞춰", "가나다라를 4색으로 왼쪽으로 흘려" → AI가 해당 도구를 호출 → 이 서버가 기존 검증된 `DibdProtocol` 코드로 바이트를 조립해 컨트롤러에 전송.
## 동작 구조
```
[사람] → [Claude Desktop (AI)] ──MCP(stdio, JSON-RPC)──> [Dabit.Mcp.exe] ──> [전광판 컨트롤러]
(이 프로젝트) (COM/TCP)
```
- 새로 만든 건 이 서버(어댑터)뿐. 바이트 조립·통신은 `DibdProtocol.Core`(프로토콜) + `DibdProtocol.Transport`(시리얼/TCP)를 그대로 재사용한다.
- 각 도구의 조립은 정본 `DabitChe.Desktop/ViewModels/SettingsViewModel.cs`를 본떠 **byte-identical**.
- `LedControlService``DabitChe.AutoSend`의 전송 패턴을 축약한 것(WPF 의존성 제거). **Phase 2에서 `DibdProtocol.Transport.Rpc.DibdRpcClient`(신 프로토콜 96 method)로 교체 예정인 폐기성 코드.**
## 도구 (20개)
### 읽기 / 상태
| 도구 | 설명 |
|------|------|
| `get_firmware_info` | 펌웨어 모델/버전/화면 크기 (HEX 0x6F) |
| `get_screen_size` | 화면 가로×세로 (펌웨어 응답에서 추출) |
| `get_time` | 컨트롤러 RTC 시각 (HEX 0x66, BCD 파싱) |
| `get_connection_status` | 설정된 연결 정보 — 통신 안 함(스모크용) |
### 콘텐츠 표시 ⚠️ 쓰기
| 도구 | 설명 |
|------|------|
| `display_text` | 단색 텍스트 한 줄 즉시 표시 (실시간 메시지) |
| `display_message` | 글자별 색 + 입장/퇴장 이동효과 + 속도/유지 |
### 화면 · 밝기 · 전원 ⚠️ 쓰기
| 도구 | 설명 |
|------|------|
| `set_screen_size` | 화면 크기(단/열) 설정 (HEX 0x40) |
| `fill_screen` | 전체 단색 채우기, 8색 (HEX 0x42) |
| `load_background_image` | 저장된 배경이미지 로드 (HEX 0x4F) |
| `set_brightness` | 밝기 0~4단계 (100/75/50/25/5%) (HEX 0x44) |
| `led_power_on` / `led_power_off` | LED 전원 (HEX 0x41) |
### 페이지 메시지 ⚠️ 쓰기
| 도구 | 설명 |
|------|------|
| `set_page_count` | 페이지 개수 1~10 (ASCII "60") |
| `reset_page_message` | 페이지 초기화 (전체/개별) (ASCII "61") |
| `set_section_effect` | 섹션 동시/개별 표출 (ASCII "62") |
### 표출신호 (HUB75) ⚠️ 쓰기
| 도구 | 설명 |
|------|------|
| `set_display_signal` | 대표 신호 7종 이름으로 설정 (HEX 0x49) |
| `send_custom_signal` | 사용자 신호 코드 직접 전송 (ASCII "44") |
### 시간 · 보드 설정 (고급)
| 도구 | 종류 | 설명 |
|------|------|------|
| `sync_controller_time` | ⚠️ 쓰기 | PC 시간으로 RTC 동기화 (HEX 0x47, 7B BCD) |
| `read_board_settings` | 읽기 | 디버그/포트기능/통신속도 (ASCII "B3") |
| `save_board_settings` | ⚠️ 쓰기 | 보드기능 저장 (ASCII "B2") |
> **제외(블록 전송)**: 폰트(0x98)·펌웨어(0x9F)·이미지(DAT/BGP/PLA)·스케줄. 대용량 다블록 전송이라 PoC 범위 밖.
## 빌드 · 실행
```powershell
dotnet build Dabit.Mcp/Dabit.Mcp.csproj
dotnet run --project Dabit.Mcp
```
## Claude Desktop 연결
`claude_desktop_config.json``mcpServers`에 등록한다(아래는 실증된 Serial COM2 설정):
```json
{
"mcpServers": {
"dabit-led": {
"command": "D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\bin\\Debug\\net9.0-windows\\Dabit.Mcp.exe",
"env": {
"DABIT_TRANSPORT": "Serial",
"DABIT_COM": "COM2",
"DABIT_BAUD": "115200"
}
}
}
}
```
(개발 중엔 `command``dotnet` + `args:["run","--project","...Dabit.Mcp"]`로 둘 수도 있으나, exe 직접 실행이 빠르고 안정적.)
### 환경변수 (연결 설정)
| 변수 | 의미 | 기본값 |
|------|------|--------|
| `DABIT_TRANSPORT` | `TcpClient` 또는 `Serial` | `TcpClient` |
| `DABIT_HOST` / `DABIT_PORT` | TCP 호스트 / 포트 | `192.168.0.201` / `5000` |
| `DABIT_COM` / `DABIT_BAUD` | 시리얼 포트 / 속도 | `COM1` / `9600` |
| `DABIT_RESPONSE_LATENCY` | 응답 타임아웃(ms) | `1000` |
## 제약 · 주의
- ⚠️ **COM 포트 동시 점유 불가** — WPF 앱(DabitChe.Desktop/AutoSend)과 같은 COM 포트를 동시에 못 연다. 시리얼 사용 시 **WPF 앱을 끄고** MCP 서버를 실행한다.
- ⚠️ **로그는 반드시 stderr** — stdio는 stdout을 JSON-RPC에 쓴다. `Program.cs`에서 `ClearProviders()` + 콘솔 로그를 stderr로 보낸다(Windows EventLog 로거 제거 포함).
- ⚠️ **요청 순서 비보장** — MCP 서버가 여러 요청을 병렬 처리한다. `LedControlService._gate`가 전송은 한 번에 하나로 직렬화하지만 **진입 순서는 보장 안 함**. 한 번에 여러 명령을 몰아 보내면(예: sync 직후 get_time) 실행 순서가 뒤바뀔 수 있다. 순서 의존 작업은 응답을 받고 다음을 보낸다.
- **FlashWrite 명령**(시간·보드·화면크기·페이지·밝기·표출신호·fill·배경)은 응답이 느려 `timeoutMsOverride: 5000` 적용.
-**scroll 4방향(left/right/up/down) 실기 검증 완료**(2026-06-02, DIBD600T V3.6.1). 🟡 미검증: `bpp` 색심도 의미, 밝기 index1 ASCII(75)≠HEX(0x48) 레거시값.
## 로드맵
| Phase | 내용 |
|-------|------|
| **0 (완료)** | 읽기 4 + 콘텐츠 2 + 설정 14 = 20 도구. stdio. 얇은 LedControlService |
| 1 | 밝기 고급(13B 시간대별/CDS)·컨트롤러 재시작/초기화·기타 고급(스케줄속도/깜빡임/잔상/릴레이) |
| 2 | 백엔드를 `DibdRpcClient`(JSON-RPC 96 method)로 승격 — 신 프로토콜 실기 검증 후 |
| 3 | HTTP/SSE transport + HMAC 인증 → 원격/B2B |
## 검증 상태 (2026-06-01)
- ✅ 빌드(경고 0/오류 0), stdio 스모크(20개 도구 노출, tools/call 왕복, 환경변수 로딩, stdout 청결성)
- ✅ 이중 코드 리뷰 반영 — Anthropic `code-reviewer`(byte 조립 정합·죽은코드·timeout) + OpenAI `codex`(HEX status 체크: 거부 응답을 성공으로 오보하던 것 수정)
-**실기 검증** (DIBD500S 펌웨어 V3.4.8, COM2 @ 115200):
- 읽기: `get_firmware_info`, `get_time`(BCD), `read_board_settings`(7필드)
- 표시: `display_text`(환영합니다 빨강), `display_message`(가나다라 4색 좌이동)
- 설정: `set_brightness`(50%), `fill_screen`(빨강), `sync_controller_time`(2026 적용 확인) — 모두 status 0x00
-**실기 검증 추가** (2026-06-02, DIBD600T 펌웨어 V3.6.1, COM2 @ 115200):
- scroll 4방향 전부 정상: `scroll_left`(우→좌) · `scroll_right`(좌→우) · `scroll_up`(아래→위) · `scroll_down`(위→아래) — 육안 확인
- `display_message` 글자별 색(`colors`) 정상 — colors를 쉼표 구분 문자열로 변경해 배열 바인딩 버그 수정
- 🟡 미검증: bpp 색심도, save_board_settings 실기 반영

View File

@@ -0,0 +1,156 @@
using Dabit.Mcp.Config;
using DibdProtocol.Protocol;
using DibdProtocol.Transport;
using Microsoft.Extensions.Logging;
namespace Dabit.Mcp.Services;
/// <summary>
/// 전광판과의 송수신을 담당하는 얇은 서비스.
/// DabitChe.AutoSend.Services.MessageService를 축약했다(WPF 의존성 제거).
/// PoC 폐기성 코드 — Phase 2에서 DibdProtocol.Transport.Rpc.DibdRpcClient(96 method)로 대체 예정.
///
/// 동시성: MCP 서버는 요청을 직렬화하지 않으므로 _gate(SemaphoreSlim)로 명령을 한 번에 하나씩 처리한다.
/// 연결: KeepOpen=true로 프로세스 수명 동안 포트를 유지하고 DisposeAsync에서 1회만 닫는다.
/// timeout: FlashWrite류 명령(시간/보드/화면크기/페이지 설정 등)은 응답이 느리므로 호출자가
/// timeoutMsOverride로 5초 등을 지정한다(기본은 설정값 ResponseLatencyMs).
/// </summary>
public sealed class LedControlService : IAsyncDisposable
{
private readonly TransportRegistry _registry;
private readonly McpConnectionSettings _settings;
private readonly ILogger<LedControlService> _log;
private readonly SemaphoreSlim _gate = new(1, 1);
public LedControlService(TransportRegistry registry, McpConnectionSettings settings, ILogger<LedControlService> log)
{
_registry = registry;
_settings = settings;
_log = log;
}
public McpConnectionSettings Settings => _settings;
private async Task<ITransport> EnsureConnectedAsync(CancellationToken ct)
{
_registry.CurrentType = _settings.ConnectType;
var transport = _registry.GetCurrent();
if (transport.IsConnected)
return transport;
var info = new ConnectionInfo
{
PortName = _settings.PortName,
BaudRate = _settings.BaudRate,
Host = _settings.Host,
Port = _settings.Port,
ResponseLatencyMs = _settings.ResponseLatencyMs,
KeepOpen = true,
};
_log.LogDebug("연결 시도: {Conn}", _settings.Describe());
await transport.ConnectAsync(info, ct);
return transport;
}
private TimeSpan Timeout(int? overrideMs) => TimeSpan.FromMilliseconds(overrideMs ?? _settings.ResponseLatencyMs);
/// <summary>
/// HEX 쓰기 명령 응답의 status를 판정한다(정본 MessageService.DescribeHexResponse 기준).
/// SetScreenSize(0x40)는 Data[0]=0xF1 echo가 정상, 나머지 쓰기는 Data[0]=0x00이 성공.
/// </summary>
public static (bool Ok, string Message) CheckWriteStatus(HexCommand cmd, HexParsedResponse? resp)
{
if (resp is null) return (false, "응답 없음 — 연결 또는 통신 오류.");
if (resp.Data.Length == 0) return (false, "빈 응답.");
var b = resp.Data[0];
var ok = cmd == HexCommand.SetScreenSize ? b == 0xF1 : b == 0x00;
if (ok) return (true, "성공");
var reason = b switch
{
0x10 => "명령 없음(No Command)",
0x20 => "기능 없음(No Function)",
0x40 => "데이터 오류(Data Error)",
0x80 => "알 수 없는 오류",
_ => $"상태 0x{b:X2}",
};
return (false, reason);
}
/// <summary>HEX 명령 송신 후 응답 파싱. 연결/통신 실패는 null로 정규화한다.</summary>
public async Task<HexParsedResponse?> SendHexAsync(HexCommand cmd, byte[] data, CancellationToken ct = default, int? timeoutMsOverride = null)
{
await _gate.WaitAsync(ct);
try
{
var transport = await EnsureConnectedAsync(ct);
var packet = PacketBuilder.BuildHex(cmd, data);
_log.LogInformation("TX HEX cmd=0x{Cmd:X2} ({Len}B)", (byte)cmd, data.Length);
var response = await transport.SendAndReceiveAsync(packet, Timeout(timeoutMsOverride), ct);
var parsed = PacketParser.ParseHex(response);
_log.LogInformation("RX HEX {Result}", parsed is null ? "파싱 실패" : $"cmd=0x{(byte)parsed.Cmd:X2} {parsed.Data.Length}B");
return parsed;
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_log.LogError(ex, "HEX 전송 실패 cmd=0x{Cmd:X2}", (byte)cmd);
return null;
}
finally { _gate.Release(); }
}
/// <summary>ASCII 명령 송신 후 응답 파싱. 연결/통신 실패는 null로 정규화한다.</summary>
public async Task<AsciiParsedResponse?> SendAsciiAsync(string msgType, string data, CancellationToken ct = default, int? timeoutMsOverride = null)
{
await _gate.WaitAsync(ct);
try
{
var transport = await EnsureConnectedAsync(ct);
var packet = PacketBuilder.BuildAscii(msgType, data);
_log.LogInformation("TX ASCII msgType={MsgType} data=\"{Data}\"", msgType, data);
var response = await transport.SendAndReceiveAsync(packet, Timeout(timeoutMsOverride), ct);
var parsed = PacketParser.ParseAscii(response);
_log.LogInformation("RX ASCII {Result}", parsed is null ? "파싱 실패" : $"result={parsed.Result} data=\"{parsed.Data}\"");
return parsed;
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_log.LogError(ex, "ASCII 전송 실패 msgType={MsgType}", msgType);
return null;
}
finally { _gate.Release(); }
}
/// <summary>ASCII 혼합(속성 EUC-KR + 텍스트 raw bytes) 송신. 연결/통신 실패는 null.</summary>
public async Task<AsciiParsedResponse?> SendAsciiMixedAsync(string msgType, string attributes, byte[] textBytes, CancellationToken ct = default, int? timeoutMsOverride = null)
{
await _gate.WaitAsync(ct);
try
{
var transport = await EnsureConnectedAsync(ct);
var packet = PacketBuilder.BuildAsciiMixed(msgType, attributes, textBytes);
_log.LogInformation("TX ASCII-mixed msgType={MsgType} attrs=\"{Attrs}\" text={Len}B", msgType, attributes, textBytes.Length);
var response = await transport.SendAndReceiveAsync(packet, Timeout(timeoutMsOverride), ct);
var parsed = PacketParser.ParseAscii(response);
_log.LogInformation("RX ASCII {Result}", parsed is null ? "파싱 실패" : $"result={parsed.Result} data=\"{parsed.Data}\"");
return parsed;
}
catch (Exception ex) when (ex is not OperationCanceledException)
{
_log.LogError(ex, "ASCII-mixed 전송 실패 msgType={MsgType}", msgType);
return null;
}
finally { _gate.Release(); }
}
public async ValueTask DisposeAsync()
{
try
{
var transport = _registry.GetCurrent();
if (transport.IsConnected)
await transport.DisconnectAsync();
}
catch { /* 종료 시 닫기 실패 무시 */ }
_gate.Dispose();
}
}

View File

@@ -0,0 +1,54 @@
using System.ComponentModel;
using Dabit.Mcp.Services;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
namespace Dabit.Mcp.Tools;
/// <summary>보드기능설정 읽기/저장 도구(디버그·포트기능·통신속도). ⚠️ 저장은 쓰기.</summary>
[McpServerToolType]
public sealed class BoardTools
{
private readonly LedControlService _led;
private readonly ILogger<BoardTools> _log;
public BoardTools(LedControlService led, ILogger<BoardTools> log)
{
_led = led;
_log = log;
}
[McpServerTool]
[Description("보드기능설정을 읽는다(디버그/BH1·J4 기능/J2·J3·BH1 통신속도 인덱스). 읽기 — 표시 변경 없음.")]
public async Task<string> ReadBoardSettings(CancellationToken ct = default)
{
var resp = await _led.SendAsciiAsync("B3", "0", ct, 5000);
if (resp is null)
return "보드설정 읽기 실패 — 연결 또는 통신 오류.";
var f = resp.Data.Trim().Split(',');
if (f.Length < 6)
return $"보드설정 응답 형식이 예상과 다릅니다: \"{resp.Data}\"";
var rs = f.Length >= 7 ? $", RS485주소={f[6]}" : "";
return $"보드설정 — debug={f[0]}, BH1기능={f[1]}, J4기능={f[2]}, J2속도={f[3]}, J3속도={f[4]}, BH1속도={f[5]}{rs}";
}
[McpServerTool]
[Description("보드기능설정을 저장한다. ⚠️ 쓰기·설정 저장. 범위: debug 0~15, 나머지 0~7. 잘못 설정하면 통신이 끊길 수 있으니 주의.")]
public async Task<string> SaveBoardSettings(
[Description("디버그 레벨 0~15")] int debug,
[Description("BH1 포트 기능 0~7")] int bh1Func,
[Description("J4 포트 기능 0~7")] int j4Func,
[Description("J2 통신속도 인덱스 0~7")] int j2Baud,
[Description("J3 통신속도 인덱스 0~7")] int j3Baud,
[Description("BH1 통신속도 인덱스 0~7")] int bh1Baud,
CancellationToken ct = default)
{
// 정본 B2: data 맨 앞 공백 1개 + 6필드. debug만 HEX 대문자, 나머지 10진수.
var hexDebug = Math.Clamp(debug, 0, 15).ToString("X");
var data = $" {hexDebug},{Math.Clamp(bh1Func, 0, 7)},{Math.Clamp(j4Func, 0, 7)},{Math.Clamp(j2Baud, 0, 7)},{Math.Clamp(j3Baud, 0, 7)},{Math.Clamp(bh1Baud, 0, 7)}";
var resp = await _led.SendAsciiAsync("B2", data, ct, 5000);
if (resp is null)
return "보드설정 저장 실패 — 연결 또는 통신 오류.";
return resp.Result == "F" ? "전광판이 거부했습니다 (Result=F)." : "보드설정 저장 완료.";
}
}

View File

@@ -0,0 +1,59 @@
using System.ComponentModel;
using Dabit.Mcp.Services;
using DibdProtocol.Protocol;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
namespace Dabit.Mcp.Tools;
/// <summary>밝기·전원 설정 도구. ⚠️ 쓰기.</summary>
[McpServerToolType]
public sealed class BrightnessPowerTools
{
private readonly LedControlService _led;
private readonly ILogger<BrightnessPowerTools> _log;
public BrightnessPowerTools(LedControlService led, ILogger<BrightnessPowerTools> log)
{
_led = led;
_log = log;
}
// 밝기 단계 0~4 → (표시%, HEX byte). 정본 SettingsViewModel 매핑 그대로(byte-identical).
private static readonly (int Pct, byte Hex)[] BrightnessMap =
{
(100, 0x64), (75, 0x48), (50, 0x32), (25, 0x19), (5, 0x05),
};
[McpServerTool]
[Description("전광판 밝기를 설정한다. ⚠️ 쓰기·설정 저장. level 0~4: 0=100%, 1=75%, 2=50%, 3=25%, 4=5%.")]
public async Task<string> SetBrightness(
[Description("밝기 단계 0~4 (0=100% 1=75% 2=50% 3=25% 4=5%)")] int level,
CancellationToken ct = default)
{
if (level < 0 || level > 4)
return "밝기 단계는 0~4여야 합니다 (0=100% 1=75% 2=50% 3=25% 4=5%).";
var (pct, hex) = BrightnessMap[level];
var resp = await _led.SendHexAsync(HexCommand.SetBrightness, new[] { hex }, ct, 5000);
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.SetBrightness, resp);
return ok ? $"밝기 {pct}% 설정." : $"밝기 설정 실패 — {msg}";
}
[McpServerTool]
[Description("전광판 LED 전원을 켠다. ⚠️ 쓰기.")]
public async Task<string> LedPowerOn(CancellationToken ct = default)
{
var resp = await _led.SendHexAsync(HexCommand.LedPowerControl, new byte[] { 0x01 }, ct);
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.LedPowerControl, resp);
return ok ? "LED 전원 켜짐." : $"전원 켜기 실패 — {msg}";
}
[McpServerTool]
[Description("전광판 LED 전원을 끈다. ⚠️ 쓰기 — 화면이 꺼진다.")]
public async Task<string> LedPowerOff(CancellationToken ct = default)
{
var resp = await _led.SendHexAsync(HexCommand.LedPowerControl, new byte[] { 0x00 }, ct);
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.LedPowerControl, resp);
return ok ? "LED 전원 꺼짐." : $"전원 끄기 실패 — {msg}";
}
}

View File

@@ -0,0 +1,163 @@
using System.ComponentModel;
using System.Text;
using Dabit.Mcp.Services;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
namespace Dabit.Mcp.Tools;
/// <summary>
/// 전광판 표시를 바꾸는 도구. ⚠️ 쓰기 — 실제 장비의 표시 내용을 변경한다.
/// </summary>
[McpServerToolType]
public sealed class DisplayControlTools
{
private readonly LedControlService _led;
private readonly ILogger<DisplayControlTools> _log;
public DisplayControlTools(LedControlService led, ILogger<DisplayControlTools> log)
{
_led = led;
_log = log;
}
/// <summary>color 문자열 → ASCII /C 색상 코드(0~7). 8색 고정(정본 ascii-reference).</summary>
private static readonly Dictionary<string, string> ColorCodes = new(StringComparer.OrdinalIgnoreCase)
{
["black"] = "0", ["red"] = "1", ["green"] = "2", ["yellow"] = "3",
["blue"] = "4", ["magenta"] = "5", ["cyan"] = "6", ["white"] = "7",
};
/// <summary>
/// effect 문자열 → /E 효과 코드(2자리). 입장·퇴장에 각각 적용한다(/E{입장}{퇴장}).
/// stop(01)·scroll 4방향(06~09) 모두 실기 검증됨(2026-06-02, DIBD600T 펌웨어 V3.6.1, 육안 확인).
/// </summary>
private static readonly Dictionary<string, string> EffectCodes = new(StringComparer.OrdinalIgnoreCase)
{
["stop"] = "01", // 정지 — 검증
["scroll_left"] = "06", // 좌이동(오른쪽→왼쪽) — 검증
["scroll_right"] = "07", // 우이동(왼쪽→오른쪽) — 검증(2026-06-02)
["scroll_up"] = "08", // 상향(아래→위) — 검증(2026-06-02)
["scroll_down"] = "09", // 하향(위→아래) — 검증(2026-06-02)
};
private const int MaxTextLength = 100;
[McpServerTool]
[Description("전광판에 단색 텍스트 한 줄을 즉시 표시한다(실시간 메시지, 정지). " +
"⚠️ 쓰기 동작 — 현재 표시 중인 실시간 메시지를 즉시 덮어쓴다. " +
"지원 색상 8종: red, green, yellow, blue, magenta, cyan, white, black (그 외는 노랑으로 근사). " +
"글자별 색이나 이동 효과가 필요하면 display_message를 쓴다.")]
public async Task<string> DisplayText(
[Description("표시할 텍스트(한글 가능, 최대 100자). 예: \"환영합니다\"")] string text,
[Description("문자 색상. red/green/yellow/blue/magenta/cyan/white/black 중 하나. 기본 yellow")] string color = "yellow",
CancellationToken ct = default)
{
if (string.IsNullOrEmpty(text))
return "표시할 텍스트가 비어 있습니다.";
if (!ValidateText(text, out var err))
return err;
if (!ColorCodes.TryGetValue(color.Trim(), out var code))
{
_log.LogWarning("지원하지 않는 색상 '{Color}' → 노랑(yellow)으로 근사", color);
code = "3";
}
// 실시간 메시지(msgType "0") + 색상/정지(즉시)/속도유지/EUC-KR 16x16. (실기 검증된 형식)
var data = $"/C{code}/E0101/S2002/F0003{text}";
_log.LogInformation("display_text: \"{Text}\" color={Color}(/C{Code})", text, color, code);
return Format(await _led.SendAsciiAsync("0", data, ct), $"\"{text}\" ({color})");
}
[McpServerTool]
[Description("전광판에 텍스트를 글자별 색상·입장/퇴장 이동효과·속도와 함께 표시한다(실시간 메시지). " +
"⚠️ 쓰기 동작 — 현재 표시 중인 실시간 메시지를 즉시 덮어쓴다. " +
"colors: 글자별 색을 쉼표로 구분(text 글자 수와 1:1, 예 \"red,green,yellow,blue\", 생략 시 전체 노랑). " +
"effect=입장 효과, exit_effect=퇴장 효과: stop/scroll_left/scroll_right/scroll_up/scroll_down. " +
"연속으로 흐르게 하려면 입장·퇴장을 같은 scroll로 준다(예: 둘 다 scroll_left). " +
"speed 0~99(작을수록 빠름, 0=최고속). hold_sec 유지 시간(초). " +
"scroll 4방향(left/right/up/down) 모두 실기 검증 완료(2026-06-02, DIBD600T V3.6.1).")]
public async Task<string> DisplayMessage(
[Description("표시할 텍스트(한글 가능). 예: \"가나다라\"")] string text,
[Description("글자별 색을 쉼표로 구분한 문자열, text 글자와 1:1 대응. 예: \"red,green,yellow,blue\". 생략 시 전체 노랑")] string? colors = null,
[Description("입장 효과: stop/scroll_left/scroll_right/scroll_up/scroll_down. 기본 stop")] string effect = "stop",
[Description("퇴장 효과: 위와 동일 목록. 입장과 같은 scroll을 주면 멈춤 없이 연속으로 흐른다. 기본 stop")] string exitEffect = "stop",
[Description("이동 속도 0~99(작을수록 빠름, 0=최고속). 기본 20")] int speed = 20,
[Description("유지 시간(초), 0=대기 없음. 기본 1")] int holdSec = 1,
CancellationToken ct = default)
{
if (string.IsNullOrEmpty(text))
return "표시할 텍스트가 비어 있습니다.";
if (!ValidateText(text, out var err))
return err;
var enter = ResolveEffect(effect);
var exit = ResolveEffect(exitEffect);
var sp = Math.Clamp(speed, 0, 99);
var hold2 = Math.Clamp(holdSec * 2, 0, 99); // 유지시간은 0.5초 단위(1초=02)
// colors는 쉼표 구분 문자열("red,green,..."). ModelContextProtocol 1.3.0이 string[]? 파라미터의
// JSON 스키마를 깨뜨려(type 누락) 배열 인자 바인딩 시 호출 자체가 실패하므로, 문자열로 받아 직접 분리한다.
var colorArr = string.IsNullOrWhiteSpace(colors)
? null
: colors.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
// 속성(입장/퇴장 효과·속도·폰트) 먼저, 그 뒤에 글자별 색을 인라인으로 붙인다.
var sb = new StringBuilder($"/E{enter}{exit}/S{sp:D2}{hold2:D2}/F0003");
for (int i = 0; i < text.Length; i++)
{
var code = "3"; // 기본 노랑
var want = colorArr is not null && i < colorArr.Length ? colorArr[i] : null;
if (want is not null && ColorCodes.TryGetValue(want.Trim(), out var cc))
code = cc;
else if (want is not null)
_log.LogWarning("지원하지 않는 색상 '{Color}'(글자 {Idx}) → 노랑", want, i);
sb.Append($"/C{code}{text[i]}");
}
var data = sb.ToString();
_log.LogInformation("display_message: 입장={Enter} 퇴장={Exit} speed={Speed} hold={Hold}s data=\"{Data}\"", effect, exitEffect, sp, holdSec, data);
return Format(await _led.SendAsciiAsync("0", data, ct), $"\"{text}\" (입장 {effect}, 퇴장 {exitEffect}, 속도 {sp}, 유지 {holdSec}s)");
}
/// <summary>effect 문자열 → /E 코드. 모르는 값은 정지(01)로.</summary>
private string ResolveEffect(string name)
{
if (EffectCodes.TryGetValue(name.Trim(), out var c))
return c;
_log.LogWarning("알 수 없는 효과 '{Effect}' → stop", name);
return "01";
}
/// <summary>본문에 ASCII 속성/프레임 제어코드가 섞이면 표시가 깨지거나 속성이 주입된다. 차단.</summary>
private bool ValidateText(string text, out string error)
{
if (text.Contains('/') || text.Contains("![") || text.Contains("!]"))
{
error = "텍스트에 제어 문자('/', '![', '!]')는 사용할 수 없습니다 — 속성 코드로 해석되어 표시가 깨집니다.";
return false;
}
if (text.Length > MaxTextLength)
{
error = $"텍스트가 너무 깁니다(최대 {MaxTextLength}자, 입력 {text.Length}자).";
return false;
}
error = "";
return true;
}
/// <summary>응답을 사용자 메시지로 변환. 성공 판정은 Result=="0" allowlist.</summary>
private static string Format(DibdProtocol.Protocol.AsciiParsedResponse? resp, string what)
{
if (resp is null)
return "전송 실패 — 연결 또는 통신 오류 (전광판 응답 없음).";
return resp.Result switch
{
"0" => $"표시 완료: {what}.",
"F" => "전광판이 명령을 거부했습니다 (Result=F).",
_ => $"전송했으나 응답 형식이 예상과 다릅니다 (Result=\"{resp.Result}\") — 실기 확인이 필요합니다.",
};
}
}

View File

@@ -0,0 +1,72 @@
using System.ComponentModel;
using System.Text;
using System.Text.RegularExpressions;
using Dabit.Mcp.Services;
using DibdProtocol.Protocol;
using ModelContextProtocol.Server;
namespace Dabit.Mcp.Tools;
/// <summary>
/// 전광판 상태를 읽는 도구 모음. 모두 읽기 전용 — 표시 내용을 바꾸지 않는다.
/// </summary>
[McpServerToolType]
public sealed class DisplayStatusTools
{
private readonly LedControlService _led;
public DisplayStatusTools(LedControlService led)
{
_led = led;
}
[McpServerTool]
[Description("연결된 LED 전광판의 펌웨어 정보(모델/버전/현재 화면 크기 등)를 읽는다. 표시 내용은 변경하지 않는다.")]
public async Task<string> GetFirmwareInfo(CancellationToken ct = default)
{
var resp = await _led.SendHexAsync(HexCommand.ReadBoardInfo, [0xF1], ct);
if (resp is null || resp.Data.Length == 0)
return "펌웨어 정보를 읽지 못했습니다 (응답 없음 또는 연결 실패).";
// 응답 data[0] == 0xF1 (요청 마커 에코)이면 건너뛴다 (FirmwareService 선례)
var start = (resp.Data.Length > 1 && resp.Data[0] == 0xF1) ? 1 : 0;
var text = Encoding.ASCII.GetString(resp.Data, start, resp.Data.Length - start).TrimEnd('\0', ' ');
return string.IsNullOrWhiteSpace(text) ? "펌웨어 정보가 비어 있습니다." : text;
}
[McpServerTool]
[Description("전광판의 현재 화면 크기(가로 x 세로 픽셀)를 읽는다. 펌웨어 정보 응답에서 추출한다.")]
public async Task<string> GetScreenSize(CancellationToken ct = default)
{
var info = await GetFirmwareInfo(ct);
// 펌웨어 문자열에서 "96x320" 같은 패턴 추출 (실기 검증 후 정밀화)
var m = Regex.Match(info, @"(\d+)\s*[xX]\s*(\d{2,4})");
return m.Success
? $"화면 크기: 가로 {m.Groups[1].Value} x 세로 {m.Groups[2].Value} 픽셀"
: $"화면 크기를 펌웨어 응답에서 찾지 못했습니다. 원문: {info}";
}
[McpServerTool]
[Description("전광판 컨트롤러의 현재 시각(RTC)을 읽는다.")]
public async Task<string> GetTime(CancellationToken ct = default)
{
var resp = await _led.SendHexAsync(HexCommand.ReadTime, [0x00], ct);
if (resp is null || resp.Data.Length == 0)
return "시간을 읽지 못했습니다 (응답 없음 또는 연결 실패).";
// 응답 7바이트 BCD: [yy, MM, dd, dow, HH, mm, ss] (sync_controller_time과 동일 레이아웃)
var d = resp.Data;
if (d.Length < 7)
return $"시간 응답이 예상보다 짧습니다 (raw {Convert.ToHexString(d)}).";
static int FromBcd(byte b) => (b >> 4) * 10 + (b & 0x0F);
var year = 2000 + FromBcd(d[0]);
return $"컨트롤러 시각: {year}-{FromBcd(d[1]):D2}-{FromBcd(d[2]):D2} {FromBcd(d[4]):D2}:{FromBcd(d[5]):D2}:{FromBcd(d[6]):D2}";
}
[McpServerTool]
[Description("현재 MCP 서버에 설정된 전광판 연결 정보를 반환한다. 실제 통신은 하지 않는다(연결 점검·스모크용).")]
public string GetConnectionStatus()
{
return $"전광판 연결 설정 — {_led.Settings.Describe()}";
}
}

View File

@@ -0,0 +1,62 @@
using System.ComponentModel;
using Dabit.Mcp.Services;
using DibdProtocol.Protocol;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
namespace Dabit.Mcp.Tools;
/// <summary>페이지 메시지 개수·초기화·섹션 효과 도구. ⚠️ 쓰기.</summary>
[McpServerToolType]
public sealed class PageTools
{
private readonly LedControlService _led;
private readonly ILogger<PageTools> _log;
public PageTools(LedControlService led, ILogger<PageTools> log)
{
_led = led;
_log = log;
}
[McpServerTool]
[Description("페이지 메시지 개수를 설정한다. ⚠️ 쓰기·설정 저장. count 1~10.")]
public async Task<string> SetPageCount(
[Description("페이지 수 1~10")] int count,
CancellationToken ct = default)
{
if (count < 1 || count > 10)
return "페이지 수는 1~10이어야 합니다.";
var resp = await _led.SendAsciiAsync("60", count.ToString("D2"), ct, 5000);
return Format(resp, $"페이지 개수 {count}개 설정.");
}
[McpServerTool]
[Description("페이지 메시지를 초기화(삭제)한다. ⚠️ 쓰기. page: 0=전체 삭제, 1~10=개별 페이지.")]
public async Task<string> ResetPageMessage(
[Description("0=전체 삭제, 1~10=개별 페이지")] int page,
CancellationToken ct = default)
{
if (page < 0 || page > 10)
return "page는 0(전체)~10이어야 합니다.";
// 정본: 전체→"99", 개별 N(1~10)→(N-1) zero-pad 2자리
var data = page == 0 ? "99" : (page - 1).ToString("D2");
var resp = await _led.SendAsciiAsync("61", data, ct, 5000);
return Format(resp, page == 0 ? "전체 페이지 초기화." : $"{page}번 페이지 초기화.");
}
[McpServerTool]
[Description("섹션별 표출 효과 모드를 설정한다. ⚠️ 쓰기. mode: 0=동시표출, 1=개별표출.")]
public async Task<string> SetSectionEffect(
[Description("0=동시표출, 1=개별표출")] int mode,
CancellationToken ct = default)
{
// 정본: N=동시표출, Y=개별표출
var data = mode == 0 ? "N" : "Y";
var resp = await _led.SendAsciiAsync("62", data, ct, 5000);
return Format(resp, mode == 0 ? "섹션 동시표출 설정." : "섹션 개별표출 설정.");
}
private static string Format(DibdProtocol.Protocol.AsciiParsedResponse? r, string ok)
=> r is null ? "전송 실패 — 연결 또는 통신 오류." : (r.Result == "F" ? "전광판이 거부했습니다 (Result=F)." : ok);
}

View File

@@ -0,0 +1,76 @@
using System.ComponentModel;
using Dabit.Mcp.Services;
using DibdProtocol.Protocol;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
namespace Dabit.Mcp.Tools;
/// <summary>화면 크기·채우기·배경이미지 설정 도구. ⚠️ 쓰기.</summary>
[McpServerToolType]
public sealed class ScreenTools
{
private readonly LedControlService _led;
private readonly ILogger<ScreenTools> _log;
public ScreenTools(LedControlService led, ILogger<ScreenTools> log)
{
_led = led;
_log = log;
}
// 색 인덱스 0~7 → RGB332 colorByte (정본 SettingsViewModel, R[2:0]G[5:3]B[7:6])
private static readonly byte[] FillColors = { 0x00, 0x07, 0x38, 0x3F, 0xC0, 0xC7, 0xF8, 0xFF };
private static readonly string[] ColorNames = { "검정", "빨강", "초록", "노랑", "파랑", "분홍", "청록", "하양" };
// bpp 입력(0/1/2) → payload byte (정본: 0→0x02, 2→0x08, 그 외→0x03=3bpp 8색 기본)
private static byte BppByte(int bpp) => bpp switch { 0 => 0x02, 2 => 0x08, _ => 0x03 };
[McpServerTool]
[Description("전광판 화면 크기(단/열)를 설정한다. ⚠️ 쓰기·설정 저장. layout은 모듈 배열(0~5).")]
public async Task<string> SetScreenSize(
[Description("단(행) 수 1~16")] int rows,
[Description("열 수 1~32")] int cols,
[Description("색심도 0/1/2 (기본 1=3bpp 8색)")] int bpp = 1,
[Description("배열 0~5 (기본 0)")] int layout = 0,
CancellationToken ct = default)
{
byte[] payload = { BppByte(bpp), (byte)Math.Clamp(rows, 1, 16), (byte)Math.Clamp(cols, 1, 32), (byte)Math.Clamp(layout, 0, 5), 0x00, 0xF1 };
var resp = await _led.SendHexAsync(HexCommand.SetScreenSize, payload, ct, 5000);
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.SetScreenSize, resp);
if (!ok) return $"화면 크기 설정 실패 — {msg}";
// 응답 [0xF1, rows, cols] — 실제 적용된 값
return resp!.Data.Length >= 3
? $"화면 크기 설정됨: {resp.Data[1]}단 x {resp.Data[2]}열."
: "화면 크기 설정됨.";
}
[McpServerTool]
[Description("전광판 전체를 단색으로 채운다. ⚠️ 쓰기 — 현재 화면을 덮어쓴다. color 0~7: 0검정 1빨강 2초록 3노랑 4파랑 5분홍 6청록 7하양.")]
public async Task<string> FillScreen(
[Description("색 인덱스 0~7 (0검정 1빨강 2초록 3노랑 4파랑 5분홍 6청록 7하양)")] int color,
[Description("색심도 0/1/2 (기본 1)")] int bpp = 1,
CancellationToken ct = default)
{
if (color < 0 || color > 7)
return "색 인덱스는 0~7이어야 합니다 (0검정 1빨강 2초록 3노랑 4파랑 5분홍 6청록 7하양).";
byte[] payload = { BppByte(bpp), FillColors[color], 0x00, 0x00, 0x00 };
var resp = await _led.SendHexAsync(HexCommand.FillScreen, payload, ct, 5000);
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.FillScreen, resp);
return ok ? $"화면 채움: {ColorNames[color]}." : $"화면 채우기 실패 — {msg}";
}
[McpServerTool]
[Description("저장된 배경이미지를 불러와 표시한다. ⚠️ 쓰기. num 0~255 (0=사용 안 함).")]
public async Task<string> LoadBackgroundImage(
[Description("이미지 번호 0~255 (0=사용 안 함)")] int num,
CancellationToken ct = default)
{
if (num < 0 || num > 255)
return "이미지 번호는 0~255여야 합니다.";
var resp = await _led.SendHexAsync(HexCommand.LoadBackgroundImage, new[] { (byte)num }, ct, 5000);
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.LoadBackgroundImage, resp);
if (!ok) return $"배경이미지 로드 실패 — {msg}";
return num == 0 ? "배경이미지 사용 안 함으로 설정." : $"배경이미지 {num}번 로드.";
}
}

View File

@@ -0,0 +1,69 @@
using System.ComponentModel;
using Dabit.Mcp.Services;
using DibdProtocol.Protocol;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
namespace Dabit.Mcp.Tools;
/// <summary>
/// 표출신호(HUB75 패턴) 설정 도구. ⚠️ 쓰기 — F16 영역(70+ 모델 의존).
/// 대표 신호만 이름으로 지원하고, 그 외는 send_custom_signal로 raw 코드 전송.
/// </summary>
[McpServerToolType]
public sealed class SignalTools
{
private readonly LedControlService _led;
private readonly ILogger<SignalTools> _log;
public SignalTools(LedControlService led, ILogger<SignalTools> log)
{
_led = led;
_log = log;
}
// 대표 표출신호 → HexData 4바이트 (정본 DisplaySignalData 일부). 전체 70+는 send_custom_signal로.
private static readonly Dictionary<string, byte[]> SignalHexData = new(StringComparer.OrdinalIgnoreCase)
{
["16D-P16D1S11"] = new byte[] { 0x00, 0x10, 0x01, 0x11 },
["16D-P16D1S10-1"] = new byte[] { 0x00, 0x10, 0x01, 0x01 },
["08D-P32D1S11"] = new byte[] { 0x00, 0x08, 0x01, 0x11 },
["08D-P64D1S21"] = new byte[] { 0x00, 0x08, 0x01, 0x21 },
["04D-P32D2S11"] = new byte[] { 0x00, 0x04, 0x02, 0x11 },
["04D-P64D1S11"] = new byte[] { 0x00, 0x04, 0x01, 0x11 },
["32D-P16D1S11"] = new byte[] { 0x00, 0x20, 0x01, 0x11 },
};
[McpServerTool]
[Description("전광판 표출신호(HUB75 패턴)를 이름으로 설정한다. ⚠️ 쓰기 — 잘못 설정하면 화면이 깨진다. " +
"지원: 16D-P16D1S11, 16D-P16D1S10-1, 08D-P32D1S11, 08D-P64D1S21, 04D-P32D2S11, 04D-P64D1S11, 32D-P16D1S11. " +
"목록에 없으면 send_custom_signal을 쓴다. (08D-P64D1S21은 138 IC 스캔 고정)")]
public async Task<string> SetDisplaySignal(
[Description("신호 이름. 예: \"16D-P16D1S11\"")] string signalName,
[Description("색 순서 0~6 (기본 0)")] int colorOrder = 0,
CancellationToken ct = default)
{
if (!SignalHexData.TryGetValue(signalName.Trim(), out var hd))
return $"지원하지 않는 신호 '{signalName}'. 지원 목록: {string.Join(", ", SignalHexData.Keys)}. 또는 send_custom_signal 사용.";
var colorHex = (byte)(Math.Clamp(colorOrder, 0, 6) + 1); // 정본: idx+1
byte[] payload = { hd[0], hd[1], hd[2], hd[3], colorHex, 0x01 }; // scanHex 기본 0x01(138 IC)
var resp = await _led.SendHexAsync(HexCommand.SetHub75Pattern, payload, ct, 5000);
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.SetHub75Pattern, resp);
return ok ? $"표출신호 '{signalName}' 설정 (color={colorOrder})." : $"표출신호 설정 실패 — {msg}";
}
[McpServerTool]
[Description("사용자 지정 표출신호 코드를 직접 전송한다(고급). ⚠️ 쓰기 — 코드 형식을 정확히 알 때만 사용. 잘못된 값은 화면을 깨뜨릴 수 있다.")]
public async Task<string> SendCustomSignal(
[Description("신호 코드 문자열. 예: \"8121\"")] string code,
CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(code) || code.Contains('/') || code.Contains("![") || code.Contains("!]"))
return "유효한 신호 코드가 아닙니다 ('/', '![', '!]' 불가).";
// 정본: ASCII msgType "44", data 맨 앞 공백 1개 필수
var resp = await _led.SendAsciiAsync("44", " " + code.Trim(), ct, 5000);
if (resp is null) return "전송 실패 — 연결 또는 통신 오류.";
return resp.Result == "F" ? "전광판이 거부했습니다 (Result=F)." : $"사용자 표출신호 '{code}' 전송.";
}
}

View File

@@ -0,0 +1,50 @@
using System.ComponentModel;
using Dabit.Mcp.Services;
using DibdProtocol.Protocol;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
namespace Dabit.Mcp.Tools;
/// <summary>컨트롤러 시각 동기화 도구. ⚠️ 쓰기.</summary>
[McpServerToolType]
public sealed class TimeTools
{
private readonly LedControlService _led;
private readonly ILogger<TimeTools> _log;
public TimeTools(LedControlService led, ILogger<TimeTools> log)
{
_led = led;
_log = log;
}
// 정본 SettingsViewModel: ToBcd(v) = (v/10*16)+(v%10). 26 → 0x26.
private static byte ToBcd(int v) => (byte)(v / 10 * 16 + v % 10);
private static string KoreanDow(DayOfWeek d) => d switch
{
DayOfWeek.Sunday => "일", DayOfWeek.Monday => "월", DayOfWeek.Tuesday => "화",
DayOfWeek.Wednesday => "수", DayOfWeek.Thursday => "목", DayOfWeek.Friday => "금",
_ => "토",
};
[McpServerTool]
[Description("컨트롤러의 시각(RTC)을 현재 PC 시간으로 동기화한다. ⚠️ 쓰기·설정 저장.")]
public async Task<string> SyncControllerTime(CancellationToken ct = default)
{
var now = DateTime.Now;
// 정본 0x47: [yy(BCD), MM(BCD), dd(BCD), dow(raw 0=일~6=토), HH(BCD), mm(BCD), ss(BCD)]
byte[] payload =
{
ToBcd(now.Year % 100), ToBcd(now.Month), ToBcd(now.Day),
(byte)(int)now.DayOfWeek,
ToBcd(now.Hour), ToBcd(now.Minute), ToBcd(now.Second),
};
var resp = await _led.SendHexAsync(HexCommand.SyncTime, payload, ct, 5000);
var (ok, msg) = LedControlService.CheckWriteStatus(HexCommand.SyncTime, resp);
return ok
? $"시간 동기화 완료: {now:yyyy-MM-dd HH:mm:ss} ({KoreanDow(now.DayOfWeek)})."
: $"시간 동기화 실패 — {msg}";
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,13 @@
{
"runtimeOptions": {
"tfm": "net9.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "9.0.0"
},
"configProperties": {
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
"CSWINRT_USE_WINDOWS_UI_XAML_PROJECTIONS": false
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,293 @@
{
"format": 1,
"restore": {
"D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\Dabit.Mcp.csproj": {}
},
"projects": {
"D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\Dabit.Mcp.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\Dabit.Mcp.csproj",
"projectName": "Dabit.Mcp",
"projectPath": "D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\Dabit.Mcp.csproj",
"packagesPath": "C:\\Users\\INSU LEE\\.nuget\\packages\\",
"outputPath": "D:\\Gitea\\dabitche-mcp\\Dabit.Mcp\\obj\\",
"projectStyle": "PackageReference",
"configFilePaths": [
"C:\\Users\\INSU LEE\\AppData\\Roaming\\NuGet\\NuGet.Config"
],
"originalTargetFrameworks": [
"net9.0-windows"
],
"sources": {
"C:\\Program Files\\dotnet\\library-packs": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net9.0-windows7.0": {
"targetAlias": "net9.0-windows",
"projectReferences": {
"D:\\Gitea\\dabitche-mcp\\DibdProtocol\\DibdProtocol.csproj": {
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol\\DibdProtocol.csproj"
}
}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.300"
},
"frameworks": {
"net9.0-windows7.0": {
"targetAlias": "net9.0-windows",
"dependencies": {
"Microsoft.Extensions.Hosting": {
"target": "Package",
"version": "[9.0.0, )"
},
"ModelContextProtocol": {
"target": "Package",
"version": "[1.3.0, )"
},
"System.Text.Encoding.CodePages": {
"target": "Package",
"version": "[10.0.5, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.314/PortableRuntimeIdentifierGraph.json"
}
}
},
"D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj",
"projectName": "DibdProtocol.Core",
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj",
"packagesPath": "C:\\Users\\INSU LEE\\.nuget\\packages\\",
"outputPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\obj\\",
"projectStyle": "PackageReference",
"configFilePaths": [
"C:\\Users\\INSU LEE\\AppData\\Roaming\\NuGet\\NuGet.Config"
],
"originalTargetFrameworks": [
"net9.0"
],
"sources": {
"C:\\Program Files\\dotnet\\library-packs": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"projectReferences": {}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.300"
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"dependencies": {
"System.Text.Encoding.CodePages": {
"target": "Package",
"version": "[10.0.5, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.314/PortableRuntimeIdentifierGraph.json"
}
}
},
"D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\DibdProtocol.Transport.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\DibdProtocol.Transport.csproj",
"projectName": "DibdProtocol.Transport",
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\DibdProtocol.Transport.csproj",
"packagesPath": "C:\\Users\\INSU LEE\\.nuget\\packages\\",
"outputPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\obj\\",
"projectStyle": "PackageReference",
"configFilePaths": [
"C:\\Users\\INSU LEE\\AppData\\Roaming\\NuGet\\NuGet.Config"
],
"originalTargetFrameworks": [
"net9.0"
],
"sources": {
"C:\\Program Files\\dotnet\\library-packs": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"projectReferences": {
"D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj": {
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj"
}
}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.300"
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"dependencies": {
"MQTTnet": {
"target": "Package",
"version": "[4.3.7.1207, )"
},
"System.IO.Ports": {
"target": "Package",
"version": "[9.0.5, )"
}
},
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.314/PortableRuntimeIdentifierGraph.json"
}
}
},
"D:\\Gitea\\dabitche-mcp\\DibdProtocol\\DibdProtocol.csproj": {
"version": "1.0.0",
"restore": {
"projectUniqueName": "D:\\Gitea\\dabitche-mcp\\DibdProtocol\\DibdProtocol.csproj",
"projectName": "DibdProtocol",
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol\\DibdProtocol.csproj",
"packagesPath": "C:\\Users\\INSU LEE\\.nuget\\packages\\",
"outputPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol\\obj\\",
"projectStyle": "PackageReference",
"configFilePaths": [
"C:\\Users\\INSU LEE\\AppData\\Roaming\\NuGet\\NuGet.Config"
],
"originalTargetFrameworks": [
"net9.0"
],
"sources": {
"C:\\Program Files\\dotnet\\library-packs": {},
"https://api.nuget.org/v3/index.json": {}
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"projectReferences": {
"D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj": {
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Core\\DibdProtocol.Core.csproj"
},
"D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\DibdProtocol.Transport.csproj": {
"projectPath": "D:\\Gitea\\dabitche-mcp\\DibdProtocol.Transport\\DibdProtocol.Transport.csproj"
}
}
}
},
"warningProperties": {
"warnAsError": [
"NU1605"
]
},
"restoreAuditProperties": {
"enableAudit": "true",
"auditLevel": "low",
"auditMode": "direct"
},
"SdkAnalysisLevel": "9.0.300"
},
"frameworks": {
"net9.0": {
"targetAlias": "net9.0",
"imports": [
"net461",
"net462",
"net47",
"net471",
"net472",
"net48",
"net481"
],
"assetTargetFallback": true,
"warn": true,
"frameworkReferences": {
"Microsoft.NETCore.App": {
"privateAssets": "all"
}
},
"runtimeIdentifierGraphPath": "C:\\Program Files\\dotnet\\sdk\\9.0.314/PortableRuntimeIdentifierGraph.json"
}
}
}
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<RestoreSuccess Condition=" '$(RestoreSuccess)' == '' ">True</RestoreSuccess>
<RestoreTool Condition=" '$(RestoreTool)' == '' ">NuGet</RestoreTool>
<ProjectAssetsFile Condition=" '$(ProjectAssetsFile)' == '' ">$(MSBuildThisFileDirectory)project.assets.json</ProjectAssetsFile>
<NuGetPackageRoot Condition=" '$(NuGetPackageRoot)' == '' ">$(UserProfile)\.nuget\packages\</NuGetPackageRoot>
<NuGetPackageFolders Condition=" '$(NuGetPackageFolders)' == '' ">C:\Users\INSU LEE\.nuget\packages\</NuGetPackageFolders>
<NuGetProjectStyle Condition=" '$(NuGetProjectStyle)' == '' ">PackageReference</NuGetProjectStyle>
<NuGetToolVersion Condition=" '$(NuGetToolVersion)' == '' ">6.14.3</NuGetToolVersion>
</PropertyGroup>
<ItemGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<SourceRoot Include="C:\Users\INSU LEE\.nuget\packages\" />
</ItemGroup>
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\9.0.0\buildTransitive\net8.0\Microsoft.Extensions.Configuration.UserSecrets.props" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\9.0.0\buildTransitive\net8.0\Microsoft.Extensions.Configuration.UserSecrets.props')" />
</ImportGroup>
</Project>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)system.text.json\10.0.6\buildTransitive\net8.0\System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json\10.0.6\buildTransitive\net8.0\System.Text.Json.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\10.0.7\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\10.0.7\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\10.0.7\buildTransitive\net8.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\10.0.7\buildTransitive\net8.0\Microsoft.Extensions.Options.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.binder\9.0.0\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.binder\9.0.0\buildTransitive\netstandard2.0\Microsoft.Extensions.Configuration.Binder.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\9.0.0\buildTransitive\net8.0\Microsoft.Extensions.Configuration.UserSecrets.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.configuration.usersecrets\9.0.0\buildTransitive\net8.0\Microsoft.Extensions.Configuration.UserSecrets.targets')" />
</ImportGroup>
</Project>

View File

@@ -0,0 +1,4 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")]

View File

@@ -0,0 +1,24 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("Dabit.Mcp")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+ac1891aedbf9906ac5f44fa8c087ff49e6444d59")]
[assembly: System.Reflection.AssemblyProductAttribute("Dabit.Mcp")]
[assembly: System.Reflection.AssemblyTitleAttribute("Dabit.Mcp")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
[assembly: System.Runtime.Versioning.TargetPlatformAttribute("Windows7.0")]
[assembly: System.Runtime.Versioning.SupportedOSPlatformAttribute("Windows7.0")]
// MSBuild WriteCodeFragment 클래스에서 생성되었습니다.

View File

@@ -0,0 +1 @@
a58c2ac0574aaa40dc89832cefa5ad0f0c2c7f550929d01c266b6eecff930bba

View File

@@ -0,0 +1,16 @@
is_global = true
build_property.TargetFramework = net9.0-windows
build_property.TargetPlatformMinVersion = 7.0
build_property.UsingMicrosoftNETSdkWeb =
build_property.ProjectTypeGuids =
build_property.InvariantGlobalization =
build_property.PlatformNeutralAssembly =
build_property.EnforceExtendedAnalyzerRules =
build_property._SupportedPlatformList = Linux,macOS,Windows
build_property.RootNamespace = Dabit.Mcp
build_property.ProjectDir = D:\Gitea\dabitche-mcp\Dabit.Mcp\
build_property.EnableComHosting =
build_property.EnableGeneratedComInterfaceComImportInterop =
build_property.CsWinRTUseWindowsUIXamlProjections = false
build_property.EffectiveAnalysisLevelStyle = 9.0
build_property.EnableCodeStyleSeverity =

View File

@@ -0,0 +1,8 @@
// <auto-generated/>
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;

View File

@@ -0,0 +1 @@
599f2696c7a3ad4ebfa014e6b19e2ed952ac76c1c22d37596f60595ad29efe1a

View File

@@ -0,0 +1,85 @@
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Dabit.Mcp.exe
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Dabit.Mcp.deps.json
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Dabit.Mcp.runtimeconfig.json
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Dabit.Mcp.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Dabit.Mcp.pdb
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.AI.Abstractions.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Caching.Abstractions.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.Abstractions.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.Binder.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.CommandLine.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.EnvironmentVariables.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.FileExtensions.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.Json.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Configuration.UserSecrets.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.DependencyInjection.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.DependencyInjection.Abstractions.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Diagnostics.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Diagnostics.Abstractions.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.FileProviders.Abstractions.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.FileProviders.Physical.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.FileSystemGlobbing.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Hosting.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Hosting.Abstractions.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.Abstractions.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.Configuration.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.Console.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.Debug.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.EventLog.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Logging.EventSource.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Options.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Options.ConfigurationExtensions.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\Microsoft.Extensions.Primitives.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\ModelContextProtocol.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\ModelContextProtocol.Core.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\MQTTnet.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Diagnostics.DiagnosticSource.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Diagnostics.EventLog.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.IO.Pipelines.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.IO.Ports.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Net.ServerSentEvents.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Text.Encoding.CodePages.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Text.Encodings.Web.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\System.Text.Json.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\android-arm\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\android-arm64\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\android-x64\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\android-x86\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-arm\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-arm64\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-bionic-arm64\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-bionic-x64\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-musl-arm\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-musl-arm64\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-musl-x64\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\linux-x64\native\libSystem.IO.Ports.Native.so
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\maccatalyst-arm64\native\libSystem.IO.Ports.Native.dylib
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\maccatalyst-x64\native\libSystem.IO.Ports.Native.dylib
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\osx-arm64\native\libSystem.IO.Ports.Native.dylib
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\osx-x64\native\libSystem.IO.Ports.Native.dylib
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\win\lib\net9.0\System.Diagnostics.EventLog.Messages.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\win\lib\net9.0\System.Diagnostics.EventLog.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\unix\lib\net9.0\System.IO.Ports.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\win\lib\net9.0\System.IO.Ports.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\win\lib\net9.0\System.Text.Encoding.CodePages.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\browser\lib\net8.0\System.Text.Encodings.Web.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\runtimes\win\lib\net9.0\System.Text.Encodings.Web.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.Core.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.Transport.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.pdb
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.Core.pdb
D:\Gitea\dabitche-mcp\Dabit.Mcp\bin\Debug\net9.0-windows\DibdProtocol.Transport.pdb
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.csproj.AssemblyReference.cache
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.GeneratedMSBuildEditorConfig.editorconfig
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.AssemblyInfoInputs.cache
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.AssemblyInfo.cs
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.csproj.CoreCompileInputs.cache
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.csproj.Up2Date
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\refint\Dabit.Mcp.dll
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.pdb
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\Dabit.Mcp.genruntimeconfig.cache
D:\Gitea\dabitche-mcp\Dabit.Mcp\obj\Debug\net9.0-windows\ref\Dabit.Mcp.dll

Some files were not shown because too many files have changed in this diff Show More