3 primitive: tools / resources / prompts
บทที่ 1 วางไว้ว่า server ห่อระบบภายนอกจริงแล้วเปิดออกมาเป็น primitive สามแบบ บทนี้เจาะที่สามแบบนั้นโดยตรง — แต่เรากลับเข้าทางที่คนส่วนใหญ่มองข้าม จุดที่ทำให้ tool, resource และ prompt เป็นคนละอย่างกัน ไม่ใช่ “อ่านข้อมูล vs เขียนข้อมูล” หรือ “GET vs POST” แต่คือ ใครเป็นผู้ตัดสินใจว่าจะเรียกมันเมื่อไร spec วางแกนนี้ไว้ตรงๆ ในหน้าของ primitive แต่ละแบบ และมันคือเลนส์ที่จะร้อยทั้งบทที่ 3–6 เข้าด้วยกัน
คอร์สนี้เป็น คอร์สออกแบบ code ในบทนี้เป็น ภาพร่าง ของ primitive ที่ server เปิดออกมา (schema + method + ใครควบคุม) ไม่ใช่ server ที่รันได้จริง ต่อยอดจาก domain เดิมที่ repo kaen-food-ordering (กำลังจัดทำ) — server ออเดอร์ ของ platform ฟู้ดเดลิเวอรีที่ถูกห่อให้ ops-copilot เรียกใช้
primitive คืออะไร และทำไมต้องแยกที่ “ใครควบคุม”
หัวข้อที่มีชื่อว่า “primitive คืออะไร และทำไมต้องแยกที่ “ใครควบคุม””PrimitivePrimitiveหน่วยพื้นฐานสามแบบที่ MCP Server เปิดให้ใช้ คือ Tool (model เลือกเรียกเอง), Resource (app เป็นผู้แนบเข้า context), และ Prompt (ผู้ใช้เป็นผู้เรียกเอง) — สามแบบนี้ต่างกันตรง 'ใครเป็นผู้ตัดสินใจว่าจะเรียกเมื่อไร' ไม่ใช่แค่รูปแบบข้อมูลที่ส่งมาArchitecture คือหน่วยพื้นฐานที่ MCP server เปิดออกมาให้ฝั่ง host เรียกใช้ มีสามแบบพอดี — tool, resource, prompt — และ spec ออกแบบให้แต่ละแบบมี เจ้าของการตัดสินใจ ต่างกัน สรุปเป็นตรีเอกภาพ “ใครควบคุม” ได้ว่า tool model ควบคุม, resource app ควบคุม, prompt ผู้ใช้ควบคุม เหตุที่แกนนี้สำคัญกว่าแกน “อ่าน/เขียน” ก็เพราะมันบอกว่าเราต้อง ตรวจสอบสิทธิ์และผลลัพธ์เข้มงวดแค่ไหน: สิ่งที่ model เรียกเองได้อัตโนมัติต้องระวังกว่าสิ่งที่ผู้ใช้ต้องกดเรียกเองเสมอ
ตรีเอกภาพ model / app / user นี้เป็นถ้อยคำ เดียวกันเป๊ะ ระหว่าง spec ฉบับ 2025-06-18 กับ 2025-11-25 — ต่างจากรายละเอียดสายส่ง/session ที่กำลังขยับ (ดูกล่อง version ในบทที่ 1) มันคือหนึ่งใน ชั้นที่ทนต่อการเปลี่ยน version ที่คอร์สนี้เลือกสอนเป็นแกน ในบทนี้ยังอ้าง path 2025-11-25 เสมอเพื่อความสม่ำเสมอ
Tool — model ควบคุม (model-controlled)
หัวข้อที่มีชื่อว่า “Tool — model ควบคุม (model-controlled)”ToolToolfunction ที่ Server ประกาศให้ model เรียกเองได้ระหว่างให้เหตุผล เช่น ค้นหาออเดอร์ หรือยิง refund มักมีผลข้างเคียงจริง (side effect) — เป็น Primitive แบบ Model-controlled ต้องระวัง parameter ผิด สิทธิ์เกินจำเป็น และคำอธิบาย tool ที่ถูกฝังคำสั่งแฝงTactical Design คือ function ที่ server ประกาศให้ model ค้นพบและเรียกเองได้อัตโนมัติ ระหว่างให้เหตุผล spec ระบุตรงว่า “tool ใน MCP ถูกออกแบบให้เป็น model-controlled หมายความว่า model ภาษาสามารถค้นพบและเรียก tool ได้เอง” (Tools, MCP spec 2025-11-25) model ขอรายการที่มีผ่าน tools/list แล้วสั่งเรียกผ่าน tools/call เอง โดย server ประกาศความสามารถ tools (มี listChanged เสริมไว้บอกว่ารายการ tool เปลี่ยนได้ระหว่าง session)
จุดนี้เองที่ทำให้ tool เป็น primitive ที่ต้องระวังที่สุด: มันเป็นแบบ Model-controlledModel-controlledลักษณะของ Primitive ที่ตัว model เป็นผู้ตัดสินใจเองว่าจะเรียกเมื่อไรและเรียกอย่างไร เช่น Tool — ต่างจาก App-driven ที่ app เป็นผู้ตัดสินใจแทน ความแตกต่างนี้กำหนดว่าควรตรวจสอบสิทธิ์และผลลัพธ์เข้มงวดแค่ไหน ไม่ใช่แค่ดูว่าข้อมูลนั้น 'อ่านอย่างเดียว' หรือ 'แก้ไขได้'Architecture — modelเป็นผู้ตัดสินใจว่าจะเรียกเมื่อไรและด้วย parameter อะไร ไม่ใช่มนุษย์ ในตัวอย่างฟู้ดเดลิเวอรี find_late_orders(zone, since) คือ tool ที่ ops-copilot จะเลือกยิงเองเมื่อมันคิดว่าควรยิง — ซึ่งเป็นเหตุผลว่าทำไม schema และสิทธิ์ของ tool จึงเป็นทั้งพื้นผิวการใช้งานและพื้นผิวการโจมตี (แกนของบทที่ 3)
Resource — app ควบคุม (application-driven)
หัวข้อที่มีชื่อว่า “Resource — app ควบคุม (application-driven)”ResourceResourceข้อมูลที่ Server เปิดให้ Host แนบเข้า context ได้ เช่น file log แถวในฐานข้อมูล หรือเอกสารนโยบาย — เป็น Primitive แบบ App-driven: app หรือผู้ใช้เป็นผู้เลือกว่าจะแนบอันไหนเมื่อไร ไม่ใช่ model เรียกเอง คล้าย endpoint อ่านอย่างเดียวที่ไม่มีผลข้างเคียงTactical Design คือข้อมูลที่ server เปิดให้ host เป็นผู้เลือกแนบเข้า context ไม่ใช่ model เรียกเอง spec ระบุว่า “resource ใน MCP ถูกออกแบบให้เป็น application-driven โดย app ฝั่ง host เป็นผู้ตัดสินใจว่าจะนำ context เข้ามาอย่างไรตามความต้องการของตัวเอง” (Resources, MCP spec 2025-11-25) นี่คือแบบ App-drivenApp-drivenลักษณะของ Primitive ที่ Host/Client เป็นผู้ตัดสินใจว่าจะดึงเข้ามาเมื่อไรและอันไหน ไม่ใช่ model เรียกเอง เช่น Resource — ความเสี่ยงจึงต่างจาก Tool เพราะ model ไม่ได้เลือกเอง แต่เนื้อหาที่ดึงมายังปนเปื้อน prompt injection ได้เหมือนกันArchitecture — app หรือผู้ใช้ผ่าน host เป็นผู้ตัดสินใจว่าจะดึง resource ไหนเข้ามาเมื่อไร
resource มีสองอย่างที่ tool ไม่มี อย่างแรกคือ ตัวตน (identity): ทุก resource ระบุด้วย URI (ตาม RFC 3986) และถ้ามีรูปแบบซ้ำๆ ก็ประกาศเป็น Resource TemplateResource TemplateURI ที่มี parameter (เช่น orders://{orderId}) ให้ Server ประกาศชุด Resource ที่มีรูปแบบเดียวกันได้โดยไม่ต้องแจงทีละรายการ — Host เติมค่า parameter ก่อนขอ Resource จริงตามรูปแบบนั้น รองรับการ subscribe ฟังการอัปเดตได้ด้วยTactical Design ด้วย URI Template (RFC 6570) ได้ เช่น orders://{orderId} แทนที่จะแจงทุกออเดอร์ทีละรายการ อย่างที่สองคือ การติดตามการเปลี่ยนแปลง: host ขอผ่าน resources/list และ resources/read และถ้า server รองรับก็ resources/subscribe เพื่อรับ notifications/resources/updated เมื่อข้อมูลเปลี่ยน — เหมาะกับ context ที่ต้องสด เช่นสถานะออเดอร์ที่กำลังวิ่ง
Prompt — ผู้ใช้ควบคุม (user-controlled)
หัวข้อที่มีชื่อว่า “Prompt — ผู้ใช้ควบคุม (user-controlled)”Prompt (MCP)Prompt (MCP primitive)template คำสั่ง/ขั้นตอนสำเร็จรูปที่ Server เตรียมไว้ให้ผู้ใช้เรียกเองตรง ๆ เช่นผ่านเมนูหรือ slash command — เป็น Primitive ที่ผู้ใช้เป็นผู้ควบคุม ต่างจาก Tool ที่ model เรียกเอง และต่างจาก Resource ที่ app เป็นผู้แนบให้ ใช้มาตรฐานขั้นตอนงานที่ทำซ้ำบ่อยTactical Design คือ template คำสั่งหรือขั้นตอนสำเร็จรูปที่ server เตรียมไว้ให้ ผู้ใช้เรียกเองตรงๆ spec ระบุว่า “prompt ถูกออกแบบให้เป็น user-controlled… โดยผู้ใช้สามารถเลือกใช้มันได้อย่างชัดแจ้ง” (Prompts, MCP spec 2025-11-25) UI ตามแบบฉบับคือ slash command — ผู้ใช้พิมพ์ /triage_late_order เอง ไม่ใช่ model ไปหยิบมาเรียกกลางทาง host ขอรายการผ่าน prompts/list และดึง template จริงผ่าน prompts/get
เส้นแบ่งจึงคมชัด: prompt คือของที่ ผู้ใช้ กดเรียก, tool คือของที่ model เรียกเอง, resource คือของที่ app แนบให้ ทั้งสามอาจ “อ่านข้อมูลออเดอร์” เหมือนกันได้ แต่ต่างกันที่เจ้าของการตัดสินใจ ไม่ใช่ที่ชนิดของงาน
เดี๋ยวจะเจอคำว่า sampling (server ขอให้ client ยิง completion จาก LLM แทน) และ elicitation (server ขอข้อมูลเพิ่มจากผู้ใช้กลางทาง) — สองอย่างนี้ ไม่ใช่ primitive ฝั่ง server แต่เป็น ความสามารถฝั่ง client ที่ไหลย้อนทิศทาง เอามาปนกับตรีเอกภาพ tool/resource/prompt จะทำให้ภาพเบลอ เราจะเปิดมันในบทที่ 7 ตอนพูดเรื่องการประกอบและระบบนิเวศ
ภาพรวม: primitive × ใครควบคุม
หัวข้อที่มีชื่อว่า “ภาพรวม: primitive × ใครควบคุม”flowchart LR
M["model (LLM)<br/>ค้นพบ + เรียกเองอัตโนมัติ"]
A["app / Host<br/>เลือกแนบเข้า context"]
U["ผู้ใช้<br/>เรียกเองตรง ๆ"]
subgraph SERVER["MCP Server (เช่น server ออเดอร์)"]
T["Tool<br/>tools/list · tools/call"]
R["Resource<br/>URI · template · subscribe<br/>resources/read"]
P["Prompt<br/>prompts/list · prompts/get"]
end
M -->|"model-controlled"| T
A -->|"application-driven"| R
U -->|"user-controlled · slash command"| P
classDef ctrl fill:#c7d2fe,stroke:#3730a3,color:#1a1a1f;
classDef prim fill:#bbf7d0,stroke:#065f46,color:#1a1a1f;
class M,A,U ctrl;
class T,R,P prim;
คำบรรยายภาพ: ผู้ควบคุมสามฝ่าย (น้ำเงิน) จับคู่กับ primitive สามแบบ (เขียว) แบบหนึ่งต่อหนึ่ง — modelเรียก tool เองผ่าน tools/call, app/host เลือกแนบ resource เข้า context ตามที่เห็นควร, ผู้ใช้กดเรียก prompt เองผ่าน slash command เส้นแบ่งของ primitive อยู่ที่ ลูกศรมาจากใคร ไม่ใช่ที่ตัว primitive อ่านหรือเขียนข้อมูล
ภาพร่าง: server ออเดอร์เปิด primitive สามแบบ
หัวข้อที่มีชื่อว่า “ภาพร่าง: server ออเดอร์เปิด primitive สามแบบ”พอมองผ่านเลนส์ “ใครควบคุม” การออกแบบ server ก็กลายเป็นการตัดสินใจว่างานแต่ละอย่างควรให้ ใครเป็นผู้เรียก ภาพร่างข้างล่างคือ server ออเดอร์ที่เปิดครบทั้งสามแบบ:
# Tool — model ควบคุม: ops-copilot เลือกยิงเองผ่าน tools/calltools: - name: find_late_orders # โผล่ให้ model เห็นผ่าน tools/list # model ตัดสินใจเรียกเอง → ต้องรักษาสิทธิ์และตรวจผลลัพธ์เข้มที่สุด
# Resource — app ควบคุม: host เลือกแนบเข้า context เมื่อเห็นควรresources: - uri: orders://policy/refund # resources/read — เอกสารนโยบาย (คงที่) - uriTemplate: orders://{orderId} # Resource Template (RFC 6570) # subscribe → notifications/resources/updated เมื่อสถานะออเดอร์เปลี่ยน
# Prompt — ผู้ใช้ควบคุม: ผู้ใช้พิมพ์เรียกเองผ่าน slash commandprompts: - name: triage_late_order # prompts/get — ผู้ใช้กด /triage_late_order เองกลับไปที่วินัยเดิม: tool เป็น ACL, server surface เป็นภาษา
หัวข้อที่มีชื่อว่า “กลับไปที่วินัยเดิม: tool เป็น ACL, server surface เป็นภาษา”ตรีเอกภาพนี้ต่อตรงเข้ากับวินัยจากคอร์ส Agent as Bounded Context: การเลือกว่า “อะไรควรเป็น tool” คือการเลือกว่า อะไรที่ยอมให้ model กดปุ่มเองได้ — คือการวาง Anti-Corruption Layer / ACL ว่า model เอื้อมถึงอะไรได้บ้าง (บทที่ 3) ส่วนชื่อและคำอธิบายของ primitive ทั้งสามแบบรวมกันคือ ภาษาที่เผยแพร่ (published language) ของ bounded context นั้น — เป็นภาษาที่ model ใช้ให้เหตุผล (บทที่ 5) และเพราะ ops-copilot เป็น host ที่เห็นบทสนทนาทั้งหมด การจับคู่ “งานไหนให้ใครเรียก” ให้ถูกจึงเป็นก้าวแรกของความปลอดภัย ก่อนจะไปถึงเรื่อง trust boundary ในบทที่ 6
บทนี้อ้างอิงแหล่งปฐมภูมิโดยตรง (ยังไม่มีการ์ด DevIQ สำหรับ primitive ของ MCP โดยเฉพาะ):
- Tools (MCP spec 2025-11-25) — tool เป็น model-controlled,
tools/list·tools/call - Resources (MCP spec 2025-11-25) — resource เป็น application-driven, URI · template · subscribe
- Prompts (MCP spec 2025-11-25) — prompt เป็น user-controlled,
prompts/list·prompts/get - คอร์ส Agent as Bounded Context — เลนส์ DDD (tool เป็น ACL, server surface เป็น published language) ที่คอร์สนี้ต่อยอด
เช็กความเข้าใจ — บทที่ 2
ข้อ 1 / 3ในตรีเอกภาพ 'ใครควบคุม' ของ MCP — tool ถูกควบคุมโดยใคร (ณ spec 2025-11-25)?