บทความ › AI

บทเรียนจาก MCP server จริง 18 เครื่องมือ ที่ AI ใช้ทำ วิดีโอ

ออกแบบ MCP server ที่ AI ใช้ถูก: ชื่อเป็นกลุ่ม เช็กโควตาก่อนจ่าย งานนานคืน id ส่งไฟล์เป็น path ไม่ใช่ base64 key อยู่ใน env และเครื่องมือเผยแพร่ต้องให้คนยืนยัน

กลาง อ่าน 13 นาที 21 ตุลาคม 2569

อ่านจบแล้วจะรู้
  • ตั้งชื่อเครื่องมือเป็น <กลุ่ม>_<กริยา> และเขียน docstring ให้บอกราคา เวลาที่ใช้ และสิ่งที่ต้องทำก่อน เพราะ AI เลือกเครื่องมือจากข้อความนี้
  • งานที่ใช้เวลาเป็นนาทีให้คืน id แล้วมีเครื่องมือเช็กสถานะแยก ไฟล์ส่งเป็น path ในโฟลเดอร์ร่วม ไม่ใช่ base64
  • key อ่านจาก env เท่านั้น ห้ามรับหรือคืนผ่านเครื่องมือ และเครื่องมือที่ย้อนกลับไม่ได้ เช่น เผยแพร่ ต้องแยกออกและให้คนยืนยันก่อน
  • server ตัวเดียวเปิดได้สอง transport (SSE และ stdio) ให้ระบบ agent คนละแบบใช้โค้ดชุดเดียว

ใน EP6 ของซีรีส์ Claude เราเขียน MCP server 10 บรรทัดให้ AI ค้นบันทึกได้ บทนี้ข้ามไปอีกฝั่ง เล่าจาก server ที่ใช้งานจริงบนเครื่องใน home lab มี 18 เครื่องมือ ให้ทีม agent ใช้ทำวิดีโอสั้นของ DevMakerX ตั้งแต่เขียนบท สร้างเสียง ทำวิดีโอปากขยับ ใส่ซับ จนอัปขึ้น YouTube และ TikTok ของเล็กกับของจริงต่างกันตรงที่ของจริง เสียเงินทุกครั้งที่เรียกผิด ใช้เวลาเป็นนาที และบางเครื่องมือกดแล้วถอยไม่ได้ บทเรียนในบทนี้จึงเป็นเรื่องการออกแบบให้ AI ใช้ถูก ไม่ใช่เรื่องโปรโตคอล

server เขียนด้วย Python 3.11 กับ FastMCP จาก library mcp รันใน Docker image python:3.11-slim ที่ลง ffmpeg และฟอนต์ไทยไว้

ภาพรวม: 18 เครื่องมือ 6 กลุ่ม

กลุ่มเครื่องมือทำอะไรเสียเงิน
character_*list, get, get_script_promptฐานข้อมูลตัวละคร 17 ตัว นิสัย เสียง หน้าตา และ prompt สำหรับเขียนบทไม่
tts_*list_voices, generate_speechแปลงบทไทยเป็นเสียงพูดใช่ ตามตัวอักษร
image_*generate, generate_characterสร้างภาพหน้าตัวละครใช่ ตามเครดิต
video_*check_quota, upload_talking_photo, create_lipsyncวิดีโอปากขยับจากภาพ + เสียงใช่ ตามวินาที
audio_* / video_post_processtranscribe, post_processถอดเสียงเป็นซับ ใส่ซับ เพลง โลโก้ ด้วย ffmpegไม่ รันในเครื่อง
publish_*list_platforms, auth_url, auth_callback, upload_youtube, upload_tiktok, statusOAuth และอัปโหลดไม่ แต่ย้อนกลับไม่ได้
AI agentcharacter_*ข้อมูลตัวละครtts_*เสียงพูดimage_*ภาพvideo_*วิดีโอpost_*ถอดเสียง ซับ เพลงpublish_*โพสต์ขึ้นเว็บต้องมีคนยืนยันvideo_check_quotaเช็กก่อน ฟรีvideo_create_lipsyncเสียเงิน
จัดกลุ่มตามงาน ตัวที่เสียเงินมีตัวเช็กคู่กัน ตัวที่ย้อนกลับไม่ได้แยกไว้

