# meet-transcripts — سرور MCP زیرنویس Google Meet سرور پایتونی که caption های Google Meet را از افزونه‌ی Chrome می‌گیرد و آن‌ها را به‌صورت transcript تمیز و **SRT** به Claude (از طریق MCP) می‌دهد. در یک پراسس دو کار می‌کند: ``` Google Meet (content script) │ chrome.runtime.sendMessage({type:"TRANSCRIPT_UPDATE", …}) ▼ افزونه: service worker (bridge.js) │ WebSocket ws://127.0.0.1:8765 ▼ ws_server.py ──▶ storage.py ──▶ transcripts/.{srt,txt,json} ▲ mcp_server.py (MCP stdio) ─────────┘ ──▶ Claude Desktop ``` ## ساختار | فایل | کار | |------|-----| | `storage.py` | مدل داده، dedup (بر اساس `startedAt`)، رندر SRT/TXT، خواندن/نوشتن دیسک | | `ws_server.py` | سرور WebSocket؛ caption را از افزونه می‌گیرد و به storage می‌دهد | | `mcp_server.py` | **نقطه‌ی ورود**؛ WebSocket + MCP را با هم اجرا می‌کند | | `_smoke_test.py` | تست بدون Chrome | ## اجرا (با venv) ```powershell cd meet-transcripts py -m venv .venv .\.venv\Scripts\Activate.ps1 pip install -r requirements.txt ``` ```powershell python mcp_server.py # WebSocket + MCP (حالت عادی / برای Claude Desktop) python mcp_server.py --ws-only # فقط WebSocket python ws_server.py # فقط WebSocket (معادلِ بالا، برای تست دستی) ``` > همه‌ی لاگ‌ها روی **stderr** اند چون stdout برای پروتکل MCP (JSON-RPC) رزرو است. > برای تستِ دستیِ WebSocket از `ws_server.py` یا `--ws-only` استفاده کن (وگرنه پراسس > منتظر اتصال MCP روی stdin می‌ماند). ## تست بدون Chrome ```powershell # ترمینال ۱: python ws_server.py # ترمینال ۲: python _smoke_test.py ``` باید `PONG` + چند `ACK` بگیری و `transcripts/meeting-abc123.{srt,txt,json}` ساخته شود. ## اتصال به Claude Desktop (MCP) به `claude_desktop_config.json` اضافه کن (مسیر را با مسیر واقعی عوض کن): ```jsonc { "mcpServers": { "meet-transcripts": { "command": "C:\\...\\audio-voice-converter\\meet-transcripts\\.venv\\Scripts\\python.exe", "args": ["C:\\...\\audio-voice-converter\\meet-transcripts\\mcp_server.py"] } } } ``` Claude Desktop خودش `mcp_server.py` را اجرا می‌کند؛ همان پراسس WebSocket را هم بالا می‌آورد. اگر یک نمونه‌ی دیگر از قبل پورت ۸۷۶۵ را گرفته باشد، این نمونه فقط MCP را سرو می‌کند و transcript ها را از روی دیسک می‌خواند (مشکلی پیش نمی‌آید). ### ابزارها / resourceها | ابزار | کار | |-------|-----| | `list_sessions` | فهرست جلسه‌ها + تعداد segment + آخرین خط | | `get_status(session_id?)` | **چک سبکِ تغییر** بدون متن: `latest_seq`, `count`, `updated_at` | | `get_updates(session_id?, after_seq)` | **خواندن افزایشی**: فقط segmentهای بعد از `after_seq` | | `get_latest_transcript()` | متن تمیزِ آخرین جلسه (با `[mm:ss]`) | | `get_transcript(session_id)` | متن تمیزِ یک جلسه | | `get_latest_srt()` / `get_srt(session_id)` | زیرنویس SRT | | `transcript://{id}` , `srt://{id}` | resourceها | ### خواندن افزایشی (به‌جای خواندن کل transcript هر بار) هر segment یک `seq` یکتا دارد. برای دنبال‌کردن یک جلسه‌ی زنده بدون خواندن دوباره‌ی همه‌چیز: ۱. `get_status` بزن (خیلی ارزان، بدون متن). اگر `latest_seq`/`updated_at` عوض شد → ۲. `get_updates(after_seq=)` تا فقط موارد جدید + segmentِ در‌حالِ‌تکمیل را بگیری. ## dedup و SRT — چه‌طور کار می‌کند افزونه برای هر «حرف» چند بار پیام می‌فرستد (snapshotِ روبه‌رشد) ولی `startedAt` ثابت می‌ماند. سرور با همین `startedAt` می‌فهمد این‌ها یک segment‌اند و فقط کامل‌ترین نسخه را نگه می‌دارد — پس متن تکراری ذخیره نمی‌شود. چون زمان شروع/پایان داریم، برای هر segment یک بلوک SRT با تایم‌کد ساخته می‌شود. برای هر جلسه: `.srt` (زیرنویس)، `.txt` (متن خوانا با `[mm:ss]`)، `.json` (داده‌ی خام برای resume). ## پروتکل پیام‌ها افزونه → سرور: | پیام | توضیح | |------|-------| | `{type:"PING", ts}` | heartbeat هر ۲۰ ثانیه | | `{type:"TRANSCRIPT_UPDATE", sessionId, speaker, text, startedAt, endedAt}` | یک caption | سرور → افزونه: | پیام | توضیح | |------|-------| | `{type:"PONG", ts}` | پاسخ heartbeat | | `{type:"ACK", ok:true}` | تأیید دریافت |