Tự tạo MCP server với Claude Code: hướng dẫn từng bước (2026)
Tạo MCP server với Claude Code nghĩa là bạn tự viết một dịch vụ nhỏ expose tools/resources theo Model Context Protocol, rồi cắm thẳng vào Claude Code bằng claude mcp add. Lộ trình 3 bước: (1) thiết kế và viết tool bằng Python hoặc TypeScript, (2) chọn transport (mặc định stdio cho local), (3) nối vào Claude Code và test. Có hai cách làm: tự tay viết để hiểu bản chất, hoặc để chính Claude Code scaffold hộ. Bài này đi trọn cả hai mạch.
· Tác giả: Jasmine - dev dùng Claude Code hằng ngày, đã tự viết và deploy vài MCP server nội bộ cho team.
MCP server là gì? (nhắc nhanh)
MCP server là một tiến trình nhỏ expose ba loại năng lực - tools (hàm agent gọi được), resources (dữ liệu đọc được) và prompts (mẫu prompt tái dùng) - cho một LLM qua Model Context Protocol. Nói cách khác, nó là "adapter" chuẩn hoá giữa Claude Code và thế giới bên ngoài: API nội bộ, database, hệ thống file, hay bất kỳ dịch vụ nào bạn muốn Claude gọi được một cách có kiểm soát.
Điểm hay của MCP là chuẩn mở: viết server một lần, dùng được ở nhiều host (Claude Code, Claude Desktop, và các client khác). Bài này không đào sâu khái niệm - phần đó thuộc về bài MCP là gì. Ở đây ta tập trung vào việc tự viết một server rồi nối thẳng vào Claude Code, phần mà tài liệu chính thức thường để rời.
Khi nào cần tự viết MCP server (thay vì dùng server có sẵn)?
Trước khi gõ dòng code nào, hãy tự hỏi: server này đã có người viết chưa? Rất nhiều nhu cầu phổ biến đã có server chính chủ hoặc cộng đồng - GitHub, Playwright, Sentry, filesystem... Cắm cái có sẵn luôn nhanh hơn viết mới.
| Tình huống | Nên làm |
|---|---|
| Cần thao tác GitHub, chạy browser, đọc lỗi Sentry | Dùng server có sẵn - chỉ claude mcp add |
| API nội bộ của công ty, chưa ai wrap | Tự viết MCP server |
| Database riêng, schema đặc thù | Tự viết (kiểm soát truy vấn, phân quyền) |
| Workflow nhiều bước ghép nhiều hệ thống | Tự viết, gói theo workflow |
| Chỉ cần đọc vài file local | Dùng filesystem server có sẵn |
Quy tắc của mình: viết MCP server riêng khi dữ liệu/logic là của bạn và chưa có adapter chuẩn. Đừng viết lại cái người ta đã làm tốt.
Chuẩn bị trước khi bắt đầu
Checklist gọn để không vấp giữa chừng:
- Claude Code đã cài và đăng nhập (gói Pro/Max hoặc API key đều dùng được).
- Runtime: Node.js 18+ (cho TypeScript) hoặc Python 3.10+ (cho Python).
- Chọn SDK:
FastMCP/ Python SDK nếu bạn quen Python; TypeScript SDK (@modelcontextprotocol/sdk) nếu bạn ở hệ Node. - Một mục tiêu cụ thể: ví dụ "tool tra cứu đơn hàng từ API nội bộ". Đừng khởi động bằng server chung chung.
Nếu chưa quen Claude Code, đọc trước bài Claude Code là gì để nắm cách chạy phiên làm việc và cấp quyền.
Cách 1 - Tự tay viết MCP server
Làm tay một lần giúp bạn hiểu chuyện gì đang xảy ra khi Claude gọi tool. Sau đó bạn có thể tự động hoá thoải mái. Năm bước dưới đây đi từ thiết kế đến chạy thử.
Bước 1 - Thiết kế tool theo workflow, không wrap từng endpoint
Sai lầm phổ biến nhất: ánh xạ 1-1 mỗi REST endpoint thành một tool. Kết quả là agent phải gọi 5 tool để làm một việc, dễ lạc. Hãy thiết kế agent-centric:
- Gộp thao tác theo mục đích: một tool
get_order_summarytrả về đủ thông tin thay vì bắt agent ghépget_order+get_customer+get_items. - Output cho con người/agent đọc được: trả tên trường rõ ràng, không phải mã nội bộ.
- Error message "dạy" agent cách dùng đúng: báo lỗi kèm gợi ý sửa, không chỉ ném stack trace.
Nguyên tắc này lấy từ best-practices của skill ak-mcp-builder - chỗ nhiều tutorial generic bỏ qua.
Bước 2 - Khởi tạo project + cài SDK
Đặt tên rõ ràng để về sau nhìn là biết: Python dùng {service}_mcp, TypeScript dùng {service}-mcp-server.
Python (FastMCP):
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install "mcp[cli]" # hoặc: pip install fastmcp
# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("orders_mcp")
if __name__ == "__main__":
mcp.run() # mặc định transport stdio
TypeScript (MCP SDK):
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({ name: "orders-mcp-server", version: "1.0.0" });
const transport = new StdioServerTransport();
await server.connect(transport);
Bước 3 - Viết tool đầu tiên (có ví dụ chạy được)
Một tool tốt có ba phần: input schema chặt chẽ, description rõ ràng (agent đọc để biết khi nào gọi), và tool annotations mô tả hành vi. Ví dụ tool tra cứu đơn hàng:
Python:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("orders_mcp")
@mcp.tool(
annotations={
"readOnlyHint": True, # chỉ đọc, không đổi dữ liệu
"idempotentHint": True, # gọi lại cho kết quả như nhau
"openWorldHint": True, # có gọi hệ thống bên ngoài
}
)
def get_order_summary(order_id: str) -> str:
"""Tra cứu tóm tắt một đơn hàng theo mã đơn.
Dùng khi người dùng hỏi trạng thái/tổng tiền/khách của một đơn cụ thể."""
order = fetch_order(order_id) # gọi API nội bộ của bạn
if order is None:
return f"Không tìm thấy đơn '{order_id}'. Kiểm tra lại mã đơn (dạng ORD-xxxxx)."
return (
f"Đơn {order['id']} | Khách: {order['customer']} | "
f"Trạng thái: {order['status']} | Tổng: {order['total']:,}đ"
)
TypeScript (dùng Zod cho input schema):
import { z } from "zod";
server.registerTool(
"get_order_summary",
{
description: "Tra cứu tóm tắt một đơn hàng theo mã đơn.",
inputSchema: { order_id: z.string().describe("Mã đơn, dạng ORD-xxxxx") },
annotations: { readOnlyHint: true, idempotentHint: true, openWorldHint: true },
},
async ({ order_id }) => {
const order = await fetchOrder(order_id);
const text = order
? `Đơn ${order.id} | Khách: ${order.customer} | Trạng thái: ${order.status}`
: `Không tìm thấy đơn '${order_id}'.`;
return { content: [{ type: "text", text }] };
}
);
Output mẫu khi Claude gọi tool: Đơn ORD-10231 | Khách: Nguyễn An | Trạng thái: Đang giao | Tổng: 540.000đ. Lưu ý: annotations chỉ là hint gợi ý cho host, không phải cơ chế bảo mật - đừng dựa vào readOnlyHint để chặn ghi dữ liệu.
Bước 4 - Chọn transport (stdio / HTTP / SSE)
Transport quyết định cách host nói chuyện với server. Chọn theo tình huống thay vì mặc định bừa:
| Transport | Dùng khi | Ưu / nhược |
|---|---|---|
| stdio | Server chạy local, 1 client (Claude Code trên máy bạn) | Đơn giản nhất, không cần mạng · chỉ 1 client |
| HTTP (streamable) | Server remote, nhiều client, deploy lên hạ tầng riêng | Chia sẻ được, scale được · phải lo auth/OAuth |
| SSE | Cần đẩy sự kiện real-time (đang dần được HTTP streamable thay thế) | Streaming tốt · dạng cũ hơn |
Mặc định cho local + Claude Code: dùng stdio. Chỉ chuyển HTTP khi cần chia sẻ server cho nhiều người hoặc deploy remote.
Bước 5 - Chạy thử & test đúng cách
Đây là chỗ nhiều người vấp - và ít tutorial cảnh báo:
- Server là tiến trình long-running. Chạy thẳng
python server.pysẽ "treo" terminal vì nó đứng chờ input qua stdio - đó là bình thường, không phải lỗi. Để kiểm tra nhanh mà không kẹt, dùngtimeout 5s python server.py, hoặc chạy trong tmux/một pane riêng, hoặc dùng eval harness. - Với stdio: TUYỆT ĐỐI không log ra stdout. stdout là kênh giao thức - in
print()ra đó sẽ phá luồng JSON-RPC và server "chết" khó hiểu. Log ra stderr (Python:print(..., file=sys.stderr)hoặc dùnglogging). - Kiểm tra biên dịch trước: Python
python -m py_compile server.py; TypeScriptnpm run build.
Cách test đáng tin nhất là eval-driven: viết một kịch bản gọi thử từng tool với input mẫu rồi khẳng định output, chạy trong một tiến trình con có timeout. Làm vậy bạn bắt được lỗi schema và lỗi giao thức ngay, thay vì đợi đến lúc cắm vào Claude Code mới phát hiện tool "im lặng" không phản hồi.
Cách 2 - Để Claude Code tự viết MCP server cho bạn
Khi đã hiểu cấu trúc ở Cách 1, bạn không cần gõ boilerplate lại từ đầu mỗi lần. Đây là "meta-angle" thú vị: dùng chính Claude Code để viết MCP server cho Claude Code.
Prompt pattern mình hay dùng, chia nhỏ để agent bám việc:
Viết một MCP server Python tên orders_mcp bằng FastMCP.
- Tool get_order_summary(order_id) gọi API nội bộ tại BASE_URL (đọc từ env).
- Input schema chặt, description rõ, annotations readOnlyHint/idempotentHint.
- Log ra stderr, KHÔNG stdout. Transport stdio.
Sau đó viết một script test dùng timeout để không treo, và giải thích cách nối vào Claude Code.
Claude Code sẽ scaffold project, viết tool + schema, và nếu bạn yêu cầu, tự viết bước test. Bạn review lại là chính - nó lo phần lặp đi lặp lại. Mẹo: nhắc agent nêu rõ các giả định (đường dẫn, tên env var) trước khi viết, và yêu cầu nó tự chạy py_compile/npm run build để xác nhận code biên dịch được ngay trong phiên. Nhờ vậy bạn nhận về một server đã qua kiểm tra sơ bộ thay vì đống code chưa chạy thử.
Nếu làm nhiều server, đáng để dùng skill dựng sẵn thay vì tự nhớ hết best-practices. Trong Engineer Kit của AgentKit (skill ak-mcp-builder) có sẵn quy trình build MCP server nhiều pha (nghiên cứu → hiện thực → review → eval), đóng gói luôn best-practices về tool design và một eval harness để test - đỡ phải nhớ hết phần boilerplate và các bẫy stdio/long-running ở trên. Xem chi tiết ở bài Engineer Kit của AgentKit có gì.
Nói rõ kẻo nhầm: "AgentKit" ở đây là bộ kit skills cho Claude Code (agentkit.best, CLI ak), khác sản phẩm "AgentKit" của OpenAI. Cách tiếp cận của mình: làm tay một lần để hiểu bản chất, rồi dùng skill cho nhanh.
Nối MCP server vào Claude Code
Có server rồi, giờ cắm vào Claude Code. Lệnh cốt lõi là claude mcp add.
Server stdio local - truyền command sau dấu --:
claude mcp add orders -- python /duong/dan/server.py
# hoặc TypeScript đã build:
claude mcp add orders -- node /duong/dan/dist/index.js
Server remote (HTTP):
claude mcp add orders --transport http https://mcp.congty.vn/orders
Kiểm tra kết nối:
claude mcp list # thấy: orders ✔ Connected
claude mcp get orders # xem chi tiết cấu hình một server
Scope quyết định server dùng ở đâu: local (chỉ bạn, chỉ project này), project (commit cho cả team), user (mọi project của bạn). Với server dùng chung team, viết tay file .mcp.json ở gốc repo và commit:
{
"mcpServers": {
"orders": {
"command": "python",
"args": ["server.py"],
"env": { "BASE_URL": "https://api.noi-bo.vn" }
}
}
}
Sau khi sửa .mcp.json, nhớ restart phiên Claude Code để nạp lại.
Lỗi thường gặp & cách khắc phục
| Triệu chứng | Nguyên nhân thường gặp | Cách fix |
|---|---|---|
Failed to connect | Sai command/đường dẫn/URL | Chạy claude mcp get <name> soi lại; test command chạy độc lập |
| Server "chết" ngay khi start | Log ra stdout, phá giao thức stdio | Chuyển mọi log sang stderr |
| Tools không hiện trong Claude | Thiếu env var / API key | Truyền qua --env KEY=value hoặc khai trong .mcp.json |
| Timeout lần chạy đầu (npx tải package) | Tải dependency lâu hơn timeout mặc định | Đặt MCP_TIMEOUT=60000 khi khởi động |
Sửa .mcp.json nhưng không có gì đổi | Phiên chưa nạp lại cấu hình | Restart phiên Claude Code |
Best practices khi viết MCP server
Gói lại những gì đáng nhớ (nhiều điểm lấy từ ak-mcp-builder):
- Đặt tên tool theo mẫu
{service}_{action}_{resource}(snake_case), có prefix service để tránh trùng khi cắm nhiều server. - Hỗ trợ cả JSON và Markdown cho output - agent đọc dữ liệu có cấu trúc tốt, người đọc bản Markdown dễ hơn.
- Phân trang cho tool trả nhiều dữ liệu: dùng
limit,has_more,next_offsetthay vì trả nghìn dòng. - Giới hạn độ dài output (rule of thumb ~25.000 ký tự) và truncate có hướng dẫn ("còn N kết quả, dùng offset...").
- Error message actionable: nói rõ sai gì và cách sửa.
- Bảo mật: validate input, để API key trong env (không hardcode), không lộ lỗi nội bộ/stack trace ra agent. Nhớ annotations chỉ là hint, không thay được kiểm soát quyền thật.
Muốn tự động hoá thêm quy trình dev? Xem cách tạo custom skill cho Claude Code và dùng subagents trong Claude Code để chia việc build/test.
Câu hỏi thường gặp (FAQ)
Viết MCP server bằng ngôn ngữ gì?
Phổ biến nhất là Python (FastMCP / Python SDK) và TypeScript (@modelcontextprotocol/sdk). MCP còn có SDK cho các ngôn ngữ khác, nhưng với Claude Code thì Python hoặc TS là lựa chọn nhanh và nhiều ví dụ nhất.
Khác gì so với kết nối một server có sẵn?
Kết nối server có sẵn chỉ cần claude mcp add trỏ tới server người khác đã viết. Tự viết server là khi bạn cần expose logic/dữ liệu riêng (API nội bộ, DB) mà chưa ai làm adapter.
Claude Code có tự viết được MCP server không?
Có. Bạn mô tả tool, schema và transport mong muốn, Claude Code sẽ scaffold project, viết tool và cả script test. Bạn chỉ cần review. Skill ak-mcp-builder đóng gói sẵn quy trình này kèm best-practices.
Deploy server remote (HTTP) thế nào?
Chạy server với transport HTTP streamable trên hạ tầng của bạn, rồi claude mcp add <name> --transport http <url>. Server remote cần lo thêm xác thực (OAuth/token) - khác hẳn stdio local vốn tin cậy máy của bạn.
Cần Claude Code Pro không?
Không bắt buộc gói cụ thể. MCP hoạt động với Claude Code khi bạn đã đăng nhập - dùng gói Pro/Max hay API key đều được. Việc viết server không tốn thêm phí ngoài chi phí model bạn đang dùng.
ak-mcp-builder là gì và có bắt buộc không?
ak-mcp-builder là một skill trong Engineer Kit của AgentKit giúp dựng MCP server theo quy trình nhiều pha kèm eval harness. Không bắt buộc - bạn hoàn toàn viết tay được như Cách 1. Nó chỉ giúp nhanh và bớt vấp best-practices khi làm nhiều server.
Kết luận & bước tiếp theo
Tóm lại có hai cách tạo MCP server với Claude Code: tự tay viết (thiết kế tool theo workflow → cài SDK → viết tool → chọn transport → test bằng timeout/stderr) để hiểu bản chất, và để Claude Code viết hộ khi cần tốc độ. Dù chọn cách nào, mấu chốt vẫn là nối vào Claude Code bằng claude mcp add hoặc .mcp.json và kiểm tra ✔ Connected. Lời khuyên của mình: làm tay một lần cho hiểu, rồi tự động hoá.
Bước tiếp theo, thử biến quy trình build này thành một custom skill riêng. Nếu muốn có sẵn quy trình build MCP server chuẩn hoá kèm eval, tham khảo Engineer Kit của AgentKit — giảm 20%, còn $79.20 (skill ak-mcp-builder).
Nguồn tham khảo: Model Context Protocol - spec & hướng dẫn build server (bản versioned 2026-07-28); tài liệu Claude Code - MCP & claude mcp add (v2.1.219). Xác minh câu lệnh trực tiếp trước khi dùng vì CLI cập nhật nhanh.