""" mcp_server.py — نقطه‌ی ورود. WebSocket و MCP را با هم در یک پراسس اجرا می‌کند. - ws_server.run_forever() به‌عنوان background task (گرفتن caption از افزونه) - FastMCP روی stdio (تا Claude Desktop transcript را بخواند) هر دو حافظه/فایل‌های مشترکِ storage را می‌بینند. اجرا: pip install -r requirements.txt python mcp_server.py # WebSocket + MCP (برای Claude Desktop) python mcp_server.py --ws-only # فقط WebSocket (یا مستقیم: python ws_server.py) کانفیگ Claude Desktop به همین فایل اشاره می‌کند (پایین README). نکته: stdout برای پروتکل MCP رزرو است؛ همه‌ی لاگ‌ها در storage.log به stderr می‌روند. """ import asyncio import json import sys from mcp.server.fastmcp import FastMCP import storage from storage import ( log, safe_session_id, latest_session, read_txt, read_srt, read_segments, json_mtime, fmt_clock, list_session_files, ) import ws_server # --------------------------------------------------------------------------- # MCP # --------------------------------------------------------------------------- def build_mcp() -> "FastMCP": mcp = FastMCP("meet-transcripts") @mcp.tool() def list_sessions() -> str: """فهرست جلسه‌های ضبط‌شده (از روی دیسک): تعداد segment و آخرین خط.""" files = list_session_files() if not files: return "هیچ جلسه‌ای هنوز ضبط نشده است." latest = latest_session() out = [] for p in files: lines = p.read_text(encoding="utf-8").splitlines() last = lines[-1] if lines else "" mark = " ← آخرین" if p.stem == latest else "" out.append(f"- {p.stem}{mark}: {len(lines)} segment | آخرین: {last[:80]}") return "\n".join(out) @mcp.tool() def get_status(session_id: str = "") -> str: """وضعیت سبک برای «چیزی تغییر کرد؟» بدون خواندن متن. اگر session_id ندهی، آخرین جلسه. خروجی JSON: latest_seq، count، live_len، updated_at. اگر latest_seq/updated_at عوض شد، get_updates بزن.""" sid = safe_session_id(session_id) if session_id else latest_session() if not sid: return json.dumps({"session_id": None, "latest_seq": 0, "count": 0, "live_len": 0, "updated_at": 0}, ensure_ascii=False) segs = read_segments(sid) return json.dumps({ "session_id": sid, "latest_seq": segs[-1]["seq"] if segs else 0, "count": len(segs), "live_len": len(segs[-1].get("text", "")) if segs else 0, "updated_at": round(json_mtime(sid), 3), }, ensure_ascii=False) @mcp.tool() def get_updates(session_id: str = "", after_seq: int = 0) -> str: """خواندن افزایشی: فقط segmentهای از after_seq به بعد (نه کل transcript). بار اول after_seq=0 (همه را می‌گیری). دفعات بعد latest_seqِ قبلی را بده تا فقط موارد جدید + segmentِ زنده را بگیری. خروجی JSON: {latest_seq, count, segments:[{seq,t,speaker,text}]}.""" sid = safe_session_id(session_id) if session_id else latest_session() if not sid: return json.dumps({"session_id": None, "latest_seq": 0, "count": 0, "segments": []}, ensure_ascii=False) segs = read_segments(sid) base = min((s["start"] for s in segs), default=0) sel = [s for s in segs if s.get("seq", 0) >= after_seq] if after_seq > 0 else segs out = [{"seq": s.get("seq", 0), "t": fmt_clock(s["start"] - base), "speaker": s.get("speaker", ""), "text": (s.get("text") or "").strip()} for s in sel] return json.dumps({ "session_id": sid, "latest_seq": segs[-1]["seq"] if segs else 0, "count": len(segs), "segments": out, }, ensure_ascii=False) @mcp.tool() def get_latest_transcript() -> str: """متن تمیزِ آخرین جلسه (با [mm:ss]، بدون تکرار).""" sid = latest_session() if not sid: return "هنوز هیچ caption ای دریافت نشده است." return read_txt(sid) or "" @mcp.tool() def get_transcript(session_id: str) -> str: """متن تمیزِ یک جلسه با sessionId (با [mm:ss]).""" txt = read_txt(session_id) return txt if txt is not None else f"جلسه‌ای با id «{safe_session_id(session_id)}» نیست." @mcp.tool() def get_latest_srt() -> str: """زیرنویس SRT آخرین جلسه (با تایم‌کد استاندارد).""" sid = latest_session() if not sid: return "هنوز هیچ caption ای دریافت نشده است." return read_srt(sid) or "" @mcp.tool() def get_srt(session_id: str) -> str: """زیرنویس SRT یک جلسه با sessionId.""" srt = read_srt(session_id) return srt if srt is not None else f"جلسه‌ای با id «{safe_session_id(session_id)}» نیست." @mcp.resource("transcript://{session_id}") def transcript_resource(session_id: str) -> str: return read_txt(session_id) or "" @mcp.resource("srt://{session_id}") def srt_resource(session_id: str) -> str: return read_srt(session_id) or "" return mcp # --------------------------------------------------------------------------- # اجرا # --------------------------------------------------------------------------- async def main(): asyncio.create_task(ws_server.run_forever()) log(f"📁 transcripts در: {storage.TRANSCRIPTS_DIR}") ws_only = "--ws-only" in sys.argv if FastMCP is not None and not ws_only: log("🔗 MCP server (stdio) فعال است — منتظر اتصال Claude Desktop …") await build_mcp().run_stdio_async() else: if FastMCP is None and not ws_only: log("ℹ️ پکیج mcp نصب نیست؛ فقط WebSocket اجرا می‌شود (pip install mcp).") await asyncio.Future() if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: log("\n👋 خاموش شد.")