Hãy xem xét cùng một task — format một tài liệu thành markdown — với 2 cách approach:
- Giải thích Prompts là gì và khác gì với Tools/Resources (user-controlled)
- Định nghĩa prompt qua decorator @mcp.prompt() với parameters có Field description
- Build một prompt template cho use case thực tế (format, summarize, review)
- Hiểu tại sao prompt trong MCP = "chuyên gia đóng gói kiến thức prompt engineering"
- Test prompt qua MCP Inspector để verify interpolation đúng
- Phân biệt khi nào tạo prompt vs để user tự gõ
Mở đầu: Vì sao prompt "thô" luôn thua prompt "được craft"
Cách 1: User tự gõ
User gõ:
Hãy format lại cho đẹp
Claude không biết:
- Tài liệu nào? (chưa mention)
- Format kiểu gì? (markdown? HTML? plain?)
- "Đẹp" nghĩa là gì? (nhiều spacing? ít spacing? có emoji?)
- Giữ nguyên content hay có thể chỉnh sửa?
Kết quả: response thô, không nhất quán, user phải follow-up nhiều lần.
Cách 2: Dùng prompt template đã craft
User gõ slash command: /format plan.md
Đằng sau, MCP server đã định nghĩa prompt template:
Your goal is to reformat a document to be written with markdown syntax.
The id of the document you need to reformat is:
<document_id>
plan.md
</document_id>
Add in headers, bullet points, tables, etc as necessary.
Feel free to add in structure.
Use the 'edit_document' tool to edit the document.
After the document has been reformatted, provide a summary of
changes you made.
Guidelines:
- Preserve original meaning
- Use H2/H3 headers for sections
- Use bullet points for lists of 3+ items
- Use tables for comparative data
- Add code blocks with language hints for code snippets
Claude nhận prompt đã được tinh chỉnh bởi MCP server author. Kết quả:
- Claude đọc đúng doc qua tool
- Format theo guideline cụ thể
- Report lại changes
User không phải là prompt engineer. Server author đã làm giúp — một lần, test kỹ, mọi user hưởng lợi.
Đó là giá trị cốt lõi của Prompts trong MCP.
Prompts là gì — Định nghĩa chính xác
Prompts là primitive MCP cho phép server expose message templates mà client (user) có thể kích hoạt. Thường xuất hiện trong UI như:
- Slash commands (
/format,/summarize,/review) - Buttons (workflow trigger trong Claude Desktop)
- Menu items (phải chuột → Action)
┌──────────────────────────────────────────────────────────┐
│ │
│ USER │
│ │ "/format plan.md" │
│ ▼ │
│ CLIENT (Claude Desktop, Cursor, CLI of you) │
│ │ GetPromptRequest { │
│ │ name: "format", │
│ │ arguments: { doc_id: "plan.md" } │
│ │ } │
│ ▼ │
│ MCP SERVER │
│ │ Gọi format_document("plan.md") │
│ │ Return list[Message] với prompt đã interpolate │
│ ▼ │
│ CLIENT nhận messages │
│ │ Send lên Claude API với tool definitions │
│ ▼ │
│ CLAUDE nhận prompt craft + tools → execute │
│ │
└──────────────────────────────────────────────────────────┘
Khác biệt với Tool và Resource
| Tool | Resource | Prompt | |
|---|---|---|---|
| Controlled by | Model | App | User |
| Khi nào activate? | Claude decide | App fetch | User click/type |
| UI presentation | Tool call (hidden) | Content in prompt | Slash / button |
| Interactive? | 1-shot | Data inject | Trigger workflow |
| Ví dụ | create_issue() | github://repo/X | /new-pr |
Key insight: Prompts là cách server author đóng gói kiến thức prompt engineering thành UX đơn giản cho end-user. User không cần biết cách prompt tốt — họ chỉ cần click.
Decorator @mcp.prompt()
Định nghĩa prompt giống tool/resource nhưng return list of Messages thay vì data.
from mcp.server.fastmcp.prompts import base
from pydantic import Field
@mcp.prompt(
name="format",
description="Rewrites the contents of the document in Markdown format."
)
def format_document(
doc_id: str = Field(description="Id of the document to format")
) -> list[base.Message]:
prompt = f"""
Your goal is to reformat a document to be written with markdown syntax.
The id of the document you need to reformat is:
<document_id>
{doc_id}
</document_id>
Add in headers, bullet points, tables, etc as necessary.
Feel free to add in structure.
Use the 'edit_document' tool to edit the document.
After the document has been reformatted, provide a summary of
changes you made.
"""
return [
base.UserMessage(prompt)
]
Bóc tách chi tiết
@mcp.prompt(name, description)
name— slash command Claude Desktop sẽ show (user gõ/format)description— hiển thị trong autocomplete / tooltip
Function parameters
- Giống tool, dùng type hint +
Field(description=...)để document - Client UI tự gen form yêu cầu user điền parameter
Return type: list[base.Message]
- Message list gửi tới Claude
- Có thể mix
UserMessagevàAssistantMessageđể tạo few-shot
Body function — prompt craft
- Đây là nơi bạn đầu tư thời gian
- Dùng f-string interpolate parameter
- Tag XML (
<document_id>) giúp Claude parse rõ ràng
Cấu trúc Message list
Single UserMessage (đơn giản nhất)
return [base.UserMessage("Do this task...")]
Phổ biến cho workflow đơn lẻ.
Multi-turn với Assistant response
return [
base.UserMessage("Show me an example of good code review."),
base.AssistantMessage("""Great review comment:
- Points out specific line
- Suggests alternative
- Explains why
Example: 'Line 42: Consider using defaultdict instead...'"""),
base.UserMessage(f"Now review this PR: {pr_url}"),
]
Few-shot pattern — Claude thấy ví dụ trước khi làm task thật.
Chain with context
return [
base.UserMessage("Here's the document you'll work with."),
base.AssistantMessage("I'll wait for your instructions."),
base.UserMessage(f"Format doc {doc_id} as markdown."),
]
Ít dùng hơn, nhưng hữu ích để set up "state".
Case study: 4 prompt template thực tế
Prompt 1: format (đơn giản)
@mcp.prompt(name="format", description="Reformat document in Markdown")
def format_document(doc_id: str = Field(...)) -> list[base.Message]:
return [base.UserMessage(f"""
Reformat document '{doc_id}' to Markdown.
Rules:
1. Add appropriate headers (H2/H3)
2. Convert lists to bullets
3. Preserve original meaning
4. Use edit_document tool to save changes
Return summary of changes when done.
""")]
Prompt 2: summarize (với options)
@mcp.prompt(name="summarize", description="Summarize document with configurable length")
def summarize(
doc_id: str = Field(description="Document ID"),
length: str = Field(description="Length: brief, medium, detailed", default="medium"),
) -> list[base.Message]:
length_map = {
"brief": "1-2 sentences",
"medium": "1 paragraph (3-5 sentences)",
"detailed": "3-4 paragraphs covering main points and supporting details",
}
return [base.UserMessage(f"""
Summarize document '{doc_id}' in {length_map[length]}.
Focus on:
- Main thesis or purpose
- Key findings or arguments
- Actionable conclusions (if any)
Use read_doc_contents tool to access the document.
""")]
Prompt 3: review (với few-shot)
@mcp.prompt(name="review", description="Code review with examples")
def review_code(
doc_id: str = Field(description="Code file ID"),
) -> list[base.Message]:
return [
base.UserMessage("Review this code for a Python file."),
base.AssistantMessage("""I'll provide review using this format:
- **Bugs:** issues that will cause failures
- **Style:** readability and convention
- **Performance:** optimization opportunities
- **Security:** potential vulnerabilities
Example for a quick function:
def add(a, b): return a+b
- Bugs: none
- Style: missing type hints, single-line
- Performance: fine
- Security: no input validation but low-risk for arithmetic
"""),
base.UserMessage(f"Now review {doc_id} following the same format."),
]
Prompt 4: translate (với locale options)
@mcp.prompt(name="translate", description="Translate document to target language")
def translate(
doc_id: str = Field(description="Document ID"),
target_lang: str = Field(description="Target language code (e.g. 'vi', 'ja', 'es')"),
preserve_formatting: bool = Field(description="Keep markdown/HTML formatting", default=True),
) -> list[base.Message]:
formatting_note = "Preserve all markdown, HTML, and code blocks unchanged." if preserve_formatting else "Plain text output only."
return [base.UserMessage(f"""
Translate document '{doc_id}' to {target_lang}.
Guidelines:
- {formatting_note}
- Keep technical terms in original when no good equivalent exists
- Maintain original tone (formal/informal) and style
- Use culturally appropriate phrasing
Steps:
1. read_doc_contents to access document
2. Translate
3. edit_document to save back (same doc_id with _translated suffix)
""")]
Bảng so sánh: Prompt vs các cách khác
| Cách | Pros | Cons | Phù hợp cho |
|---|---|---|---|
| MCP Prompt | Reusable, versioned, server-maintained | Phải build server | Chuyên workflow trong app |
| System prompt hard-code | Đơn giản | Không customize được | App single-purpose |
| User gõ thẳng | Flexible | Inconsistent quality | Ad-hoc exploration |
| Custom slash handler in app | Full control | Tied to app code | App-specific shortcut |
Khi nào chọn MCP Prompt:
- Cần chia sẻ prompt qua nhiều client
- Prompt complex, cần update độc lập với app
- Muốn "đóng gói" expertise domain
- App có workflow lặp đi lặp lại
Ví dụ thực chiến: Build format prompt đầy đủ
Tình huống
Bạn muốn user của document MCP server có command /format để reformat doc sang markdown.
Bước 1: Add prompt định nghĩa (~2 phút)
from mcp.server.fastmcp.prompts import base
@mcp.prompt(
name="format",
description="Rewrites the contents of the document in Markdown format."
)
def format_document(
doc_id: str = Field(description="Id of the document to format")
) -> list[base.Message]:
prompt = f"""
Your goal is to reformat a document to be written with markdown syntax.
The id of the document you need to reformat is:
<document_id>
{doc_id}
</document_id>
Add in headers, bullet points, tables, etc as necessary.
Feel free to add in structure.
Use the 'edit_document' tool to edit the document.
After the document has been reformatted, provide a summary of
changes you made.
"""
return [base.UserMessage(prompt)]
Bước 2: Restart server, check Inspector (~1 phút)
uv run mcp dev mcp_server.py
Inspector → tab Prompts → thấy format. Click, nhập doc_id = "plan.md", Get Prompt.
Inspector show messages sẽ gửi Claude:
{
"messages": [
{
"role": "user",
"content": "Your goal is to reformat a document..."
}
]
}
Notice {doc_id} đã được interpolate thành plan.md.
Bước 3: Verify prompt chất lượng (~5 phút)
Copy messages output vào Claude Desktop / Workbench. Chạy thử. Claude:
- Hiểu task không?
- Gọi tool đúng không?
- Output quality có cao?
Iterate description / guidelines đến khi hài lòng.
Bước 4: Deploy (0 phút — đã xong)
MCP server restart → prompt available cho mọi client. Một lần craft, nhiều user hưởng lợi.
Kết quả
Không có prompt, user có thể tự gõ task format doc. Với prompt craft, output chất lượng cao hơn và nhất quán hơn qua mọi lần gọi.
Ví dụ theo ngành — Prompts library patterns
Writing tools
Prompts:
/outline {topic}— generate blog outline/editorial {text}— proofread with style guide/headline {article}— generate 5 headline variants
Pattern: Writer click command thay vì gõ prompt dài. Output nhất quán với brand voice.
Sales enablement
Prompts:
/prep-call {account}— compile briefing từ CRM data/objection-handler {concern}— generate response talking points/follow-up {call_notes}— draft follow-up email
Pattern (Michael Cohen workflow): SDR có "playbook" prompts. New rep onboard nhanh vì best-practice prompts sẵn có.
Legal
Prompts:
/contract-review {doc_id}— flag unusual clauses/redline {original} {proposed}— compare versions/precedent-search {case_topic}— find similar past cases
Pattern: Legal ops team craft prompts với guidance tuân thủ bar association. Junior associate dùng mà không lo sai legal ethics.
Data analysis
Prompts:
/sql-to-chart {query}— write SQL + viz recommendation/metric-review {kpi}— analyze trend + flag anomaly/cohort-analysis {segment}— structured cohort breakdown
Pattern: Analyst chuyên data viết prompt, product manager dùng. Democratize data work.
Medical
Prompts:
/patient-summary {patient_id}— aggregate chart review/drug-check {medications}— interaction analysis/diff-diagnosis {symptoms}— differential diagnosis outline
Pattern: Chief resident craft prompts tuân thủ medical guidelines. Trainee dùng với safety net.
Security
Prompts:
/threat-model {architecture}— STRIDE analysis/pen-test-plan {target}— generate test plan/ioc-lookup {indicator}— threat intelligence enrich
Pattern: Security team đóng gói expertise vào prompt. Dev/SRE self-serve basic security queries.
Anti-patterns — Những sai lầm cần tránh
Prompt rỗng / generic
Sai lầm:
@mcp.prompt(name="help")
def help_prompt():
return [base.UserMessage("Help me")]
- Tại sao là sai
- Không cung cấp value trên câu user tự gõ. Pollute namespace.
- Cách đúng
- Chỉ tạo prompt khi có value add thật — guidance, structure, few-shot, domain knowledge.
Hard-code secret trong prompt body
Sai lầm:
@mcp.prompt(name="deploy")
def deploy_prompt():
return [base.UserMessage(f"""
Use API key sk-xxx to deploy... # leak!
""")]
- Tại sao là sai
- Secret vào prompt → vào logs → vào Claude context → training data risk.
- Cách đúng
- Secret qua tool gọi env var, không qua prompt body.
Prompt quá dài/phức tạp
- Sai lầm
- Prompt 5000 token với 20 guideline.
- Tại sao là sai
- Claude overwhelm, miss instructions. Token cost cao.
- Cách đúng
- Concise. 500-1500 token là sweet spot. Nếu dài hơn, break thành nhiều prompt nhỏ.
Không test interpolation
- Sai lầm
- Deploy prompt với
{doc_id}chưa bao giờ verify qua Inspector. - Tại sao là sai
- Typo trong template →
{doc_id}chưa replace → Claude thấy{doc_id}literal, confused. - Cách đúng
- Luôn test qua Inspector "Get Prompt" trước khi commit.
Prompt phụ thuộc tool không có
- Sai lầm
- Prompt nói "Use the get_data tool..." nhưng server không define
get_data. - Tại sao là sai
- Claude gọi tool không tồn tại → confused, fabricate.
- Cách đúng
- Trong server reference chính xác tool đã define. Hoặc prompt generic không mention tool name cụ thể.
Mix ngôn ngữ inconsistent
- Sai lầm
- Prompt bắt đầu tiếng Anh, guideline tiếng Việt, tool call tiếng Anh.
- Tại sao là sai
- Claude output mixed language, lộn xộn.
- Cách đúng
- Chọn 1 ngôn ngữ, stay consistent. Nếu multi-language, set rõ expectation đầu prompt.
Mẹo nâng cao
Mẹo 1: Version prompts
@mcp.prompt(name="format_v2", description="...")
def format_v2(doc_id: str): ...
@mcp.prompt(name="format", description="... (legacy, prefer format_v2)")
def format_v1(doc_id: str): ...
Evolve prompt mà không break user đã quen command cũ.
Mẹo 2: Argument completion
MCP spec hỗ trợ argument completion — server back autocomplete cho prompt param.
@mcp.prompt_argument_completion("format", "doc_id")
async def complete_doc_id(prefix: str) -> list[str]:
return [d for d in docs.keys() if d.startswith(prefix)]
User gõ /format re → popup report.pdf, request.md. UX gần như native.
Mẹo 3: Prompt composition
def _base_guidelines() -> str:
return """
- Preserve original meaning
- Use appropriate formatting
- Cite sources when applicable
"""
@mcp.prompt(name="format")
def format(doc_id: str):
return [base.UserMessage(f"""
Task: Format {doc_id}.
Guidelines:
{_base_guidelines()}
Specific format: markdown.
""")]
@mcp.prompt(name="summarize")
def summarize(doc_id: str):
return [base.UserMessage(f"""
Task: Summarize {doc_id}.
Guidelines:
{_base_guidelines()}
Specific length: 1 paragraph.
""")]
Share guideline giữa nhiều prompt → update 1 chỗ → mọi prompt benefit.
Mẹo 4: Metrics về prompt usage
Server-side track:
- Prompt nào được dùng nhất
- Prompt nào fail (arg validation error)
- Completion rate (tool followup thành công?)
Dùng để iterate — prompt ít dùng nên cải thiện discoverability hoặc deprecate.
Áp dụng ngay
Bài tập 1: Add format prompt (~10 phút)
Bước 1: Mở mcp_server.py. Import:
from mcp.server.fastmcp.prompts import base
- Bước 2
- Add
format_documentprompt như bài. - Bước 3
- Save, restart
mcp dev mcp_server.py. - Bước 4
- Inspector tab Prompts → click format → nhập
doc_id = "plan.md"→ Get Prompt. - Bước 5
- Ghi lại:
- Output messages có đúng format không? ___________
{doc_id}đã interpolate? ___________- Prompt có đọc tự nhiên không? ___________
Bài tập 2: Viết prompt thứ hai (~15 phút)
Tự design một prompt mới cho document server. Ý tưởng:
/summarize doc_id length/translate doc_id target_lang/diff doc_id_a doc_id_b
- Bước 1
- Chọn 1, design params + guidelines.
- Bước 2
- Viết decorator và body prompt.
- Bước 3
- Test qua Inspector với các input khác nhau.
- Bước 4
- Ghi lại:
- Tên prompt: ___________
- Params: ___________
- Prompt body bao nhiêu từ: ___________
- Claude output có align với guideline không? ___________
Bài tập 3 (thử thách): Few-shot prompt (~15 phút)
Design prompt có 2-3 example (UserMessage + AssistantMessage) trước request cuối.
Ví dụ: /tag-extract {doc_id} — extract tags từ doc.
Pattern:
return [
base.UserMessage("Extract tags from: 'Python tutorial for beginners'"),
base.AssistantMessage("Tags: python, tutorial, beginner, programming"),
base.UserMessage("Extract tags from: 'Deep learning with transformers'"),
base.AssistantMessage("Tags: deep-learning, transformers, ml, neural-networks"),
base.UserMessage(f"Extract tags from content of doc {doc_id}"),
]
Test: output có follow format example không?
Tóm tắt bài học
- Prompts là slash command craft sẵn — User trigger, server craft, Claude execute. User không cần là prompt engineer.
- Decorator
@mcp.prompt()giống tool/resource — Name, description, params với Field. Returnlist[base.Message]. - Message list hỗ trợ multi-turn / few-shot — UserMessage + AssistantMessage mix để set up context, show example, sau đó task thật.
- Server author đóng gói expertise — Prompt tốt = domain knowledge + best-practice + consistency. Deploy 1 lần, mọi user hưởng lợi.
- Test qua Inspector bắt buộc — Verify interpolation, verify guidelines, verify Claude produces expected output trước khi ship.
Tài liệu tham khảo
- MCP spec — Prompts
- FastMCP Prompts module
- "Defining prompts in MCP" — Anthropic Academy video