บทเรียน 1: ตั้งชื่อเป็น กลุ่ม_กริยา

AI ไม่เห็นไฟล์โค้ดของเรา มันเห็นแค่รายชื่อเครื่องมือกับคำอธิบาย ถ้าชื่อเป็น generate, make, process, upload ปน ๆ กัน 18 ตัว มันจะเลือกผิดบ่อย พอใช้ชื่อ <กลุ่ม>_<กริยา> รายชื่อจะเรียงเป็นก้อนเอง AI อ่านครั้งเดียวก็เห็นว่าเรื่องเสียงไปที่ tts_* เรื่องอัปโหลดไปที่ publish_*

ข้อดีที่ไม่ได้ตั้งใจคือ แค่เห็นว่ามี publish_auth_url และ publish_auth_callback อยู่ข้าง publish_upload_youtube AI ก็เดาถูกว่าต้องทำ auth ก่อนอัป โดยไม่ต้องเขียนอธิบายยาว

บทเรียน 2: เครื่องมือ “เช็กก่อนจ่าย” วางไว้ข้างเครื่องมือที่เสียเงิน

วิดีโอปากขยับคิดเงินเป็นวินาที โควตาหมดกลางทางแปลว่าเสียงที่ทำไปแล้วเสียเปล่า เราจึงมี video_check_quota อยู่ในกลุ่มเดียวกับ video_create_lipsync และบอกราคากับเวลาไว้ใน docstring ตรง ๆ

@mcp.tool()
def video_check_quota() -> dict:
    """Check remaining lip-sync quota (seconds). Free. Call before video_create_lipsync."""
    return check_quota()

@mcp.tool()
def video_create_lipsync(talking_photo_id: str, audio_path: str, output_name: str | None = None) -> dict:
    """Create a lip-sync video from a face photo and an MP3.

    COSTS quota: about 1 quota second per second of audio. Takes 3-5 minutes.
    Call video_check_quota first; if remaining < audio length, stop and tell the user.
    """

หลักเดียวกันใช้กับ tts_list_voices (ฟรี) ข้าง tts_generate_speech (เสียเงิน) ประโยคสำคัญคือบรรทัดที่บอกว่า เสียอะไร เท่าไหร่ และต้องเรียกอะไรก่อน AI ตัดสินใจจากข้อความนี้ ไม่ใช่จากโค้ดข้างใน

บทเรียน 3: งานนาน คืน id แล้วให้ถามสถานะ

ตอนแรก video_create_lipsync ทำครบในเครื่องมือเดียว อัปเสียง สั่งสร้าง แล้ววนถามสถานะทุก 5 วินาทีจนครบ 5 นาที ผลคือ agent ระบบหนึ่งตัดการเชื่อมต่อก่อนที่งานจะเสร็จ เพราะ timeout เริ่มต้นของมันสั้นกว่านั้น ต้องไปแก้ config ฝั่ง agent ให้รอได้ 300 วินาที ซึ่งเป็นการแก้ที่ผิดที่

แบบที่ถูกคือแบบที่กลุ่ม publish_* ใช้อยู่แล้ว เครื่องมืออัปโหลดคืน publish_id ทันที แล้วมี publish_status แยก

@mcp.tool()
def publish_upload_tiktok(video_path: str, title: str = "", privacy_level: str = "SELF_ONLY") -> dict:
    """Start a TikTok upload. Returns publish_id at once; poll with publish_status."""
    ...
    return {"success": True, "publish_id": publish_id, "status": "PROCESSING"}

@mcp.tool()
def publish_status(platform: str, publish_id: str) -> dict:
    """Check an upload started earlier. Cheap; call every 10-20 s until status is done or failed."""

งานที่นานกว่าหนึ่งนาทีควรแยกเป็น “เริ่ม” กับ “ถามสถานะ” ให้ AI เป็นคนรอ ไม่ใช่ให้การเชื่อมต่อรอ

บทเรียน 4: ส่งไฟล์เป็น path ในโฟลเดอร์ร่วม ไม่ใช่ base64

