Bạn đã đi qua 10 bài. Đây là mảnh ghép cuối cùng của stack MCP đầy đủ:
- Extend MCPClient class với 2 method cuối cùng: list_prompts() và get_prompt()
- Hiểu cơ chế "variable interpolation" từ client args sang server prompt body
- Implement slash command UX trong CLI: detect /command, show autocomplete, dispatch prompt
- Gửi messages từ prompt template vào agentic loop đúng cách
- Hoàn thành workflow end-to-end: /format plan.md → prompt craft → Claude → tool call → result
Mở đầu: Mảnh ghép cuối của bức tranh MCP
┌───────────────────────────────────────────────────────────┐
│ │
│ PRIMITIVE SERVER SIDE CLIENT SIDE │
│ │
│ Tools ✅ Bài 7.4 ✅ Bài 7.6 │
│ Resources ✅ Bài 7.7 ✅ Bài 7.8 │
│ Prompts ✅ Bài 7.9 🎯 Bài 7.10 │
│ │
│ Inspector ✅ Bài 7.5 │
│ │
└───────────────────────────────────────────────────────────┘
Sau bài này, bạn sẽ có một CLI chat app đầy đủ tính năng:
- Hỏi tự do → Claude dùng tools tự quyết
@mention doc→ app inject resources/slash-command→ user trigger prompts
3 primitive × 3 trigger mode khác nhau — chính là cái Claude Desktop, Cursor, Zed có. Bạn tự build.
Client method: list_prompts()
Pattern giống list_tools() và list_resources():
async def list_prompts(self) -> list[types.Prompt]:
"""List all prompts defined on the server."""
result = await self.session().list_prompts()
return result.prompts
Mỗi types.Prompt object có:
name— slash command namedescription— hiển thị trong autocompletearguments— listPromptArgumentvới name, description, required
Output test:
prompts = await client.list_prompts()
for p in prompts:
args_str = ", ".join(a.name for a in (p.arguments or []))
print(f"/{p.name}({args_str}) - {p.description}")
Ra:
/format(doc_id) - Rewrites the contents of the document in Markdown format.
/summarize(doc_id, length) - Summarize document with configurable length.
Client method: get_prompt()
Đây là nơi variable interpolation xảy ra. User click /format, nhập doc_id=plan.md, client gọi:
async def get_prompt(
self, prompt_name: str, args: dict[str, str]
) -> list[types.PromptMessage]:
"""Get a prompt with arguments interpolated."""
result = await self.session().get_prompt(prompt_name, args)
return result.messages
Cách interpolation chạy
User input: /format doc_id=plan.md
│
▼
Client: client.get_prompt("format", {"doc_id": "plan.md"})
│
▼
Server route to: format_document(doc_id="plan.md")
│ (SDK chuyển args dict → function kwargs)
▼
Function body: prompt = f"""Reformat {doc_id}..."""
│ f-string interpolate → "Reformat plan.md..."
▼
Return: [UserMessage("Reformat plan.md...")]
│
▼
Client receive: list[PromptMessage]
Key: args dict keys phải match với parameter names của function server-side. Sai 1 chữ → ValidationError.
Code đầy đủ thêm vào MCPClient
async def list_prompts(self) -> list[types.Prompt]:
result = await self.session().list_prompts()
return result.prompts
async def get_prompt(
self, prompt_name: str, args: dict[str, str]
) -> list[types.PromptMessage]:
result = await self.session().get_prompt(prompt_name, args)
return result.messages
Thêm vào mcp_client.py. Bây giờ class MCPClient đã có đầy đủ 5 method:
| Method | Protocol call | Primitive |
|---|---|---|
| list_tools() | tools/list | Tools |
| call_tool(name, input) | tools/call | Tools |
| list_resources() | resources/list | Resources |
| read_resource(uri) | resources/read | Resources |
| list_prompts() | prompts/list | Prompts |
| get_prompt(name, args) | prompts/get | Prompts |
(6 method thật, nhưng list_resource_templates đếm chung với list_resources về mặt logic.)
Test prompt flow trong CLI
Quick test trong mcp_client.py main harness:
async def main():
async with MCPClient(
command="uv",
args=["run", "mcp_server.py"],
) as client:
prompts = await client.list_prompts()
print(f"Available prompts: {[p.name for p in prompts]}")
messages = await client.get_prompt(
"format",
{"doc_id": "plan.md"}
)
for msg in messages:
# content có thể là TextContent object, không phải plain string
content = msg.content
text = content.text if hasattr(content, 'text') else str(content)
print(f"[{msg.role}] {text[:80]}...")
if __name__ == "__main__":
asyncio.run(main())
Output:
Available prompts: ['format']
[user] Your goal is to reformat a document to be written with markdown...
Notice: msg.content là một TextContent object (giống như content từ Claude API), không phải plain string. Khi ghép vào main chat, bạn cần extract .text — xem hàm prompt_messages_to_claude() dưới đây cho pattern chuẩn.
Ghép slash commands vào CLI chat
Plan
- Pre-fetch prompts list ở startup
- Detect
/ở đầu user input - Parse
/command arg1=value1 arg2=value2 - Gọi
get_prompt()→ nhận messages - Convert messages sang format Anthropic API expect
- Append vào conversation, tiếp tục agentic loop
Code: update main.py
import re
def parse_slash_command(user_input: str) -> tuple[str, dict] | None:
"""Parse '/command arg1=val1 arg2=val2' format."""
if not user_input.startswith('/'):
return None
parts = user_input[1:].split()
if not parts:
return None
command = parts[0]
args = {}
for arg in parts[1:]:
if '=' in arg:
key, value = arg.split('=', 1)
args[key] = value
return command, args
def prompt_messages_to_claude(messages):
"""Convert MCP PromptMessage list to Anthropic messages format."""
out = []
for msg in messages:
content = msg.content
# content is TextContent or ImageContent
if hasattr(content, 'text'):
out.append({
"role": msg.role,
"content": content.text,
})
elif isinstance(content, list):
# Multi-part content
out.append({
"role": msg.role,
"content": [
{"type": "text", "text": c.text}
for c in content if hasattr(c, 'text')
],
})
return out
prompts = await mcp.list_prompts()
prompt_names = {p.name for p in prompts}
while True:
raw_input = input("\nBạn > ").strip()
if raw_input.lower() in {"quit", "exit"}:
break
# Check slash command
parsed = parse_slash_command(raw_input)
if parsed and parsed[0] in prompt_names:
cmd_name, cmd_args = parsed
print(f"[Running prompt: /{cmd_name}]")
prompt_msgs = await mcp.get_prompt(cmd_name, cmd_args)
claude_msgs = prompt_messages_to_claude(prompt_msgs)
messages.extend(claude_msgs)
else:
# Normal input (+ @mention handling từ Bài 7.8)
augmented = await build_augmented_prompt(raw_input, mcp)
messages.append({"role": "user", "content": augmented})
# Agentic loop (same as Bài 7.6)
while True:
response = claude.messages.create(...)
...
Test end-to-end
Chạy uv run main.py. Thử:
Bạn > /format doc_id=plan.md
[Running prompt: /format]
[Tool call: read_doc_contents({'doc_id': 'plan.md'})]
[Tool call: edit_document({'doc_id': 'plan.md', 'old_str': '...', 'new_str': '...'})]
Claude > I've reformatted plan.md to Markdown. Changes:
- Added H2 header for main section
- Converted inline list to bullets
- Added code block for example
Magic. User gõ 1 slash command → Claude execute full workflow, gọi tools, trả kết quả. Đó là giá trị của Prompts.
Hỗ trợ autocomplete cho slash commands
Professional UX: user gõ /, thấy popup prompts available.
Với prompt_toolkit:
from prompt_toolkit import PromptSession
from prompt_toolkit.completion import NestedCompleter
async def build_completer(mcp: MCPClient):
prompts = await mcp.list_prompts()
slash_commands = {}
for p in prompts:
args = [a.name for a in (p.arguments or [])]
slash_commands[f"/{p.name}"] = {
f"{a}=": None for a in args
}
return NestedCompleter.from_nested_dict(slash_commands)
completer = await build_completer(mcp)
session = PromptSession(completer=completer)
while True:
user_input = await session.prompt_async("\nBạn > ")
...
User gõ / → popup list commands. Gõ /format → popup doc_id=. Professional UX.
Bảng so sánh: 3 activation patterns hoàn chỉnh
Với full MCP stack, user có 3 cách trigger action:
| Pattern | User type | Primitive | Flow |
|---|---|---|---|
| Natural | "Show me plan.md" | Tools | Claude tool_use loop |
| @mention | "Tóm tắt @plan.md" | Resources | App inject context |
| Slash command | "/format doc_id=plan.md" | Prompts | Server template + tools |
Khi user nào dùng gì?
- New user → natural language (easy, Claude handles)
- Power user → @mention (fast, explicit context)
- Repeat workflow → slash command (one-click, craft prompt)
Good app provide cả 3. Great app make them discoverable (hint @, hint / khi user "khựng").
Ví dụ thực chiến: Daily workflow với slash commands
Tình huống
Bạn là analyst tại công ty fintech. Mỗi sáng cần:
- Review overnight trades
- Summarize market moves
- Draft client email
Trước MCP prompts
- Mở dashboard → copy numbers
- Open news feed → read, summarize mentally
- Open Gmail → write from scratch
- Time: ~45 phút
Với MCP prompts setup
Bạn tạo 3 prompts trên MCP server internal:
/trade-review date=today
/market-summary region=asia
/client-email account=<X> context=<Y>
Sáng thứ 2, gõ 3 command:
/trade-review date=today→ Claude query DB, summary trades, flag anomaly/market-summary region=asia→ fetch news feed, summarize/client-email account=abc context=weekly-update→ draft email dùng 2 output trên
Time: ~5 phút, output chất lượng cao vì prompt đã craft bởi senior analyst.
Ví dụ theo ngành — Slash command ecosystems
DevOps / SRE
Commands:
/incident-summary {channel} {timeframe}— summarize Slack incident thread/deploy-check {service} {version}— pre-deploy checklist/runbook {alert}— pull matching runbook
Pattern: On-call engineer quick context. No more "which runbook was it?"
Content marketing
Commands:
/blog-outline {topic} {tone}— generate outline/seo-check {url}— audit SEO issues/social-variants {post}— X/LinkedIn/Threads versions
Pattern: Writer spawn variants quickly. Consistency across channels.
Finance
Commands:
/variance-report {entity} {period}— budget vs actual/cashflow-forecast {horizon}— rolling forecast/expense-category {receipt_id}— categorize
Pattern: Monthly close accelerate. FP&A team productivity up.
Teaching / Training
Commands:
/quiz-generate {topic} {difficulty}— make quiz/rubric {assignment}— grading rubric/feedback {student_work}— structured feedback
Pattern: Teacher save 60%+ time on repetitive content gen.
Internal ops
Commands:
/onboard {role} {start_date}— onboarding kit/review-prep {employee}— perf review context/policy-lookup {question}— find policy answer
Pattern: HR / manager self-serve. IT overhead giảm.
Anti-patterns — Những sai lầm cần tránh
Ignore PromptArgument.required
Sai lầm:
messages = await mcp.get_prompt("format", {})
- Tại sao là sai
- Server throw error "required arg doc_id missing". Ugly crash UX.
- Cách đúng
- Validate args trước khi gọi:
prompt_def = next(p for p in prompts if p.name == cmd_name)
required = [a.name for a in prompt_def.arguments if a.required]
missing = set(required) - set(args.keys())
if missing:
print(f"Missing required args: {missing}")
continue
Shadow existing commands
- Sai lầm
- App có built-in
/help, add MCP server cũng có/help. - Tại sao là sai
- Xung đột. User confused.
- Cách đúng
- Namespace MCP prompts:
/<server_name>/<command>. Hoặc disable built-in khi có clash.
Không display prompt messages cho user
- Sai lầm
- Slash command chạy silent. User không biết prompt gửi gì cho Claude.
- Tại sao là sai
- Debugging khó. User không verify được.
- Cách đúng
- Option
--verbosehoặc debug mode show prompt body:
if args.get('debug') == 'true':
print(f"[Prompt body]\n{msg.content.text[:500]}...")
Arg parsing sơ sài
Sai lầm:
args = user_input.split()[1:]
- Tại sao là sai
- Value có space (
/format style="code block") break. - Cách đúng
- Dùng shlex hoặc proper parser:
import shlex
tokens = shlex.split(user_input[1:])
Chạy prompt message như tool output
- Sai lầm
- Nhét prompt messages vào
tool_resultblock. - Tại sao là sai
- Sai semantic. Claude confused.
- Cách đúng
- Prompt messages là user/assistant messages. Append vào
messages[]như user input thường.
Quên role field
Sai lầm:
messages.append({"content": msg.content.text}) # missing role
- Tại sao là sai
- Anthropic API strict về shape.
- Cách đúng
- Always include
role:
messages.append({"role": msg.role, "content": msg.content.text})
Mẹo nâng cao
Mẹo 1: Prompt discovery trong UI
Show all prompts khi user lost:
if user_input.strip() == '/':
for p in prompts:
print(f" /{p.name} - {p.description}")
continue
Mẹo 2: Prompt với default args
Nếu user không cung cấp arg optional, client tự fill default:
prompt_def = next(p for p in prompts if p.name == cmd_name)
for arg in (prompt_def.arguments or []):
if not arg.required and arg.name not in args:
# Default logic
args[arg.name] = "medium" # or fetch from config
Mẹo 3: Server-side argument completion
MCP spec hỗ trợ completion cho prompt args:
completions = await mcp.session().complete(
{"type": "ref/prompt", "name": "format"},
{"name": "doc_id", "value": "re"}
)
Client dùng list này show autocomplete realtime.
Mẹo 4: Combine prompt + resource
Pattern powerful: pre-fetch context qua resource, pass vào prompt template.
Áp dụng ngay
Bài tập 1: Add prompt methods (~10 phút)
- Bước 1
- Mở
mcp_client.py. Addlist_prompts()vàget_prompt()như bài. - Bước 2
- Update test harness:
prompts = await client.list_prompts()
print(f"Prompts: {[p.name for p in prompts]}")
messages = await client.get_prompt("format", {"doc_id": "plan.md"})
print(f"Messages: {messages}")
Bước 3: Run uv run mcp_client.py. Ghi lại:
- Số prompts list ra: ___________
- Messages có bao nhiêu item: ___________
- Type của
messages[0].content: ___________
Bài tập 2: Slash command trong chat (~20 phút)
- Bước 1
- Mở
main.py. Addparse_slash_commandvàprompt_messages_to_claude. - Bước 2
- Trong chat loop, check slash command trước natural input.
- Bước 3
- Test:
/format doc_id=plan.md/format(no args — error message graceful?)/unknown(command không tồn tại — fallback natural?)
Bước 4: Ghi lại:
- Claude execute đúng tool chain không? ___________
- UX khi command sai thế nào? ___________
- Bạn có thêm validation arg không? ___________
Bài tập 3 (thử thách): Autocomplete slash (~15 phút)
Cài prompt_toolkit:
uv add prompt_toolkit
Implement autocomplete như mẹo ở trên. Verify:
- Gõ
/→ thấy dropdown - Gõ
/f→ filter to/format - Gõ
/format(với space) → thấy arg hints
Ghi lại:
- UX có native feel không? ___________
- Latency tạo autocomplete? ___________
Tóm tắt bài học
- 2 method đơn giản hoàn thành client —
list_prompts()+get_prompt(name, args). Pattern giống tools/resources. - Variable interpolation tại server — Client gửi args dict, server function nhận qua kwargs, f-string interpolate. Một flow rõ ràng.
- Slash command = prompt trigger —
/command arg=value→ parse → get_prompt → inject messages → agentic loop. - 3 activation patterns hoàn chỉnh — Natural (tools), @mention (resources), / (prompts). Mỗi cái có chỗ tốt nhất.
- MCP stack đã đầy đủ — Bạn đã build client + server với đủ 3 primitive. Giờ có thể phát triển thành production app.
Tài liệu tham khảo
- MCP spec — Prompts
- prompt_toolkit docs
- "Prompts in the client" — Anthropic Academy video