ข้ามไปยังเนื้อหา

Server Primitives

MCP server เปิดสิ่งให้ใช้ได้แค่สามประเภทเท่านั้น บน wire มันหน้าตาคล้ายกัน — แต่ละอย่างมี method */list และมีวิธีดึงมาทีละตัว — แต่ต่างกันตรง ใครเป็นคนตัดสินใจว่าจะใช้เมื่อไร คำถามข้อนี้ข้อเดียวคือการตัดสินใจด้านการออกแบบที่สำคัญที่สุดของโมดูลนี้

PrimitiveควบคุมโดยUI ที่พบบ่อยMethod family
Toolsmodel — LLM ตัดสินใจเรียกเองจากบริบทของบทสนทนาหน้ายืนยัน tool call, ตัวบอกสถานะ “กำลังรัน…”tools/list, tools/call
Resourcesapplication — host เป็นคนเลือกว่าจะแนบ context อะไรfile picker, @-mention, tree viewresources/list, resources/templates/list, resources/read
Promptsuser — คนเลือกใช้เองอย่างชัดเจนslash command, เมนู templateprompts/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
ใครขับเคลื่อน primitive แต่ละตัว

protocol ไม่ได้ บังคับ interaction model เหล่านี้ — host จะปล่อยให้ model เลือก resource เองหรือรัน prompt อัตโนมัติก็ได้ แต่ primitive ถูกออกแบบมารอบ ๆ แนวคิดนี้ และ server ที่ดีที่สุดจะเคารพเจตนาของมัน: tool คือ action ที่ model อาจทำ, resource คือ ข้อมูลที่ app ยื่นให้ model ได้, prompt คือ workflow ที่คนเป็นคนเรียก

ตารางช่วยตัดสินใจสำหรับกรณีที่พบบ่อยที่สุด:

อยากเปิดให้ใช้…ใช้เพราะ
”ค้นหาใน catalog”, “สร้าง ticket”, “รัน query นี้”Toolmodel ต้องตัดสินใจเองว่าจะทำ เมื่อไร จากสิ่งที่ user ขอ
ไฟล์, database schema, config document, logResourceมันคือ context ไม่ใช่ action host หรือ user จะแนบให้ตอนที่เกี่ยวข้อง
”ทุกไฟล์ที่ตรงกับ repo://{repo}/…”Resource templateresource ตระกูลเดียวกันที่รับ parameter ได้ ค้นพบและ auto-complete ได้
”รีวิว PR นี้แบบที่ทีมเราทำกัน”Promptworkflow ที่ใช้ซ้ำได้ user เป็นคนกด มี argument และโครงแบบ few-shot
dashboard แบบ live ที่ user ควรโต้ตอบด้วยได้Tool + MCP Apps extensionอยู่ในบท Extensions

วิธีทดสอบง่าย ๆ: ถ้าคุณจะไม่สบายใจเมื่อ model ทำสิ่งนั้น โดยไม่ถามก่อน มันคือ tool (และคุณควรตั้ง annotation ให้รอบคอบ ดู Structured Output and Annotations) ถ้ามันเป็นข้อมูลนิ่ง ๆ มันคือ resource ถ้าคนจะพูดว่า “รัน workflow X หน่อย” มันคือ prompt

ทุก primitive มีรูปแบบเดียวกัน — ค้นพบก่อน แล้วค่อยลงมือ:

ขั้นตอนToolsResourcesPrompts
ค้นพบtools/listresources/list, resources/templates/listprompts/list
ลงมือtools/callresources/readprompts/get
Change notificationnotifications/tools/list_changednotifications/resources/list_changed, notifications/resources/updatednotifications/prompts/list_changed

method */list ทุกตัวรองรับ cursor pagination และมี field สำหรับ caching คือ ttlMs กับ cacheScope ส่วน method “ลงมือ” ทุกตัว — tools/call, resources/read, prompts/get — อาจ return InputRequiredResult เมื่อ server ต้องการอะไรบางอย่างจาก user ก่อน (ดู Client Interaction)

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

เพราะ MCP เป็น stateless (ดู Foundations) spec จึงระบุชัดว่า list ขึ้นกับอะไรได้บ้าง:

  • ชุดของ tool, resource หรือ prompt ต้องไม่ ต่างกันตาม connection และ ต้องไม่ เปลี่ยนเป็นผลข้างเคียงจาก request อื่นบน connection เดียวกัน การเรียก tools/call ต้องไม่ปลดล็อก tools/list ชุดใหม่ให้ เฉพาะ client นั้น
  • ชุดนี้ อาจ ต่างกันตาม authorization ที่แนบมากับ request — ผู้เรียกที่มี files:write อาจเห็น tool delete_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 AnnotationsoutputSchema, structuredContent, behavior hint, title และ icon
ResourcesURI, text กับ blob, template, annotation, URI scheme
Promptsargument, message, completion, การออกแบบ workflow ที่ใช้ซ้ำได้
Notifications and Cachingsubscriptions/listen, list-changed, ttlMs กับ cacheScope, pagination
primitive ตัวไหนถูกออกแบบให้ language model เรียกใช้ได้เองตามการตัดสินใจของมัน?
server อยากเผย tool เพิ่มอีกตัวเฉพาะเมื่อ client เคยเรียก tool "login" ไปก่อนหน้าบน connection เดียวกัน spec ว่าอย่างไร?
sub-capability `subscribe` ใต้ `resources` หมายความว่าอย่างไร?