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

505
CLAUDE.md Normal file
View File

@@ -0,0 +1,505 @@
# CLAUDE.md — DabitChe 모노레포 루트
LED 전광판 콘텐츠 제작 + 스케줄 관리 + 컨트롤러 전송 소프트웨어 (다빛솔루션 DABIT)
## 프로그램 정체성
DabitChe는 **LED 전광판 운영 소프트웨어**이다. 핵심 기능은 콘텐츠 제작(텍스트/이미지) → 스케줄 편집(PLA/BGP) → 컨트롤러 전송이다. 통신설정, 제어, 시뮬레이터는 보조 기능이다. dbSimulator(DBPS)는 dabitche의 부분집합으로, 프로토콜 시뮬레이터 기능에 해당한다. 사용자는 현장 설치기사/운영자이며, 프로토콜을 몰라도 사용 가능해야 한다.
## Naming 규칙 (3층) — 회사 / 제품 / 프로토콜
이름은 3층으로 나눈다. 새 프로젝트·모듈·MCP 명명 시 이 규칙을 따른다.
| 층 | 이름 | 적용 대상 | 예 |
|----|------|----------|-----|
| 회사/브랜드 | **Dabit** (다빛솔루션 DABIT) | 하드웨어 컨트롤러를 다루는 브랜드 레벨 자산 | MCP 서버 `Dabit.Mcp` (등록명 `dabit-led`) |
| 제품 | **DabitOne** (레거시명 = DabitChe) | PC 운영 앱 | `DabitOne.Desktop`, `DabitOne.AutoSend` |
| 프로토콜 | **DibdProtocol** | 제품 무관 프로토콜/전송 라이브러리 | `DibdProtocol.Core` / `.Transport` |
- **레거시 잔재 주의**: 폴더·namespace·sln은 아직 `DabitChe.*`(Desktop/AutoSend). 단 출력물(exe `DabitOne.exe`)·창 제목은 이미 `DabitOne`. 내부 `DabitChe``DabitOne` 전체 리네임은 **워크트리 다수 동시 진행**이라 충돌 위험 → main 단일 big-bang으로 일괄 처리 예정(보류).
- `Dabit.Mcp`가 형제 `DabitOne.*`와 이름이 다른 건 **의도된 계층 구분**(브랜드 vs 제품)이지 실수가 아니다. (2026-06-02 MCP만 선행 리네임: `DabitChe.Mcp``Dabit.Mcp`, 등록명 `dabitche-led``dabit-led`)
## UI/UX 원칙
| 원칙 | 설명 |
|------|------|
| **가로 우선 레이아웃** | 콘텐츠 편집 + 스케줄 그리드가 핵심 → 가로 공간을 넉넉히 확보 |
| **좁은 레이아웃 금지** | dbSimulator(580px 고정)의 좁은 배치를 그대로 가져오지 않음 — 가로 900px+ 기준으로 재배치 |
| **GroupBox 가로 병렬** | 통신설정 등에서 Serial/TCP/UDP를 세로로 쌓지 말고 가로 2~3열로 배치 |
| **입력 필드 폭 제한** | COM 포트(5글자), IP(15글자), Port(5글자) → 필드 폭을 내용에 맞춤, 우측 여백 과다 금지 |
| **전체 통일감** | 한 패널의 배치를 바꾸면 나머지 패널도 동일 원칙 적용 |
| **사이드바 5그룹** | 통신 → 설정 → 시뮬 → 편집 → 고급 (사용 순서 = 위→아래) |
| **Design Token System** | `Themes/` 폴더에 토큰 + implicit style 집중. **토큰 1곳 수정 → 전체 반영**이 핵심. 정본: `AI_Rules/080_ui-design-system.md` |
| **창 크기 기준** | 새 Window/팝업의 크기 요청 시 **메인 창(MainWindow) 대비 비율**로 확인. 예: "메인의 2/3", "메인의 1/2". 비율 미지정 시 사용자에게 "메인 창 대비 몇 분의 몇으로 할까요?" 확인 후 적용 |
### Design Token 필수 규칙
| 규칙 | 이유 |
|------|------|
| 인라인 FontSize/Height/Background/Foreground 금지 | 토큰 파일에서만 관리해야 전역 변경 가능 |
| RowDefinition Height="Auto" 필수 | 고정값은 Control.Margin을 찌그러뜨림 |
| 모든 입력 컨트롤에 FontWeight="Normal" 명시 | GroupBox SemiBold 상속 차단 |
| Window 태그에 `Background="{StaticResource Surface.Background}"` | Window는 implicit style 불가 |
| 새 컨트롤 추가 시 Themes/ implicit style 확인 | 토큰 시스템 밖에서 컨트롤이 생성되면 불일치 발생 |
### Design Token 현재 값 (참고)
값은 `Themes/_Typography.xaml`, `_Spacing.xaml`, `_Colors.xaml`에서 관리. 변경 시 해당 파일만 수정.
| 토큰 | 현재 값 | 파일 |
|------|--------|------|
| `Control.Height` | 26 (MinHeight) | `_Typography.xaml` |
| `Control.Margin` | 0,4,0,4 | `_Spacing.xaml` |
| `Font.Size.MD` | 12 | `_Typography.xaml` |
**Button 규칙:**
- 전역 스타일이 `MinHeight=26`, `Padding="12,0"`으로 설정됨 — **ComboBox/TextBox와 동일 높이**
- **인라인 Padding 금지** — 버튼마다 다른 Padding을 넣으면 높이가 달라져 이질적. 전역 스타일만 사용
- 인라인 `FontWeight`, `FontFamily`, `Height` 지정 금지 — 전역 스타일(`Themes/Button.xaml`)에서 관리
- 예외적으로 큰 버튼이 필요하면 Named Style(x:Key)로 별도 정의. 인라인 Padding은 여전히 금지
## 핵심 규칙
- **한국어 응답**, 간결함 최우선 (3~5줄), 인사말/서론/결론 생략
- **PowerShell 7.0+** 필수 (`pwsh.exe`), Bash/Linux 명령어 금지
- **C# nullable enable** (WPF), **TypeScript strict** (Electron)
- camelCase/PascalCase/SCREAMING_SNAKE_CASE, UTF-8 LF
- **Conventional Commits**: `<type>(<scope>): <desc>` (scope: protocol, ui, transport, settings, desktop, electron, test, docs)
- 환각 방지: 근거 부족 시 명시, 불확실 시 추측 표기 + 대안 제시
### AI 코딩 4대 원칙
1. **Think Before Coding** — 불확실하면 추측하지 말고 먼저 확인
2. **Simplicity First** — 요청받은 것만 구현
3. **Surgical Changes** — 수정 대상만 변경
4. **Goal-Driven Execution** — 명령보다 목표로 지시
### 모듈화(Modularity) 원칙
핵심 기능은 나중에 독립 앱/CLI/REST API로 추출할 수 있게 만든다:
- **Service 계층에 WPF 타입(Window, Dispatcher, Application 등) 직접 참조 금지** — UI 알림은 Result<T> 반환 후 ViewModel이 처리
- **DibdProtocol/은 플랫폼 무관 라이브러리로 유지** — System.Windows 참조 절대 금지
- **새 Service 추가 시 `DialogService.Instance` 직접 호출 금지** — IDialogService 주입 또는 ViewModel에서 처리
- **`DibdProtocol.Transport/Rpc/`** — DIBD JSON-RPC 2.0 envelope 96 method 본문, namespace `Dabit.LedProtocol` (Blazor WASM 회피 영역이라 Transport 어셈블리 적재, 0517a 트랙)
### 영속성(Persistence) 필수 규칙
새 기능/UI 추가 시 반드시 확인:
- **사용자가 입력한 모든 설정값은 앱 재시작 후에도 유지**되어야 한다
- 새 입력 컨트롤(ComboBox, TextBox, CheckBox 등) 추가 시 → AppSettings + config.properties 저장/복원 구현 필수
- 저장 경로: `AppSettings``PropertiesConfigRepository``config.properties`
- 슬롯 구조 데이터는 `SettingService.SetProperties()` 배치 저장 사용
- 앱 종료 시 `App.OnExit()`에서 자동 저장됨 — 새 ViewModel 추가 시 여기에 SaveSettings 호출 추가
## FORBIDDEN (19개)
위반 시 현장 장비 오동작 또는 아키텍처 붕괴. 예외 없음.
### 프로토콜/바이너리 (F3F5, F7F8, F16F19)
| ID | 금지 | 이유 |
|----|------|------|
| F3 | OPCode 값 변경 금지 | 컨트롤러 펌웨어가 고정 OPCode에 의존 |
| F4 | HEX 프레임(DLE-STX) 포맷 변경 금지 | 컨트롤러 파싱 고정 |
| F5 | ASCII 프레임(`![...!]`) 포맷 변경 금지 | 레거시 호환 |
| F7 | `pendingDle` 상태 제거 금지 | TCP 버퍼 경계 DLE(0x10) 분할 시 패킷 손실 |
| F8 | `DLE_OPCODES` Set 변경 금지 | C# `ShouldApplyDle()`과 정확히 일치 |
| F16 | DisplaySignal 시그널 코드 변경 금지 | 70+ 모델이 의존 |
| F17 | CrcCalculator 수정 금지 | 펌웨어 CRC 테이블과 일치 필수 |
| F18 | MSchedule 25바이트 구조 변경 금지 | 펌웨어 `memcpy` 직접 매핑 |
| F19 | tsParameter 792바이트 구조/오프셋 변경 금지 | 바이너리 레이아웃 고정 |
### INI/설정 (F1F2, F9, F11)
| ID | 금지 | 이유 |
|----|------|------|
| F1 | INI 키 오타 수정 금지 (`ColorOder`, `ShowInfomation`, `PorotocolType`) | 현장 장비 하위 호환성 |
| F2 | Boolean INI 값을 `true`/`false` 변경 금지 | 레거시 앱·컨트롤러가 `0`/`1`만 읽음 |
| F9 | API Key/비밀번호 하드코딩 금지 | 환경변수 사용 필수 |
| F11 | AppSettings static 필드 직접 수정 금지 | `IConfigRepository` 경유 필수 |
### 아키텍처 (F6, F10, F12F15)
| ID | 금지 | 이유 |
|----|------|------|
| F6 | BrowserRouter 사용 금지 | Electron file:// 미지원, HashRouter 필수 (WPF 해당 없음) |
| F10 | Bash 명령어 금지 | PowerShell 7.0+ 사용 |
| F12 | SerialTransport `portLock`(SemaphoreSlim) 우회 금지 | 동시 접근 시 포트 충돌 |
| F13 | DI 외부에서 Service 직접 생성 금지 | DI 컨테이너 일관성 |
| F14 | ViewModel에서 직접 Transport 호출 금지 | Service 계층 경유 필수 |
| F15 | MQTT 토픽명 변경 금지 (`/db_msg`, `/db_set`, `/db_sch`, `/db_msg_r`, `/db_sch_r`) | 장비 구독 고정 |
### 읽기 전용
| 금지 | 이유 |
|------|------|
| `references/DabitChe-V2.2.1/` 소스 수정 | 읽기 전용 레거시 참조 — 마이그레이션 대조 기준 훼손 |
## Architecture
```
dabitche/
├── DabitChe.Desktop/ ← [NEW] C# .NET 9.0 + WPF + CommunityToolkit.Mvvm
│ ├── Views/ │ XAML UI (19 FU 매핑)
│ ├── ViewModels/ │ MVVM ViewModel
│ ├── Services/ │ 비즈니스 로직
│ ├── Config/ │ AppSettings, IConfigRepository
│ └── Helpers/ │ EventBus, Result<T>
├── DabitChe.Desktop.Tests/ ← xUnit 데스크톱 테스트
├── DibdProtocol/ ← [SHARED] 프로토콜 라이브러리 (Desktop + Simulator 공유)
│ ├── Protocol/ │ HexCommand, AsciiCommand, PacketBuilder, PacketParser
│ ├── Codec/ │ CrcCalculator, DleFramer, BccCalculator, EncodingConverter
│ ├── Transport/ │ ITransport, Serial/TCP/UDP/MQTT/BT
│ ├── Model/ │ DisplayConfig, ConnectionState, MessageSlot
│ └── Image/ │ DatBuilder, RgbConverter
├── DibdProtocol.Tests/ ← xUnit 프로토콜 테스트
├── DabitChe.Electron/ ← [현행] Electron 33 + React 19 + Vite 6 + Zustand 5
│ ├── src/main/ │ Electron 메인 (IPC, services, transport)
│ ├── src/renderer/ │ React UI (10 pages, 44 components, 8 stores)
│ └── src/shared/ │ 공유 타입/프로토콜/i18n
├── references/ ← 참조 자료 집중
│ ├── DabitChe-V2.2.1/ │ [읽기 전용] C++ Builder 6 레거시
│ ├── DabitChe_Java/ │ [로컬 참조] Java 포팅 (git 추적 안 함)
│ ├── Font/ │ .fnt + FontConverter (릴리즈에 복사)
│ ├── firmware/ │ .bin 펌웨어 (릴리즈에 복사)
│ └── DabitChe_UserManual_v9.0.pdf
└── docs/ ← 통합 문서
```
### 전환 전략
- **현행**: DabitChe.Electron 운영 중
- **목표**: DabitChe.Desktop(C# WPF)으로 점진적 대체
- **공유**: DibdProtocol/ 라이브러리를 Desktop과 dbSimulator가 공유
- 프로토콜 엔진은 DibdProtocol/에 집중, 앱별 중복 구현 금지
## Build & Test
```powershell
# ── C# WPF (새) ──
cd DabitChe.Desktop
dotnet build
dotnet test ../DibdProtocol.Tests
dotnet test ../DabitChe.Desktop.Tests
# ── Electron (현행) ──
cd DabitChe.Electron
npm test # Vitest 387+개
npm run test:e2e # Playwright E2E
npm run dev # Vite dev (:5173)
npm run dev:electron # 전체 앱
npm run package:win # NSIS 인스톨러
```
### DibdProtocol.Core 변경 시 브라우저 안전성 게이트
`DibdProtocol.Core/` 하위 소스를 수정한 커밋은 CI(`.gitea/workflows/ci.yml`)에서
다음을 자동 실행·검증한다.
1. `dotnet build DabitChe.sln --configuration Release`
2. `dotnet test DibdProtocol.Tests` + `dotnet test DibdProtocol.Core.Tests`
3. `dotnet publish DibdProtocol.Core.BlazorCheck/... --configuration Release`
3번이 실패하면 Core에 브라우저 비호환 API(System.IO.Ports / System.Net.Sockets /
MQTTnet 등)가 섞인 것. 로컬에서도 Core 수정 후
`dotnet publish DibdProtocol.Core.BlazorCheck -c Release` 수동 실행을 권장한다.
`dotnet build`만으로는 WASM 링커 회귀를 감지 못 한다.
**단, publish 성공은 "WASM 링커·트림 통과"까지만 증명하고, 브라우저에서
`Pages/Home.razor``OnInitialized()` smoke가 실제 실행됨을 보장하지는 않는다.**
런타임 증명이 필요하면 `dotnet run --project DibdProtocol.Core.BlazorCheck`
브라우저에서 5-way smoke(CRC·DLE·EUC-KR·byte-font·ASCII)를 육안 확인하거나,
Phase 1+에서 Playwright 1회 접속 step을 CI에 추가한다.
## Hooks
DabitChe.Electron에 Claude Code hooks가 설정되어 있다.
| 패턴 | 동작 |
|------|------|
| 커밋 전 품질 게이트 | git commit 시 `npm run quality` 자동 실행, 실패 시 차단 |
| FORBIDDEN 위반 감지 | 파일 수정 시 FORBIDDEN 패턴 정규식 매칭, 위반 시 경고 |
| /careful 안전 모드 | `.claude/.careful` 존재 시 위험 명령 차단 |
| Gotchas 자동 수집 | 세션 종료 시 빌드/테스트 실패 패턴 분석 |
## 로그 작성 원칙
`LogService`는 4 레벨(Info/Warn/Error/Debug)을 제공한다. UI는 평소 Info/Warn/Error만 표시하고, Debug는 로그 헤더의 "Debug 보기" 토글(`MainWindow.xaml`)을 켜야 보인다. 호출부에서 적절한 레벨을 선택해 노이즈를 줄인다.
### 레벨 분류 기준
- **Info** — 사용자 시나리오 흐름. 작업 시작/완료, 핵심 결과, 세션 라이프사이클. **TX/RX 단발 명령은 Info 유지** (업체가 시스템 구현 시 protocol 참고용 — `_logService.Info($"TX: 시간동기화 ASCII ![0030...!]")` 같은 라인은 의도적으로 보임)
- **Warn** — 사용자 조치가 필요한 비정상. 파일 누락, 응답 status 비정상, 펌웨어 거부 가능성
- **Error** — 작업 실패. 통신 끊김, 예외
- **Debug** — 평소 OFF, 트러블슈팅 시 ON. 패킷 단위 hex 덤프(`TX[cmd] NB → RX MB (Tms)`), 반복 호출 진행률(블록 N/M), 분기 결정 trace, 동일값 반복 entry 출력
### 연속 전송 (펌웨어/폰트/이미지 다블록)
블록 단위 진행률은 **Debug**. 시작·요약(블록 수)·완료만 **Info**.
```
Info: 펌웨어 전송 시작 219블록
Debug: 펌웨어 전송: 1/219 (0%) ← 블록 단위 진행률
Debug: 펌웨어 전송: 51/219 (23%)
...
Info: 펌웨어 전송 완료
```
### 임시 디버그 (`DEBUG-TEMP`)
특정 버그 추적·신규 기능 검증을 위해 박은 일회성 로그는 다음 중 하나로 마킹한다:
1. 메시지 접두사: `_logService.Debug($"[DEBUG-TEMP] ...")`
2. 코드 주석: `// DEBUG-TEMP: <목적>, <날짜>`
검증 완료 시점에 두 갈래로 정리:
- "환경에 따라 또 깨질 수 있나?" (예: TCP fragment, 재부팅 응답) → **영구 Debug로 강등**`[DEBUG-TEMP]` 접두사 제거하고 `_logService.Debug`만 유지
- "한번 검증으로 끝났나?" (예: 특정 byte offset 확인) → **제거**
월 1회 또는 PR 직전 `git grep "DEBUG-TEMP"`로 잔존 검토.
### 영구 Debug 후보 — 시스템 경계
코드 안쪽 깊은 곳의 trace는 임시 디버그(검증 후 제거)지만, **시스템 경계의 객관적 사실**은 영구 Debug로 둔다:
- 외부 I/O raw byte (TX/RX hex, latency `(Tms)`)
- 분기 결정 (어떤 path 탔는지: legacy vs modern, ImageDir fallback 등)
- 재시도/fallback 작동 (`ack timeout, retry 2/3`)
- 외부 환경 로드 결과 (`config.properties NN키 로드, 인코딩=EUC-KR`)
- 변환 입출력 byte count (`ConvertToSplitDat: 3549B → 612B+893B`)
기준 한 줄: "사용자 환경에서만 재현되는 문제를 원격 진단할 때 결정적 단서인가?"
## Data Flow Chains (흐름 기반 탐색)
함수 시그니처는 AI 도구(grep/glob)로 직접 탐색한다.
### C# WPF (DabitChe.Desktop + DibdProtocol)
| 시나리오 | 호출 체인 |
|---------|----------|
| UI → 컨트롤러 명령 | View → ViewModel → Service → DibdProtocol.Protocol → DibdProtocol.Transport |
| 프로토콜 수신 파싱 | Transport → PacketParser → Service → ViewModel → View |
| INI 설정 로드 | Config → IConfigRepository → fs (EUC-KR → UTF-8) |
| 파일 블록 전송 | Service → 블록 분할 (1024B) → Protocol → Transport |
| 스케줄 파일 | Service → PLA 직렬화/역직렬화 (리틀엔디언) → fs |
### Electron (DabitChe.Electron)
| 시나리오 | 호출 체인 |
|---------|----------|
| UI → 컨트롤러 명령 | Renderer → preload (contextBridge) → IPC → DeviceCommandService → HexProtocolBuilder → Transport |
| 프로토콜 수신 파싱 | Transport → ProtocolParser (상태 머신) → CRC 검증 → DeviceCommandService → IPC → Renderer |
| INI 설정 로드 | Renderer → IPC → IniSettingsService → fs.readFileSync → iconv.decode (EUC-KR) |
| 파일 블록 전송 | Renderer → IPC → 블록 분할 (1024B) → DeviceCommandService → HexProtocolBuilder → Transport |
| 스케줄 파일 | Renderer → IPC → ScheduleFileService → PLA 직렬화/역직렬화 (리틀엔디언) |
## Symptom → Where to Look
| 증상 | 확인 위치 (C# WPF) | 확인 위치 (Electron) |
|------|--------------------|--------------------|
| 프로토콜 바이트 불일치 | `DibdProtocol/Protocol/` | `src/shared/protocol/` |
| UI 렌더링 문제 | `DabitChe.Desktop/Views/` | `src/renderer/pages/`, `src/renderer/components/` |
| 통신/시리얼 문제 | `DibdProtocol/Transport/` | `src/main/services/transport/` |
| INI 설정 문제 | `DabitChe.Desktop/Config/` | `src/main/services/settings/ini-settings-service.ts` |
| 빌드/테스트 실패 | `dotnet build` 출력 | `DabitChe.Electron/package.json` 스크립트 |
| 레거시 동작 참조 | `references/DabitChe-V2.2.1/CLAUDE.md` | 동일 |
## Decisions (왜 이렇게 했는지)
| 결정 | 이유 |
|------|------|
| C++ Builder → C# → Electron → C# WPF | 레거시 65개 TForm 직접 포팅 불가, 단계적 재작성 |
| dbSimulator DibdProtocol 공유 | 프로토콜 엔진 중복 개발 방지 |
| INI 오타(`ColorOder` 등) 보존 | 현장 장비 하위 호환성 유지 |
| INI 읽기 EUC-KR → 저장 UTF-8 | 점진적 인코딩 전환 |
| BCD 시간 인코딩 | 레거시 컨트롤러 RTC 호환 |
| 13바이트 밝기 payload | 시간대별 밝기 + CDS 센서 레거시 구조 |
| HexProtocolBuilder C# byte-identical | V3.0.0(C#)과 바이트 일치 보장 |
| Electron IPC preload.ts 경유 | contextIsolation 보안 모델 준수 |
| WPF ViewModel → Service → Transport | MVVM 계층 분리, Transport 직접 호출 금지 (F14) |
| DI 컨테이너 필수 | Service 생명주기 관리 일관성 (F13) |
## Markdown 작성 규칙 (`docs/` 편집 시)
1. **Frontmatter 필수**: title, tags, last_updated 3필드
2. **H1 1개**, 레벨 스킵 금지 (H2→H3 순서)
3. **첫 문장 = 문맥 앵커**: H2 직후 첫 문장에 주어+문맥 포함
4. **한 문단 = 한 줄** (Git Diff 가독성), 코드블록 독립 (언어 태그 필수)
5. **TL;DR**: 문서 상단에 무엇/왜/어떻게 3줄 요약
## Deep Context (심층 문서)
심볼 검색은 AI 도구(grep/glob)에 맡기고, 문서에는 코드가 말 못 하는 것만 담는다.
| 작업 유형 | 읽을 문서 |
|----------|----------|
| 정본 매트릭스 (SSOT) | `docs/00_AI_가이드/README.md` |
| 로드맵 | `docs/00_AI_가이드/10_ROADMAP.md` |
| 프로젝트 구조 | `docs/00_AI_가이드/11_프로젝트컨텍스트.md` |
| 코딩 패턴 | `docs/00_AI_가이드/20_CODE_PATTERNS.md` |
| 모듈/프로토콜 참조 | `docs/00_AI_가이드/30_MODULE_INDEX.md` |
| 워크플로우 절차 | `docs/00_AI_가이드/40_WORKFLOW.md` |
| 스킬 레퍼런스 | `docs/00_AI_가이드/41_SKILL_REFERENCE.md` |
| 빌드 함정/에러 패턴 | `docs/00_AI_가이드/50_BUILD_ERRORS.md` |
| 테스트 카탈로그 | `docs/00_AI_가이드/60_TEST_CATALOG.md` |
| 자율 루프 | `docs/00_AI_가이드/70_AUTONOMOUS_LOOP.md` |
| 시각 검증 | `docs/00_AI_가이드/72_VISUAL_VERIFY.md` |
| DeepDive (모듈별) | `docs/00_AI_가이드/Modules/` |
| 플랜+회고 | `docs/00_AI_가이드/Decisions/` |
| 프로토콜 바이트 명세 | `docs/01_protocols/protocol-reference.md` |
| 레거시 참조 | `references/DabitChe-V2.2.1/CLAUDE.md` |
| UI 디자인 시스템 | `AI_Rules/080_ui-design-system.md` (토큰 정의, 컴포넌트 스펙, 적용 절차) |
### PM 시스템 (장기 매니저 운영 + 자동 분업)
DabitONe의 통합 매니저 역할은 별도 문서·슬래시 커맨드·claude agents로 운영된다. 새 세션에서 PM 직책을 인수하려면 `/pm-start`로 부트스트랩, 종료 직전 `/pm-end`로 인계. 작업 분해 후 PM이 `@worker-isolated`, `@researcher`, `@code-reviewer`, `@dabitone-protocol-expert`를 자동 호출.
**시간축 (PM 시스템 문서·커맨드):**
| 산출물 | 용도 |
|--------|------|
| `PROJECT_BRAIN.md` (루트) | 현재 동적 상태 — 진행·보류·우선순위. 매 세션 갱신 |
| `docs/PM/PM_CONTEXT_MAP.md` | 영역 색인 — "어디에 뭐가 있나" |
| `docs/PM/PM_PROTOCOL.md` | PM 운영 룰 — 4-Role·검토·팀빌딩·가드레일 |
| `docs/PM/templates/spec.md` | Worker brief 표준 템플릿 |
| `docs/PM/templates/retrospective.md` | Phase 회고 템플릿 |
| `/pm-start` | PM 세션 부트스트랩 슬래시 커맨드 |
| `/pm-end` | PM 세션 종료 + `PROJECT_BRAIN.md` 갱신 |
**공간축 (claude agents 4종 — `.claude/agents/`):**
| Agent | 역할 | 호출 시점 |
|-------|------|---------|
| `worker-isolated` | 격리된 worktree에서 spec.md 따라 한 Phase·기능 구현. 빌드·테스트·commit | PM이 Phase 분해 후 호출 |
| `researcher` | 외부 자료·라이브러리·기술 비교 조사. WebFetch·WebSearch | 결정 분기 전 호출 |
| `code-reviewer` | Worker 완료 코드 독립 검토 (worker 대화 안 봄, 확증편향 회피) | Worker 완료 후 자동 호출 |
| `dabitone-protocol-expert` | DIBD 프로토콜 + 콘텐츠 포맷(DAT/PLA/BGP/ANI) + 전송 절차 전문가 | 프로토콜·콘텐츠·전송 영역 변경 시 자동 호출 |
**시행 강제 (Hook 1개 — `.claude/hooks/`):**
| 산출물 | 역할 | 호출 시점 |
|--------|------|---------|
| `r-pm-1-guard.ps1` | **R-PM-1 시행 강제** — PM 직접 grep/glob/read 3회 경고·5회 차단 | PreToolUse 자동 (`.claude/.pm-session-active` 존재 시 발동) |
**R-PM 직책 한도 (PM 세션에서 매번 준수)**:
- **R-PM-1**: 다중 grep/glob/read 직접 금지 — sub-agent(Explore/general-purpose)에 위임 필수. Hook이 3회 경고·5회 차단으로 시행 강제
- **R-PM-2**: 단일 핵심 문서 Read·작은 fix·사용자 인터뷰·PROJECT_BRAIN 갱신은 PM 직접 OK
- **R-PM-5**: 사용자 이미 답한 내용 재질문 금지·다음 단계 진입 전 명시 동의 받기·"잘못됐다" 지적 시 즉시 중단·결정 항목은 1~3개 선택지로 좁힘
- **R-PM-7**: Evidence Tier 강제 — 모든 결론 ✅사실(카메라/실기 확인) / 🟡추정(코드·논리 미검증) / ❌가정(미입증) 3분류 자동 분리. **코드 변경 ≠ 검증.** 사용자가 "근거는?" 묻기 *전에* 자발 분리. 사례: 2026-05-26 retry 정책 — PM이 코드 변경만 보고 "회복 + 견고" 단정했다가 사용자 지적
- 상세: `docs/PM/PM_PROTOCOL.md` 2.5 섹션, `AI_Rules/100_pm-system.md` "PM 직책 한도 R-PM-7"
**🚨 세션 종료 직전 반드시 `/pm-end`** — `PROJECT_BRAIN.md` 갱신 누락은 다음 PM 세션의 인계 단절 = 시스템 무결성 손상.
정본 가이드: `D:\Note\20_Marketing_Team\23_Resource\AI_Rules\100_pm-system.md` + `101_pm-system-guide.md`
### 프로토콜 점검 필수 참조
프로토콜 전송 코드(패킷 빌드, 바이트 매핑, msgType, /P 페이지 등)를 수정할 때 **반드시** 아래 5개 문서를 검토한다:
| 문서 | 용도 |
|------|------|
| `docs/01_protocols/ascii-reference.md` | ASCII 명령어 전체 목록 + 속성코드 |
| `docs/01_protocols/hex-protocol.md` | HEX 프레임 구조 + DLE 이스케이프 |
| `docs/01_protocols/hex-reference.md` | HEX 명령별 실전 예시 |
| `docs/01_protocols/protocol-reference.md` | DIBD 완전 레퍼런스 (ASCII+HEX 통합) |
| `docs/01_protocols/protocol-schema.json` | AI 파싱용 구조화 스키마 |
### 콘텐츠 편집 필수 참조 — **🚨 강제 규칙**
콘텐츠 편집(`TextEditorWindow`, `DatFileService`, `LegacyDatHeader`, `ConvertToSplitDat` 등) 관련 코드를 **수정·추가·리팩토링**할 때, 작업 전에 **반드시** 아래 문서를 읽는다. 호환성 가정을 깨뜨리면 레거시 파일을 읽을 수 없게 되어 마이그레이션이 실패한다.
| 문서 | 용도 |
|------|------|
| `docs/00_AI_가이드/85_EDITOR_LEGACY_COMPAT.md` | 레거시 분할 DAT 호환성 현황 + 파싱 누락 지점 + 수정 제안 |
| `docs/01_protocols/dat-bgp-pla-textbook.md` | DAT/PLA/BGP 바이트 포맷 전체 정의 (분할 DAT 2-섹션 구조 포함) |
| `memory/project_split_block_format.md` | 분할 블록 펌웨어 검증 기록 (Option[0], BFormKind=2, dsrCnt 필수) |
**원칙**: 바이트 포맷 호환성은 이미 맞춰져 있으므로 깨지 않는다. 파싱 로직은 누락되어 있으므로 수정 시 이 문서의 수정 범위(P0/P1/P2)를 따른다.
## Gotchas
- **Gitea 릴리즈 한국어 깨짐 방지** — Git Bash curl로 한국어 JSON 전송 시 인코딩 깨짐. 반드시 `pwsh.exe -NoProfile`에서 `ConvertTo-Json` + `[System.Text.Encoding]::UTF8.GetBytes()` + `Invoke-RestMethod` 조합 사용. curl 금지
- **ASCII 페이지 메시지 msgType은 항상 "1"** — 페이지 번호는 `/P{xx}` 속성(0-based)으로 지정. msgType에 페이지 번호를 넣으면 안됨
- **분할 DAT Section 2에 TextInfo 넣으면 XY 어긋남** — FCH(32B)만 기록, TextInfo(256B) 금지
- **BFormKind=1 대신 2 사용** — 펌웨어가 BFormKind=1을 다르게 처리하여 좌표 계산 오류 발생
- **분할 시 Option[0]=1 필수** — 누락 시 Y 5px 어긋남
- **분할 시 dsrCnt EDEffect 최소 0x01** — 0이면 컨트롤러가 분할을 인식 못함
- **레거시 분할 DAT의 Section1 FCH `RH`는 "메인 블록 폭"** — 전체 폭이 아님. H1F 샘플 파일의 `RH=16`인데 실제 전광판 폭은 96. `DatFileService.Load`가 현재 이를 전체 폭으로 오해하여 레거시 분할 파일을 못 읽음. 수정 전 반드시 `docs/00_AI_가이드/85_EDITOR_LEGACY_COMPAT.md` 참조
- **레거시 DAT 열기는 사이드카 기반** — `TextEditorWindow.OnLoadDat``.blocks.json` 사이드카가 없으면 `BackgroundImagePath`에 경로만 할당하고 TextInfo 파싱을 건너뜀. 레거시 파일(사이드카 없음) 호환 작업 시 이 경로 수정 필요
- **레거시 BGP/PLA의 dat 참조는 bare 파일명** — V2.x는 `.bgp`(`Data\Playlist\`)와 `.dat`(`Data\Image\`)이 다른 폴더. `ScheduleFileService.ParseLegacyBinary`가 BGP 같은 폴더만 뒤지면 못 찾고 bare name(`"test_r.dat"`)으로 fallback → 전송 단계 `File.Exists` 실패 → 0블록 전송 → 컨트롤러 슬롯 모두 동일 메모리(블록 0) 가리킴 → "1~7번 모두 RED" 증상. 수정 후 해석 우선순위: **`AppPaths.ImageDir`/`VideoDir`(레거시 V2.x DestTextPath와 동일) → BGP 같은 폴더 → bare name**. `ScheduleTransferService.ResolveContentPath`가 전송 단계에서도 한번 더 구제(2026-05-07 fix)
- **펌웨어/폰트 청크 전송은 ACK 대기 필수 — Fire-and-forget 금지** — 시리얼/TCP에서 컨트롤러 처리 지연(플래시 쓰기 등)으로 일부 청크가 누락되면 침묵 회귀(전송 성공 같지만 미적용·한글 폰트 비트맵 손상). `FirmwareService` + `FontSettingsViewModel`의 청크 루프는 반드시 `AckPolicy.Required` + 청크별 3회 retry. timeout warning이 가끔 뜨더라도 **무결성 > 속도**. 정본: 67f1fe88(Fire-and-forget 도입)의 회귀 fix (2026-05-09 카메라 검증 — 한글 폰트가 색 채움 형태로 깨졌던 증상이 ACK 대기 복원으로 해결). 향후 속도 개선 시 청크 간 sleep 등 다른 방향으로 (ACK 대기는 유지)
- **FNT 폰트 글리프 시작 ASCII는 글리프 수로 분기** — `FntRenderer.RenderEngGlyph``asciiCode * glyphSize`로 lookup하지만 폰트 파일별 시작 ASCII가 다름. 256/128 글리프 폰트(DABIT 표준, Gothic, Roman 8x16 등)는 ASCII 0부터, **95 글리프 폰트(ENG 16x16-ASCII-Roman/RN90, ENG 16x32-굴림체 등)는 ASCII 0x20(space)부터** 저장. 95 글리프는 `glyphIdx = asciiCode - 0x20` 보정 필요. 미리보기에서 "1" 입력 시 "Q"/"R" 같은 다른 글자가 나오면 이 보정 누락. 헤더 byte 12=W, byte 13=H, byte 14=glyphSize(B). 시작 ASCII는 헤더 명시 없어 글리프 수로 추정. 정본: FntRenderer 2026-05-10 fix
- **.NET 7+ SerialPort timeout은 IOException** — `SerialPort.ReadAsync` timeout이 .NET 6까지 `TimeoutException`이었으나 .NET 7부터 **`IOException`**으로 throw (GitHub dotnet/runtime#81035). 외부 catch가 `TimeoutException`만 잡으면 .NET 9 환경에서 회귀 — 시리얼 일시 timeout이 외부 retry 발동 안 함. transport retry 정책 작업 시 catch에 `IOException` + `SocketException` 추가 필수. `TransferSessionService.IsRetriable`은 이미 세 가지 다 잡으나, `FirmwareService` + `FontSettingsViewModel`의 청크 외부 retry는 좁아져 있을 수 있음. 정본: memory `reference_dotnet7_serialport_breaking`, 인계 doc `docs/00_AI_가이드/Decisions/2026-05-26-transport-retry-attempt-revert.md`
### Phase 1b Parser + VirtualControllerCore Gotchas (2026-04-21 v1.2 설계·플랜 반영)
- **Parser는 wire-faithful — text canonicalization 금지** — `PacketParser.TryParseDisplayMessagePayload`는 decode된 문자열을 **원문 그대로** 반환해야 한다. `\r`(0x0D) 제거 / `\n`→공백 변환 같은 canonicalization을 **parser 내부에서 절대 하지 말 것**. M07(CR/LF 원문 전송) Inv-1 byte stability를 보장하려면 wire-faithful 필수. Canonicalization은 `DisplayMessageSpecWireCanonicalizer.CanonicalizeForRoundTrip`에서만 수행
- **`CharColorCodec.Normalize`에 3-bit 마스킹 추가 금지** — Phase 1a `CharColorCodecTests``{1, 2, 9, 9, 9}` 보존을 기대하므로 마스킹 추가 시 breaking. 3-bit (fg·bg ∈ [0, 7]) 정책은 **Phase 1b `DisplayMessageSpecWireCanonicalizer`에만** 적용. Phase 1a 정본 Codec은 건드리지 않는다
- **`VirtualControllerCore.ApplyHexFrame`에는 framed packet만 — payload 단독 입력 금지** — `ApplyHexFrame``DleFramer.TryDeframe`을 경유하므로 입력은 `DLE STX DST LEN CMD DATA DLE ETX` 전체. payload(0x94 data)만 넣으면 `ApplyResult.MalformedFrame` 반환. payload만 있으면 `ApplyHexParsed(HexParsedResponse)`로 직접 진입할 것. 테스트 `FrameVsPayloadContractTests`가 계약 오사용을 검증
- **Empty-run canonical form 고정** — 빈 텍스트(M18, payload.Length == 16) payload는 `DefaultFg/DefaultBg/CharColors/FontGroup` 복원 불가. Parser와 `DisplayMessageSpecWireCanonicalizer` 모두 **`Text="", CharColors=null, DefaultFg=0, DefaultBg=0, FontGroup=Group0`** 으로 고정. `DisplayMessageSpecStructuralComparer`를 빈 텍스트 특례로 약화시키지 말 것
- **`VirtualControllerCore`는 Phase 1b에서 단일 스레드 전용** — 내부 `Dictionary`·`List` 기반이라 `Apply*`/`Snapshot`/`GetSection` concurrent 호출 시 레이스 → 동작 미정의. 호출자가 UI 스레드 or 전용 worker로 직렬화해야 한다. Phase 2+에서 concurrent 필요 시 `lock` 또는 `ConcurrentDictionary` 전환 검토. 구현 시 XML doc에 반드시 "Thread-safety: Phase 1b 단일 스레드 전용" 명시
- **`ControllerState.Sections`/`GetSection`은 deep-copy snapshot** — `ImmutableDictionary`/`ImmutableArray`는 컨테이너 불변성만 보장. `DisplayMessageSpec.Run.CharColors.Colors`가 가변 `int[]`이므로 `SectionManager.CloneSpec`에서 `Array.Copy`로 복제해야 내부 상태 aliasing 차단. Snapshot/GetSection 호출자는 반환값의 `NormalizedCharColors.Colors[0] = 999` 변경이 내부에 영향 주지 않음을 가정하고 설계. `SnapshotAliasMutationTests` 2건이 계약을 증명
## 커밋 시 문서 업데이트 규칙 — **🚨 강제 규칙**
### MANDATORY: 커밋 전 체크리스트
**커밋하기 직전에 반드시 다음 4가지를 확인한다. 하나라도 놓치면 커밋 금지.**
```
☐ 1. 프로토콜 바이트 구조 변경/발견이 있으면 → docs/01_protocols/*.md 업데이트
☐ 2. 레거시 대비 새 기능이면 → docs/00_AI_가이드/80_LEGACY_ENHANCEMENTS.md 업데이트
☐ 3. AI가 반복 실수한 패턴이면 → CLAUDE.md Gotchas 섹션 업데이트
☐ 4. 펌웨어 동작 검증 결과면 → memory/project_*.md 추가
```
### 발견 유형별 업데이트 대상
| 발견 유형 | 업데이트 대상 |
|----------|-------------|
| 프로토콜 바이트 구조, DAT/PLA/BGP/ANI 포맷 | `docs/01_protocols/dat-bgp-pla-textbook.md` |
| HEX/ASCII 명령 응답 포맷 | `docs/01_protocols/hex-reference.md`, `ascii-reference.md` |
| 레거시 대비 변경/확장 기능 | `docs/00_AI_가이드/80_LEGACY_ENHANCEMENTS.md` |
| AI가 반복한 실수 패턴 | 이 파일의 `Gotchas` 섹션 |
| 펌웨어 동작 검증 결과 | `memory/` (project 타입 메모리) |
| 새 ViewModel/Service 패턴 | `docs/00_AI_가이드/20_CODE_PATTERNS.md` |
### 강제 시행 절차
**커밋 프로세스:**
1. **코드 변경 완료**`git diff`로 변경 내용 확인
2. **체크리스트 순회** → 4가지 업데이트 대상 중 해당하는 것 식별
3. **문서 업데이트** → 발견 사실을 해당 문서에 기록
4. **문서도 함께 스테이징**`git add <code files> <doc files>`
5. **커밋 실행** → 코드+문서를 같은 커밋에 포함
### 위반 시 복구 절차
**이미 코드만 커밋했고 문서를 빼먹은 경우:**
```powershell
# 직후 커밋으로 문서 추가
git add docs/
git commit -m "docs: [커밋해시] 후속 — {발견 내용} 반영"
```
### Why 이 규칙이 강력한가
- **반복 실수 방지**: 세션마다 레거시 바이트를 재분석하면 시간 낭비
- **다음 세션 컨텍스트**: 문서가 최신이어야 다른 세션/에이전트가 올바른 정보로 작업
- **장기 유지보수**: 6개월 후 이 코드를 볼 때 "왜 이렇게 했지?"에 답해야 함
- **검증 이력**: "컨트롤러 검증 완료 (날짜)" 표시로 회귀 방지
### 구체적 사례 (2026-04-10)
ANI 파일 포맷 수정 세션에서 놓친 것:
- ✗ FCDavHeader 16B (32B가 아님) — 레거시 소스에서 발견
- ✗ TextInfo가 프레임 데이터 뒤에 옴 (앞이 아님)
- ✗ BlockText[1] Y=16 하드코딩 — 레거시 TFormMessageText.cpp:1936
- ✗ 0x9B BGP 비디오 opcode — 문서에 미기재
- ✗ HEX SetScreenSize 응답 [0xF1, rows, cols] — 기존 문서 [status] 오기재
**교훈**: 레거시 소스(`references/DabitChe-V2.2.1/`)를 분석했다면 그 발견을 즉시 텍스트북에 반영해야 다음 세션에서 같은 분석을 반복하지 않는다.

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