วิธีที่เซิร์ฟเวอร์ MCP เรียก API ของคุณแทนผู้ใช้: กลยุทธ์โทเค็นสำหรับระบบผลิตจริง
อธิบายกลยุทธ์ของโทเค็นขาออกสำหรับเซิร์ฟเวอร์ MCP: ทำไม token passthrough และ M2M ถึงล้มเหลว และโทเค็น exchange รักษาสิทธิ์การเข้าถึงให้ตรงกับสิทธิ์ของผู้ใช้ได้อย่างไร
อธิบายกลยุทธ์ของโทเค็นขาออกสำหรับเซิร์ฟเวอร์ MCP: ทำไม token passthrough และ M2M ถึงล้มเหลว และโทเค็น exchange รักษาสิทธิ์การเข้าถึงให้ตรงกับสิทธิ์ของผู้ใช้ได้อย่างไร
ใน บทความก่อนหน้า เราได้แชร์ประสบการณ์โดยรวมในการสร้าง Logto remote MCP server บทความนี้จะครอบคลุมการออกแบบสถาปัตยกรรมและ flow ของ OAuth อย่างละเอียด
เมื่อ MCP server เป็นเส้นแบ่ง การพิสูจน์ตัวตนสำหรับ remote MCP server จะมีสองขา: ขาเข้า (inbound) และ ขาออก (outbound)
ขาเข้าถูกนิยามไว้อย่างชัดเจนในสเปค MCP มีการพูดคุยและ implementation อยู่มากมายใน community เราก็เขียน คู่มือ implement เอาไว้เช่นกัน ขาออกกลับไม่ค่อยมีใครพูดถึง: MCP server จะมีแต่โทเค็นที่เข้าถึงตัวมันเอง แล้วมันจะเรียก business API ของคุณแทนผู้ใช้ได้อย่างไร?
นี่คือคำถามที่เราคิดไม่ตกตอนสร้าง Logto MCP server Logto Cloud เป็นโปรดักต์ B2B multi-tenant ทั่วไป: ผู้ใช้คนเดียวอยู่ได้หลาย tenant และสิทธิ์การเข้าถึงจะมาจากบทบาทของผู้ใช้ในแต่ละ tenant AI ต้องไม่เพียงแค่แสดงตัวแทนผู้ใช้ ยังต้องเลือก tenant ให้ถูกอีกด้วย
บทความนี้จะดำเนินไปตามลำดับการคิดของเราจริง ๆ:
มองจากมุม OAuth, remote MCP server จะมีสองบทบาทพร้อมกัน:
พูดอีกอย่าง: MCP server เล่นคนละบทบาทในแต่ละฝั่ง และแต่ละฝั่งใช้ โทเค็นคนละตัวกัน โทเค็นที่ MCP client ได้จาก OAuth จะมี MCP server เป็น audience ซึ่ง API ธุรกิจจะปฏิเสธตอนเช็ค audience
ดังนั้น ขาออกก็คือปัญหา delegation: MCP server จะเรียก downstream API แทนผู้ใช้ ในขอบเขตสิทธิ์ผู้ใช้ โดยไม่ต้องเก็บ credentials ผู้ใช้ได้อย่างไร?
ฝัง MCP endpoint ลงใน business API เพื่อ share process และ auth stack ดูเหมือนง่ายสุด แต่หลังชั่งใจแล้ว เราเลือก deploy แยก:
MCP server deploy แยกใน domain ของตัวเอง mcp.logto.io ไม่เกี่ยวกับ service หลักเลย จะ open source ทีหลังก็ไม่มีอุปสรรค
แบบฝังไปกับ business API ก็หนีไม่พ้นปัญหา token เช่นกัน: MCP endpoint กับ business API จะ share resource identifier เดียวกัน โทเค็นที่ MCP client ได้จะมีสิทธิ์ API เต็ม ทุกอย่างขึ้นอยู่กับ "การ call นี้วิ่งด้วยสิทธิ์ของใคร" ซึ่งแค่เปลี่ยนจาก exchange ข้าม service เป็น permission ใน process เท่านั้น สุดท้ายปัญหาก็ยังอยู่ Deploy แยกทำให้เรากำหนดขอบ permission ชัดเจน ซึ่งที่เหลือในบทความนี้จะพูดถึงเรื่องนี้
จะเริ่มอภิปรายขาออกจริง ๆ ก็คือ: audience ของโทเค็นที่ MCP client ได้ควรเป็นอะไร?
ใช้ resource identifier เดียวกับ business API ดูง่ายสุด: Management API ของ Logto เป็น OAuth protected resource อยู่แล้ว MCP client จะขอโทเค็นมันไปใช้ตรง ๆ MCP server forward ไปตรง ๆ หลายระบบ MCP รุ่นแรกก็ทำอย่างนี้
แต่ราคาที่จ่ายคือ: ถ้าโทเค็นของ MCP มี audience เป็น business API, ขอบ permission ก็หายไปแล้ว
ข้อสรุปแรก: MCP server ต้องเป็น resource ที่ป้องกันแยก มี identifier (https://mcp.logto.io) และ scope ของตัวเอง
ข้อดีตรงนี้คือ permission ขอบชัดมาก และทำให้ปัญหาขาออกชัดด้วย: โทเค็นเข้าถึงได้แค่ MCP server แล้วตอน MCP server จะเรียก API ต้องใช้อะไร?
ดูทางเลือกกันต่อ
วิธีแรกเกิดจากความ analog ธรรมชาติ
Logto Console เป็น SPA วิธีเรียก Management API ง่ายสุด: ลงชื่อเข้าใช้ OAuth ใน browser รับ token ที่มี Management API เป็น audience, frontend เรียก API ตรง ๆ
แล้ว MCP server จะทำตัวเหมือน Console ได้ไหม? ให้ MCP client ขอ โทเค็นของ business API ตอน sign in, MCP server forward ไปแบบไม่ต้องเปลี่ยนอะไรเลย:
ข้อดีคือโคตรง่าย: MCP server แค่ forward โทเค็น แต่ปัญหาก็มีชัดเจน:
1. ขัดกับขอบ permission ที่ตัดสินใจไว้ชัด ๆ token passthrough ต้องให้ MCP client ถือโทเค็นของ business API ซึ่งตรงข้ามกับที่เราเพิ่งปฏิเสธ
2. MCP server ไม่ใช่ protected resource จริงอีกต่อไป โทเค็นที่รับมา audience ไม่ใช่ตัวมันเอง, การตรวจ audience เลยหมดความหมาย เหลือแต่ "ตรวจลายเซ็นกับผู้ออก" ขัดกับ definition ของ MCP spec (ควร act เป็น resource server และประกาศด้วย RFC 9728 Protected Resource Metadata) เรียกว่าเอา backend มาหลอกว่าเป็น SPA ใน browser
3. MCP client ไม่ยอมตาม MCP client ที่ถูกสเปค จะขอโทเค็นตาม metadata, audience จะเป็น MCP server ทำให้ไม่มีทางขอโทเค็นของ business API แบบนี้ไม่ได้ในฝั่ง client
ถ้าโทเค็นฝั่งผู้ใช้ใช้ไม่ได้ เอาโทเค็นของ MCP server เองได้ไหม?
ให้ MCP server ลงทะเบียน M2M (machine-to-machine) application ขอ token ด้วย client credentials แล้วใช้เรียก business API – แบบนี้ใช้บ่อยใน service ภายใน
ตอนนี้ขาเข้าก็ไม่มีปัญหา: โทเค็น client มี audience เป็น MCP server, server ตรวจปกติ แล้วใช้โทเค็น M2M ตัวเอง
จุดตกม้าตายคือ สิทธิ์ของ M2M ไม่เกี่ยวกับสิทธิ์ผู้ใช้เลย
M2M เหมาะกับ job ที่ไม่มี context ผู้ใช้ เช่น งานประจำ, sync system กับ system MCP server ไม่เหมือนกัน: ทุก call มีผู้ใช้เป็นคนเริ่ม ต้อง act ด้วยสิทธิ์และ identity ผู้นั้น
เอาปัญหาสองวิธีแรกมารวมกัน จะได้ requirement ของแบบที่ถูกต้อง:
ดังนั้นต้องใช้กลไกมาตรฐาน: token exchange (RFC 8693) MCP server เอาหลักฐานแทนผู้ใช้ แลกเป็นโทเค็นฝั่ง downstream ที่คงสิทธิ์และ identity ของผู้ใช้นั้น ๆ
ใน Logto, "หลักฐานแทนผู้ใช้" คือ user impersonation หรือ subject token เป็น credential อายุสั้นที่ server ขอแทนผู้ใช้ "การแลก token ถัดไปนี้วิ่งเป็น user นี้" ผู้ใช้ไม่ต้องทำหรือ config อะไร flow ทั้งหมดอัตโนมัติ subject token อายุสั้น ใช้ได้ครั้งเดียว หมดอายุทันทีที่ใช้
มี 4 บทบาทในสถาปัตยกรรม MCP server deploy แยกที่ mcp.logto.io:
Flow ของการแลกโทเค็นหลัง call เครื่องมือหนึ่งครั้ง:
ทีละขั้น:
① Inbound validation MCP client เรียก tool พร้อม user token token นี้ audience เป็น resource ID ของ MCP server, scope คือ mcp:all MCP server ตรวจสอบ signature, issuer, audience, scope แล้วได้ identity ผู้ใช้ หยุดที่ inbound token ตัวนี้ไม่เอาไป downstream
② Service identity MCP server ใช้ M2M credentials ขอโทเค็น access ที่ scope เฉพาะ access:mcp:api scope นี้ใช้งานเดียว: เรียก endpoint ต่อไป
③ ขอ subject token คือ key ของ chain ทั้งหมด MCP server เรียก POST /api/mcp/subject-tokens endpoint ที่ Cloud เปิดเฉพาะ MCP โดยให้ credentials สองตัว พร้อมกัน:
Authorization header: M2M token พิสูจน์ตัว "ฉันคือเซิร์ฟเวอร์ MCP"x-mcp-user-token header: user's token พิสูจน์ "ผู้ใช้นี้ authorize จริง และ authorization ยัง valid"Cloud ตรวจสอบ user token: signature, issuer, expiry, audience ต้องเป็น resource ID ของ MCP server, scope ต้อง include mcp:all หลังผ่าน userId เอาจาก sub claim ของ token endpoint นี้ไม่มี parameter เลือก user ได้เอง
ดีไซน์นี้กัน misuse ของ M2M credential ถ้า endpoint รับ userId อะไรก็ได้ ใครถือ credential ก็ปลอมเป็น user ไหนก็ได้ แบบนี้ MCP server จะแลก credential ได้แค่กับ user ที่มีโทเค็น valid เท่านั้น
Subject token ที่ได้อายุสั้น ใช้ครั้งเดียว implementation ไม่ cache เลย ขอใหม่ทุกครั้ง
④ แลก working tokens มี subject token แล้ว เอาไปแลกตามมาตรฐาน ได้ token สองแบบตามที่ต้องการ:
⑤ Outbound call เรียก business API ด้วย exchanged token แล้วส่งผลลัพธ์กลับ MCP client
Multi-tenant context จบในขั้นแลก: tenant อยู่ในขั้น exchange ไม่ใช่ขั้น connection list_tenants list ตัวเลือกด้วย Cloud API token ผู้ใช้เลือกผ่าน chat MCP server แลก org token ให้ tenant ที่เลือก endpoint เดียวรับได้ทุก tenant ไม่ต้องแยก deploy ถ้าสร้าง tenant ใหม่กลางทางก็ใช้ได้เลย
เทียบกับ failure ของสองแบบก่อน จะเห็นว่าแก้ทุกจุด:
x-mcp-user-token ล้มเหลว chain หยุดที่นี่แหละM2M credential ทำหน้าที่เหมาะเลยใน design นี้: ไว้ยืนยัน "ฉันคือใคร" ส่วนความสามารถ "act เป็น user" ต้องแลกทันทีเมื่อมีอำนาจจาก user จริง
ภาพรวม strategy โทเค็นสำหรับ remote MCP server มีไม่กี่จุดที่สำคัญ:
เทสง่าย ๆ: สมมุติ token ฝั่ง MCP client หลุดออกไป สิ่งที่ attacker ทำได้คือ call MCP tools ที่กำหนดไว้เท่านั้น แถมขอบเขตเทียบผู้ใช้ ถ้าใช้เรียก full API ได้ แปลว่า permission boundary พังแล้ว
MCP ecosystem ยัง evolve เร็วมาก Auth inbound ปกป้องไว้ดีแล้ว ส่วน "จะเรียก downstream อย่างไร" ยังขึ้นอยู่กับแต่ละทีม หวังว่ากรณีของเราจะเป็นตัวอย่างที่ช่วยคุณได้
ถ้าคุณจะสร้าง MCP server สำหรับโปรดักต์ตัวเอง ลองดู solutions ของ Logto สำหรับ AI: Auth สำหรับ AI app, agent, MCP server หรือจะลองเล่น token chain นี้จริง ๆ ก็เชื่อมไปที่ Logto MCP server ได้เลย
แหล่งอ้างอิงในบทความนี้: