Server Primitives
สาม primitive สามผู้ควบคุม
หัวข้อที่มีชื่อว่า “สาม primitive สามผู้ควบคุม”MCP server เปิดสิ่งให้ใช้ได้แค่สามประเภทเท่านั้น บน wire มันหน้าตาคล้ายกัน — แต่ละอย่างมี method */list และมีวิธีดึงมาทีละตัว — แต่ต่างกันตรง ใครเป็นคนตัดสินใจว่าจะใช้เมื่อไร คำถามข้อนี้ข้อเดียวคือการตัดสินใจด้านการออกแบบที่สำคัญที่สุดของโมดูลนี้
| Primitive | ควบคุมโดย | UI ที่พบบ่อย | Method family |
|---|---|---|---|
| Tools | model — LLM ตัดสินใจเรียกเองจากบริบทของบทสนทนา | หน้ายืนยัน tool call, ตัวบอกสถานะ “กำลังรัน…” | tools/list, tools/call |
| Resources | application — host เป็นคนเลือกว่าจะแนบ context อะไร | file picker, @-mention, tree view | resources/list, resources/templates/list, resources/read |
| Prompts | user — คนเลือกใช้เองอย่างชัดเจน | slash command, เมนู template | prompts/list, prompts/get |
flowchart LR LLM["Language model"] -- "ตัดสินใจเรียก" --> T["Tools"] App["Host application"] -- "เลือก context" --> R["Resources"] User["Human user"] -- "เลือกเองชัดเจน" --> P["Prompts"] T --> S["MCP server"] R --> S P --> S
protocol ไม่ได้ บังคับ interaction model เหล่านี้ — host จะปล่อยให้ model เลือก resource เองหรือรัน prompt อัตโนมัติก็ได้ แต่ primitive ถูกออกแบบมารอบ ๆ แนวคิดนี้ และ server ที่ดีที่สุดจะเคารพเจตนาของมัน: tool คือ action ที่ model อาจทำ, resource คือ ข้อมูลที่ app ยื่นให้ model ได้, prompt คือ workflow ที่คนเป็นคนเรียก
ควรใช้ primitive ตัวไหน?
หัวข้อที่มีชื่อว่า “ควรใช้ primitive ตัวไหน?”ตารางช่วยตัดสินใจสำหรับกรณีที่พบบ่อยที่สุด:
| อยากเปิดให้ใช้… | ใช้ | เพราะ |
|---|---|---|
| ”ค้นหาใน catalog”, “สร้าง ticket”, “รัน query นี้” | Tool | model ต้องตัดสินใจเองว่าจะทำ เมื่อไร จากสิ่งที่ user ขอ |
| ไฟล์, database schema, config document, log | Resource | มันคือ context ไม่ใช่ action host หรือ user จะแนบให้ตอนที่เกี่ยวข้อง |
”ทุกไฟล์ที่ตรงกับ repo://{repo}/…” | Resource template | resource ตระกูลเดียวกันที่รับ parameter ได้ ค้นพบและ auto-complete ได้ |
| ”รีวิว PR นี้แบบที่ทีมเราทำกัน” | Prompt | workflow ที่ใช้ซ้ำได้ user เป็นคนกด มี argument และโครงแบบ few-shot |
| dashboard แบบ live ที่ user ควรโต้ตอบด้วยได้ | Tool + MCP Apps extension | อยู่ในบท Extensions |
วิธีทดสอบง่าย ๆ: ถ้าคุณจะไม่สบายใจเมื่อ model ทำสิ่งนั้น โดยไม่ถามก่อน มันคือ tool (และคุณควรตั้ง annotation ให้รอบคอบ ดู Structured Output and Annotations) ถ้ามันเป็นข้อมูลนิ่ง ๆ มันคือ resource ถ้าคนจะพูดว่า “รัน workflow X หน่อย” มันคือ prompt
Method family
หัวข้อที่มีชื่อว่า “Method family”ทุก primitive มีรูปแบบเดียวกัน — ค้นพบก่อน แล้วค่อยลงมือ:
| ขั้นตอน | Tools | Resources | Prompts |
|---|---|---|---|
| ค้นพบ | tools/list | resources/list, resources/templates/list | prompts/list |
| ลงมือ | tools/call | resources/read | prompts/get |
| Change notification | notifications/tools/list_changed | notifications/resources/list_changed, notifications/resources/updated | notifications/prompts/list_changed |
method */list ทุกตัวรองรับ cursor pagination และมี field สำหรับ caching คือ ttlMs กับ cacheScope ส่วน method “ลงมือ” ทุกตัว — tools/call, resources/read, prompts/get — อาจ return InputRequiredResult เมื่อ server ต้องการอะไรบางอย่างจาก user ก่อน (ดู Client Interaction)
การประกาศ capability
หัวข้อที่มีชื่อว่า “การประกาศ capability”server ประกาศว่ารองรับ primitive ไหนบ้างใน object capabilities ที่ return จาก server/discover แต่ละ primitive มี key ของตัวเอง พร้อม sub-flag ที่ใส่หรือไม่ใส่ก็ได้:
{ "capabilities": { "tools": { "listChanged": true }, "resources": { "listChanged": true, "subscribe": true }, "prompts": { "listChanged": false } }}listChanged— server จะส่ง notification*/list_changedเมื่อชุดของมันเปลี่ยน (ไปยัง client ที่ opt-in ผ่านsubscriptions/listen)resources.subscribe— server แจ้งเตือนการอัปเดตของ resource URI เฉพาะตัว ได้
server ที่ประกาศ capability ใด ต้อง ตอบ request */list ของ primitive นั้น list จะว่างก็ได้และเปลี่ยนไปตามเวลาก็ได้ ถ้า server ไม่ได้ประกาศ prompts client ก็ต้องไม่ส่ง prompts/list
list ต้องนิ่ง ไม่ผูกกับ connection
หัวข้อที่มีชื่อว่า “list ต้องนิ่ง ไม่ผูกกับ connection”เพราะ MCP เป็น stateless (ดู Foundations) spec จึงระบุชัดว่า list ขึ้นกับอะไรได้บ้าง:
- ชุดของ tool, resource หรือ prompt ต้องไม่ ต่างกันตาม connection และ ต้องไม่ เปลี่ยนเป็นผลข้างเคียงจาก request อื่นบน connection เดียวกัน การเรียก
tools/callต้องไม่ปลดล็อกtools/listชุดใหม่ให้ เฉพาะ client นั้น - ชุดนี้ อาจ ต่างกันตาม authorization ที่แนบมากับ request — ผู้เรียกที่มี
files:writeอาจเห็น tooldelete_fileที่ผู้เรียกแบบ read-only ไม่เห็น credential เป็น input ต่อ request ไม่ใช่ state ของ connection - ชุดนี้ อาจ เปลี่ยนไปตามเวลาสำหรับทุกคน (deploy ใหม่, feature flag) ซึ่งนั่นคือหน้าที่ของ
listChanged
กติกานี้ตัด server แบบ “wizard ที่มี state” ที่เผย tool B เฉพาะเมื่อ tool A ถูกเรียกแล้วใน session นี้ออกไป ถ้าต้องการแบบนั้น ให้ tool A return handle ออกมาชัด ๆ แล้วให้ tool B รับ handle นั้นเป็น argument — รูปแบบ handle อยู่ในบท Tools
โมดูลนี้ครอบคลุมอะไร
หัวข้อที่มีชื่อว่า “โมดูลนี้ครอบคลุมอะไร”| บทเรียน | คุณจะได้เรียน |
|---|---|
| Tools | นิยาม, การตั้งชื่อ, tools/list และ tools/call, content type, error สองช่องทาง |
| Structured Output and Annotations | outputSchema, structuredContent, behavior hint, title และ icon |
| Resources | URI, text กับ blob, template, annotation, URI scheme |
| Prompts | argument, message, completion, การออกแบบ workflow ที่ใช้ซ้ำได้ |
| Notifications and Caching | subscriptions/listen, list-changed, ttlMs กับ cacheScope, pagination |