เสียง 1 นาทีคือ MP3 ประมาณ 1 MB วิดีโอ 1080x1920 หนึ่งนาทีคือ 20-40 MB ถ้าคืนเป็น base64 ในผลลัพธ์ของเครื่องมือ ข้อมูลทั้งก้อนจะวิ่งเข้า context ของ AI ซึ่งทั้งแพงและล้นง่าย

server นี้จึงเขียนไฟล์ลง volume ที่ mount ไว้ แล้วคืนแค่ path ทุกเครื่องมือรับ path เป็นข้อมูลเข้าและคืน path เป็นข้อมูลออก

OUTPUT_DIR = os.environ.get("MEDIA_OUTPUT_DIR", "/data/media")

def generate_speech(text, voice_id, output_name="speech", **settings) -> dict:
    out_dir = Path(OUTPUT_DIR) / "audio" / date.today().isoformat()
    out_dir.mkdir(parents=True, exist_ok=True)
    out = out_dir / f"{output_name}.mp3"
    out.write_bytes(call_tts_api(text, voice_id, settings))
    return {"success": True, "path": str(out), "size_bytes": out.stat().st_size}

เครื่องมือถัดไปตรวจก่อนทำงานทุกครั้งว่าไฟล์มีจริง และคืน error เป็นข้อความอ่านง่าย {"success": False, "error": "Audio not found: ..."} AI จะอ่านแล้วรู้ว่าต้องย้อนไปขั้นก่อน

บทเรียน 5: key อยู่ใน env เท่านั้น

server มี key ของสามบริการและ OAuth client ของอีกสองแพลตฟอร์ม ทั้งหมดอ่านจาก environment variable ตอนเริ่ม ไม่มีเครื่องมือไหนรับ key เป็น argument และไม่มีเครื่องมือไหนคืน key ออกมา แม้แต่เครื่องมือเช็กสถานะก็คืนแค่ว่า “มี” หรือ “ไม่มี”

API_KEY = os.environ.get("HEYGEN_API_KEY", "")

def check_quota() -> dict:
    if not API_KEY:
        return {"success": False, "error": "HEYGEN_API_KEY not set"}
    ...

def list_connected() -> list[dict]:
    return [{"platform": p, "has_credentials": bool(os.environ.get(cfg["client_id_env"]))}
            for p, cfg in PLATFORMS.items()]

เหตุผลไม่ใช่แค่กันรั่ว ทุกอย่างที่ผ่านเครื่องมือจะไปอยู่ใน log ของ agent ใน context ของโมเดล และบางระบบส่งต่อไปโมเดลเจ้าอื่น key ที่เคยผ่าน argument แม้ครั้งเดียวก็ถือว่าหลุดแล้ว token จาก OAuth เก็บเป็นไฟล์ใน volume ที่ mount แยก server ต่ออายุเองเมื่อหมด AI ไม่เคยเห็น token

บทเรียน 6: สอง transport จากโค้ดชุดเดียว

ที่บ้านมีระบบ agent สองแบบ แบบแรกรองรับ MCP ผ่านเครือข่าย (SSE) แบบที่สองรับแต่ stdio คือรันเป็นโปรแกรมลูกแล้วคุยผ่าน stdin/stdout แต่ server ต้องอยู่ในคอนเทนเนอร์ของตัวเองเพราะมี ffmpeg โมเดลถอดเสียง 1.5 GB และ key ครบอยู่ที่นั่น

ทางออกคือ server เปิด SSE บนพอร์ตหนึ่งเป็นทางหลัก ส่วนระบบที่รับแต่ stdio ให้ใช้ mcp-proxy ซึ่งเป็นโปรแกรมเล็ก ๆ ที่หน้าตาเป็น stdio กับ agent แต่ต่อ SSE ไปหา server อีกฝั่ง โค้ด server รองรับทั้งสองแบบด้วยบรรทัดเดียว

transport = os.environ.get("TRANSPORT", "sse")
mcp.run(transport=transport)   # "sse" หรือ "stdio"

