Tool: MCP bridge — ต่อ MCP server แบบ HTTP หรือ local stdio ระหว่างสนทนา

MCP bridge คือชุด 4 tool ที่ทำให้ Endeavor Hands ต่อ capability เพิ่มได้โดยไม่ต้องเขียน tool ใหม่ใน server.py ทุกครั้ง มีทั้งการต่อ Streamable HTTP และการเปิด local stdio MCP server บนเครื่อง โดย ChatGPT ยังเป็นผู้เลือกว่าจะค้นหาและเรียก tool ปลายทางตัวไหน
สอง registry: developer-managed กับ dynamic
รายการ server ที่ Hands รู้จักมาจากสองแหล่งที่ merge กันตามชื่อ:
config.MCP_SERVERS— รายการที่ developer provision ไว้ใน sourceEndeavor_Hands/work/tool_mcp/servers.json— รายการที่ ChatGPT เพิ่ม/แก้/เอาออกผ่านmcp_add_serverและmcp_remove_server
ถ้าชื่อชนกัน dynamic entry ชนะ และโค้ดยังอ่าน legacy registry ใต้ workspace เดิมเพื่อรักษาของที่ลงทะเบียนไว้ก่อนการย้าย runtime artifacts เมื่อมีการเขียนครั้งใหม่ work/tool_mcp/servers.json จะเป็นตำแหน่ง canonical
การแยก registry สำคัญเพราะ config.py เป็น developer-owned configuration นอก workspace — ChatGPT จัดการ server ที่ตัวเองเพิ่มได้ แต่ไม่สามารถลบรายการ hardcoded ของ developer จากบทสนทนา
สอง transport ที่รองรับ
Streamable HTTP
ส่ง url ที่ขึ้นต้นด้วย http:// หรือ https:// และ optional headers_json ซึ่งเป็น JSON object ของ HTTP headers ระบบเปิด MCP session, initialize, list/call แล้วปิด session ตามรอบที่ขอ ผลลัพธ์ text ถูกจำกัดด้วย MCP_MAX_CHARS (default 4,000 ตัวอักษร) และ timeout ต่อรอบด้วย MCP_TIMEOUT (default 60 วินาที)
Local stdio
ส่ง command ที่เป็น executable path แบบ absolute, args_json เป็น JSON array ของ string และ optional cwd ที่ต้องอยู่ใน approved workspace ระบบจะ spawn ด้วย argv โดยตรง ไม่ผ่าน shell และใช้ macOS sandbox profile เดียวกับ shell ของ Hands จึงไม่เปิดทางให้ MCP child หลุดขอบเขตเพียงเพราะเป็น server ที่ต่อเพิ่ม
ตัวอย่างรูปแบบการลงทะเบียน:
mcp_add_server(
name="local-helper",
command="/absolute/path/to/helper",
args_json="[\"--stdio\"]",
cwd="~/Desktop/my-project"
)
url กับ command ต้องมีอย่างใดอย่างหนึ่งเท่านั้น ถ้าใส่พร้อมกันหรือไม่ใส่เลยจะได้ validation error
ลำดับที่ถูกต้อง: list ก่อน call
ต้องเรียก mcp_list_tools(server) ก่อน เพื่อดูชื่อ tool และ schema/คำอธิบายจาก server ปลายทาง แล้วจึงเรียก:
mcp_call_tool(
server="local-helper",
tool_name="exact_name_from_list",
arguments_json="{\"query\": \"...\"}"
)
ห้ามเดาชื่อ tool หรือ arguments จากความคุ้นเคย เพราะ server แต่ละตัวมี schema ไม่เหมือนกัน การเรียกจาก FastMCP ที่มี event loop อยู่แล้วก็มีเส้นทาง worker thread แยกเพื่อไม่ให้เจอ asyncio.run() ซ้อน event loop และผลลัพธ์ error จะมี diagnostic ช่วยแยกว่าเป็น validation, timeout หรือ child failure
Registry เขียนแบบ crash-safe
mcp_add_server และ mcp_remove_server ไม่เขียน servers.json ทับตรงๆ ขั้นตอนคือ:
- lock registry ด้วย
fcntl.flock()เพื่อกันหลาย process แก้พร้อมกัน - เขียน JSON ลง temp file ชื่อสุ่มด้วย permission
0600 fsyncให้ข้อมูลลงดิสก์os.replace()สลับเป็นไฟล์จริงแบบ atomic
การอ่านเป็น best-effort: ถ้าไฟล์หายหรือ JSON เสียจะถือว่าไม่มี dynamic server เพื่อให้ list ไม่ทำให้ server หลักล่ม แต่การเขียนใช้ strict mode และหยุดเมื่อ registry เดิมเสีย เพื่อไม่ให้การเพิ่ม server ใหม่ล้างรายการเก่าทั้งหมดโดยไม่รู้ตัว
ถ้าเรียก mcp_add_server ด้วยชื่อเดิม จะเป็นการ overwrite URL/headers หรือ command/args/cwd ของ entry นั้นในคำสั่งเดียว ส่วน mcp_remove_server เอาออกได้เฉพาะ entry ที่ dynamic registry สร้างเอง ไม่แตะ config.MCP_SERVERS
ขอบเขตและข้อมูลลับ
URL, headers และ arguments เป็นข้อมูลที่ผู้ใช้ส่งให้ tool โดยตรง อย่าใส่ API key หรือ credential จริงในบทความ/แชทโดยไม่จำเป็น Dynamic registry ตั้ง permission 0600 แต่ยังเป็นไฟล์บนเครื่อง ดังนั้นให้ลงทะเบียนเฉพาะ server ที่ไว้ใจได้และตรวจ mcp_list_tools ก่อนเรียกทุกครั้ง
MCP bridge ไม่ได้ยกเลิก guardrail ของ Hands: local stdio child ยังอยู่ใต้ sandbox, cwd ถูกจำกัดใน workspace และผลลัพธ์ถูก cap/timeout เพื่อไม่ให้ child แขวนหรือส่งข้อความขนาดใหญ่กลับมาโดยไม่สิ้นสุด
ความคิดเห็น
กำลังโหลดความคิดเห็น...