ฝั่ง agent ที่รับแต่ stdio ตั้งค่าแบบนี้ (ที่อยู่ของ server เป็นชื่อในเครือข่าย Docker ภายใน)

media:
  command: uvx
  args: ["mcp-proxy", "http://<media-mcp-in-docker-network>/sse"]

ได้ server ชุดเดียว ไม่ต้องดูแลสองสำเนา และพอร์ต SSE ผูกไว้กับ 127.0.0.1 ของเครื่อง ไม่เปิดออกนอก

บทเรียน 7: เครื่องมือเผยแพร่ ย้อนกลับไม่ได้

tts_generate_speech เรียกผิดเสียเงินไม่กี่บาท แต่ publish_upload_youtube เรียกผิดคือวิดีโอครึ่ง ๆ กลาง ๆ ขึ้นช่องจริง เราทำสามอย่าง

  1. แยกเป็นกลุ่ม publish_* ของตัวเอง ไม่มีเครื่องมือ “ทำวิดีโอแล้วอัปเลย” ตัวเดียวจบ
  2. ค่าเริ่มต้นเป็นแบบส่วนตัวเสมอ privacy_status="private" และ SELF_ONLY ถ้า AI ไม่ได้ส่งค่ามา วิดีโอจะไม่มีใครเห็น
  3. docstring บอกชัดว่าต้องให้คนยืนยันก่อน และกฎของทีม agent บอกว่าห้ามเปลี่ยนเป็น public เองทุกกรณี
@mcp.tool()
def publish_upload_youtube(video_path: str, title: str, description: str = "",
                           privacy_status: str = "private", is_short: bool = False) -> dict:
    """Upload a video to YouTube. IRREVERSIBLE: the video appears on the real channel.

    Before calling: show the user the final video path, title and privacy, and get an
    explicit yes. Default privacy is "private"; never set "public" without the user asking.
    """

ถ้า API รองรับ ก็ใส่ป้ายบอกว่าเป็นสื่อสังเคราะห์ด้วย ซึ่งทั้งสองแพลตฟอร์มมีให้ตั้ง

ตัวอย่างจริง: จากหัวข้อถึงวิดีโอพร้อมซับ

AI ได้คำสั่ง “ทำคลิป 60 วินาทีให้ Lynn พูดเรื่องเคล็ดลับพิมพ์ 3D” สิ่งที่มันทำโดยไม่มีใครบอกลำดับ

  1. character_get_script_prompt("lynn", topic="3D printing tips", duration_sec=60) ได้ prompt กลับมา แล้วเขียนบทเองประมาณ 900 ตัวอักษร (บทไทยพูดได้ราว 15 ตัวอักษรต่อวินาที)
  2. tts_generate_speech(text=บท, agent_id="lynn") server หาเสียงจากโปรไฟล์ตัวละครให้เอง ได้ /data/media/audio/2026-10-21/lynn.mp3 ขนาด 0.9 MB
  3. video_check_quota() เหลือ 310 วินาที บทยาว 61 วินาที พอ จึงเรียก video_create_lipsync(talking_photo_id, audio_path=".../lynn.mp3") ได้ /data/media/videos/2026-10-21/lynn.mp4
  4. audio_transcribe(".../lynn.mp3") ได้ 14 ช่วงพร้อมเวลา แล้ว video_post_process(input_video=".../lynn.mp4", subtitle_segments=..., logo_text="DevMakerX") ได้ lynn_final.mp4

จบที่ AI รายงาน path ของไฟล์สุดท้ายกับคนก่อน ไม่แตะ publish_* เอง สังเกตว่าทุกขั้นคุยกันด้วย path ล้วน ๆ และขั้นที่ 4 ไม่ต้องเสียเงิน จะแก้ซับแล้วรันใหม่กี่รอบก็ได้

ข้อผิดพลาดที่เจอบ่อย

  • เครื่องมือเดียวทำครบตั้งแต่สร้างจนอัปโหลด AI จึงไม่มีจุดหยุดให้คนตรวจ
  • docstring บอกแค่ “สร้างวิดีโอ” ไม่บอกว่าเสียเงิน ใช้เวลากี่นาที หรือต้องเรียกอะไรก่อน
  • บล็อกการเชื่อมต่อนานหลายนาทีแล้วไปแก้ timeout ฝั่ง client แทนที่จะคืน id
  • คืนไฟล์เป็น base64 หรือใส่ไฟล์ทั้งก้อนใน argument
  • รับ key หรือ token ผ่าน argument “เพื่อความสะดวก” แล้วมันไปอยู่ใน log ของทุกระบบที่ผ่าน
  • ค่าเริ่มต้นของเครื่องมือเผยแพร่เป็น public
  • เขียนลิมิตของบริการไว้แค่ในหัว เช่น จำนวนภาพใบหน้าที่อัปได้ ควรเขียนไว้ใน docstring ให้ AI เห็นก่อนเรียก

คำถามที่พบบ่อย

ทำไมไม่รวมเป็นเครื่องมือเดียวชื่อ make_video แล้วให้ server ทำทุกขั้นเอง

ทำได้ แต่จะเสียสองอย่าง หนึ่งคือจุดที่คนตรวจระหว่างทาง เช่น ฟังเสียงก่อนจ่ายเงินทำวิดีโอ สองคือความยืดหยุ่น เครื่องมือเล็ก ๆ ที่ส่ง path ต่อกันให้ AI จัดลำดับใหม่ได้เอง เช่น ทำซับจากเสียงเดิมโดยไม่ต้องสร้างวิดีโอใหม่

AI ลืมเรียกเครื่องมือเช็กโควตาก่อนทำวิดีโอได้ไหม

ได้ docstring ช่วยให้มันเรียกบ่อยขึ้น แต่ไม่ใช่การบังคับ ถ้าต้องการให้แน่ ให้เครื่องมือที่เสียเงินเช็กโควตาเองในโค้ดอีกชั้นแล้วปฏิเสธเมื่อไม่พอ ส่วนเครื่องมือเช็กแยกยังมีไว้ให้ AI วางแผนก่อนเริ่ม

ใช้ stdio อย่างเดียวได้ไหม ทำไมต้องมี SSE

ถ้า AI รันบนเครื่องเดียวกับ server ใช้ stdio ได้เลย แต่พอ server อยู่ในคอนเทนเนอร์ที่มี ffmpeg โมเดลถอดเสียง และ key ส่วน agent อยู่อีกคอนเทนเนอร์ ต้องต่อผ่านเครือข่าย SSE จึงเป็นทางหลัก และ stdio ผ่าน proxy มีไว้ให้ระบบที่รับแต่ stdio

คำสั่งและค่าตั้งในบทความทดสอบบนระบบตัวอย่าง ลองบนเครื่องทดสอบก่อนใช้กับระบบจริงเสมอ

อ่านต่อ

#AIขั้นสูง

MCP แบบ Code Mode: ให้ AI เขียนโค้ดเรียก tool แทนเรียกทีละครั้ง

แทนการเรียก MCP tool ทีละครั้งแล้วอ่านผลทุกก้อน ให้ AI เขียนโค้ดเรียก tool เอง ตัวอย่าง 150,000 เหลือ 2,000 token, Code Mode ของ Cloudflare และราคาที่ต้องจ่าย

อ่าน 8 นาที
#AIเริ่มต้น

EP6 MCP ให้ AI มีมือ เขียนเครื่องมือครั้งเดียว ใช้ได้กับ AI หลายตัว

MCP คือมาตรฐานที่ให้ AI ต่อกับระบบอื่น เช่น ฐานข้อมูลหรือระบบบทความ เขียน MCP server ตัวเล็กใน 10 บรรทัด ต่อเข้า Claude Code และสิ่งที่ต้องระวังก่อนให้ AI แตะระบบจริง

อ่าน 4 นาที
#AIเริ่มต้น

AI Agent คืออะไร ต่างจากแชทบอทธรรมดาตรงไหน

อธิบาย AI Agent แบบเข้าใจง่าย มันทำงานเป็นวงรอบยังไง ใช้เครื่องมืออะไรได้บ้าง เหมาะกับงานแบบไหน และความเสี่ยงที่ต้องคุมก่อนให้มันแตะระบบจริง

อ่าน 2 นาที