MCP server là gì? Tự viết MCP server bằng Python cho người mới (2026)

AI

Trong bài này
  1. Cần chuẩn bị
  2. Bước 1: Hiểu MCP server là gì
  3. Bước 2: Kiểm tra phiên bản SDK trên PyPI
  4. Bước 3: Tạo môi trường ảo và cài SDK
  5. Bước 4: Viết MCP server đầu tiên
  6. Bước 5: Viết client để xem AI "nhìn thấy" gì
  7. Bước 6: Gọi tool và đọc kết quả
  8. Bước 7: Kiểm tra bằng MCP Inspector
  9. Bước 8: Gắn server vào VS Code
  10. Lỗi thường gặp
  11. 1. ModuleNotFoundError: No module named 'mcp.server.fastmcp'
  12. 2. Tool báo lỗi nhưng chỉ có "Error executing tool ..."
  13. 3. Dùng print() trong server chạy stdio
  14. 4. Chạy python server.py thấy "treo"
  15. Câu hỏi thường gặp
  16. MCP server khác API thông thường thế nào?
  17. Có cần trả tiền hay API key để viết MCP server không?
  18. Mình học Android, viết MCP server bằng Kotlin được không?
  19. Có nên cài MCP server người khác viết sẵn không?
  20. Nguồn tham khảo

Dạo này các bạn hay thấy cụm từ "MCP server" trong VS Code, Claude, ChatGPT hay Android Studio. Bài này giải thích MCP server là gì bằng ví dụ cụ thể: mình sẽ cùng các bạn tự viết một MCP server nhỏ bằng Python (tra lịch thi và tính điểm trung bình môn), gọi thử nó bằng code và bằng công cụ MCP Inspector chính thức, rồi xem những lỗi người mới hay gặp.

Làm hết bài mất khoảng 30 phút. Bài áp dụng cho Python SDK mcp bản 2.3.0 (phát hành 02/10/2026, theo đặc tả MCP 2026-07-28) và MCP Inspector 2.14.0. Mọi lệnh và kết quả trong ảnh đều chạy thật ngày 10/11/2026.

Video demo trên kênh Huy Nguyen - Android A - Z.

Cần chuẩn bị

  • Python 3.10 trở lên (trang PyPI của gói mcp ghi hỗ trợ 3.10 đến 3.14). Mình dùng Python 3.12.
  • Node.js để chạy MCP Inspector bằng npx (mình dùng Node 22).
  • Một thư mục trống, ví dụ tro-ly-hoc-tap, và một trình soạn code (VS Code là đủ).
  • Không cần tài khoản hay API key nào: server và client chạy hoàn toàn trên máy các bạn.

Bước 1: Hiểu MCP server là gì

MCP (Model Context Protocol) là một chuẩn mở để ứng dụng AI kết nối với hệ thống bên ngoài. Trang chính thức ví MCP như "cổng USB-C cho ứng dụng AI": thay vì mỗi ứng dụng AI tự viết cách đọc file, gọi database hay gọi API, ta viết một MCP server, và mọi ứng dụng hỗ trợ MCP (gọi là host, ví dụ VS Code, Claude Desktop) đều dùng lại được.

Một MCP server cung cấp ba loại thứ cho AI: tools (hàm mà AI được phép gọi, ví dụ tính điểm), resources (dữ liệu để đọc, giống file) và prompts (mẫu câu lệnh soạn sẵn). Trong bài này mình làm 2 tool và 1 resource.

Trang What is the Model Context Protocol trên modelcontextprotocol.io, phiên bản đặc tả 2026-07-28
Bước 1: trang giới thiệu MCP chính thức, đang ở phiên bản 2026-07-28.

Bước 2: Kiểm tra phiên bản SDK trên PyPI

Trước khi cài, các bạn mở trang PyPI của gói mcp để xem bản mới nhất. Điều này quan trọng vì từ bản 2.0 (28/07/2026) cách viết code đã đổi: lớp FastMCP mà nhiều bài hướng dẫn cũ dùng đã được đổi tên thành MCPServer.

Trang PyPI của gói mcp hiển thị phiên bản 2.3.0, phát hành ngày 2/10/2026
Bước 2: gói mcp trên PyPI, bản 2.3.0 phát hành 02/10/2026.

Bước 3: Tạo môi trường ảo và cài SDK

Trong thư mục dự án, các bạn tạo môi trường ảo rồi cài gói mcp kèm phần cli. Trên Windows, lệnh kích hoạt là .venv\Scripts\activate thay cho dòng source.

python -m venv .venv
source .venv/bin/activate
pip install "mcp[cli]"
pip show mcp
Terminal cài mcp[cli] bằng pip, pip show mcp báo Version 2.3.0, Python 3.12.16
Bước 3: cài xong, pip show mcp báo bản 2.3.0.

Bước 4: Viết MCP server đầu tiên

Tạo file lich_thi.json chứa vài môn mẫu (mã môn, ngày thi, ca, phòng), rồi tạo server.py như dưới đây. Mỗi hàm có @mcp.tool() sẽ thành một tool. SDK tự đọc tên hàm, docstring và kiểu dữ liệu để mô tả tool cho AI, nên các bạn viết docstring rõ ràng, có thể viết tiếng Việt.

import json
from pathlib import Path
from typing import Annotated

from pydantic import Field
from mcp.server import MCPServer
from mcp.server.mcpserver.exceptions import ToolError

mcp = MCPServer("tro-ly-hoc-tap")
DATA = Path(__file__).with_name("lich_thi.json")


@mcp.tool()
def tra_lich_thi(ma_mon: Annotated[str, Field(description="Mã môn, ví dụ MOB201")]) -> dict:
    """Tra lịch thi (ngày, ca, phòng, hình thức) của một môn theo mã môn."""
    lich = json.loads(DATA.read_text(encoding="utf-8"))
    for mon in lich:
        if mon["ma_mon"].lower() == ma_mon.strip().lower():
            return mon
    raise ToolError(f"Không tìm thấy môn {ma_mon}")


@mcp.tool()
def tinh_diem_trung_binh(
    diem: Annotated[list[float], Field(description="Các đầu điểm, thang 10")],
    trong_so: Annotated[list[float], Field(description="Trọng số từng đầu điểm, tính theo %")],
) -> float:
    """Tính điểm trung bình môn có trọng số. Tổng trọng số phải bằng 100."""
    if len(diem) != len(trong_so):
        raise ToolError("Số đầu điểm và số trọng số phải bằng nhau")
    if abs(sum(trong_so) - 100) > 0.01:
        raise ToolError(f"Tổng trọng số đang là {sum(trong_so):g}, phải bằng 100")
    return round(sum(d * w for d, w in zip(diem, trong_so)) / 100, 2)


@mcp.resource("lichthi://tat-ca")
def tat_ca_lich_thi() -> str:
    """Toàn bộ lịch thi dạng JSON."""
    return DATA.read_text(encoding="utf-8")


if __name__ == "__main__":
    mcp.run()  # mặc định chạy qua stdio
Mã nguồn server.py dùng MCPServer với hai tool tra_lich_thi, tinh_diem_trung_binh và một resource lichthi://tat-ca
Bước 4: server.py hoàn chỉnh, khoảng 40 dòng.

Dòng mcp.run() chạy server theo kiểu stdio: server không mở cổng mạng nào, mà nói chuyện với ứng dụng AI qua đầu vào và đầu ra chuẩn của tiến trình. Vì vậy chạy python server.py trực tiếp các bạn sẽ chỉ thấy nó đứng chờ. Đó là bình thường, ứng dụng host mới là bên khởi động và gửi lệnh cho nó.

Bước 5: Viết client để xem AI "nhìn thấy" gì

Để thấy server hoạt động mà chưa cần tới ứng dụng AI nào, mình viết một client nhỏ bằng chính SDK. Client này khởi động server.py như một tiến trình con rồi hỏi danh sách tool (tools/list):

import asyncio, sys
from mcp import Client, StdioServerParameters

async def main():
    server = StdioServerParameters(command=sys.executable, args=["server.py"])
    async with Client(server) as client:
        tools = await client.list_tools()
        for t in tools.tools:
            print(f"- {t.name}: {t.description}")
        print(await client.call_tool("tinh_diem_trung_binh",
                                     {"diem": [8, 7.5, 9], "trong_so": [20, 30, 50]}))

asyncio.run(main())
Kết quả tools/list: hai tool kèm mô tả tiếng Việt và JSON Schema tham số diem, trong_so do SDK tự sinh
Bước 5: danh sách tool và JSON Schema mà SDK tự sinh từ code Python.

Các bạn để ý phần schema: list[float] thành "type": "array", chữ trong Field(description=...) thành "description". Đây chính là thứ mô hình AI đọc để quyết định khi nào gọi tool và truyền tham số gì. Mô tả càng rõ, AI càng gọi đúng.

Bước 6: Gọi tool và đọc kết quả

Với điểm 8; 7,5; 9 và trọng số 20%, 30%, 50%, tool trả về 8.35, vừa ở dạng chữ (content) vừa ở dạng có cấu trúc (structuredContent). Khi mình cố tình truyền trọng số tổng 90, kết quả có "isError": true kèm câu báo lỗi tiếng Việt từ ToolError, nhờ vậy AI biết mình sai ở đâu để gọi lại.

Kết quả tools/call: tinh_diem_trung_binh trả về 8.35, lần gọi với trọng số tổng 90 trả về isError true và thông báo lỗi tiếng Việt
Bước 6: một lần gọi thành công (8.35) và một lần gọi bị từ chối có lý do rõ ràng.

Bước 7: Kiểm tra bằng MCP Inspector

MCP Inspector là công cụ chính thức để thử MCP server mà không cần viết client. Chế độ --cli chạy ngay trong terminal, chỉ cần Node.js. Trong ảnh, Python của mình nằm ở .venv-uv/bin/python. Các bạn dùng đường dẫn tới Python trong môi trường ảo tạo ở bước 3, ví dụ .venv/bin/python (Windows: .venv\Scripts\python.exe), để server tìm thấy gói mcp.

npx @modelcontextprotocol/inspector --cli .venv/bin/python server.py --method tools/list
npx @modelcontextprotocol/inspector --cli .venv/bin/python server.py --method tools/call --tool-name tra_lich_thi --tool-arg ma_mon=WEB1013
npx @modelcontextprotocol/inspector --cli .venv/bin/python server.py --method resources/read --uri lichthi://tat-ca
MCP Inspector CLI liệt kê tool tra_lich_thi với inputSchema có tham số ma_mon
Bước 7: Inspector liệt kê tool, giống hệt những gì client ở bước 5 nhận được.
MCP Inspector CLI gọi tra_lich_thi với ma_mon WEB1013 và đọc resource lichthi://tat-ca
Bước 7: gọi tool tra_lich_thi và đọc resource lichthi://tat-ca bằng Inspector.

README của SDK còn có lệnh mcp dev server.py để mở Inspector dạng giao diện web. Bài này mình chỉ dùng chế độ CLI.

Bước 8: Gắn server vào VS Code

Khi server đã chạy đúng với Inspector, các bạn có thể thêm nó vào ứng dụng AI. Với VS Code, tài liệu chính thức hướng dẫn: mở Command Palette, chạy Chat: Open Customizations, chọn MCP Servers, thêm server vào nhóm User hoặc Workspace, chọn Command (stdio) rồi nhập lệnh chạy server, ví dụ đường dẫn đầy đủ tới Python trong venv kèm server.py. Nếu thích sửa file, trang tham chiếu cấu hình của VS Code mô tả định dạng .vscode/mcp.json với khóa servers, type, command, args và biến ${workspaceFolder}. Mình lấy ví dụ trong tài liệu và thay lệnh bằng lệnh chạy server của bài này:

{
  "servers": {
    "tro-ly-hoc-tap": {
      "type": "stdio",
      "command": "${workspaceFolder}/.venv/bin/python",
      "args": ["${workspaceFolder}/server.py"]
    }
  }
}
Tài liệu VS Code Add MCP Servers, bảng chọn kiểu server với lựa chọn Command (stdio) được đóng khung đỏ
Bước 8: với server tự viết chạy bằng lệnh, chọn Command (stdio).

Lưu ý: phần này mình làm theo tài liệu VS Code, chưa chụp trên máy có đăng nhập Copilot. Để kiểm tra, tài liệu khuyên gửi một câu lệnh nêu rõ tên server, ví dụ "dùng tro-ly-hoc-tap tra lịch thi môn MOB201", rồi mở chi tiết lời gọi tool xem có đúng là tool của mình được gọi không. Tài liệu nhấn mạnh: chỉ có câu trả lời nghe hợp lý thôi thì chưa đủ.

Lỗi thường gặp

1. ModuleNotFoundError: No module named 'mcp.server.fastmcp'

Các bạn chép code từ bài hướng dẫn cũ (from mcp.server.fastmcp import FastMCP) nhưng máy cài SDK 2.x. Thông báo lỗi nói rõ cách sửa: đổi sang from mcp.server import MCPServer và MCPServer("ten-server"), hoặc cài bản cũ bằng pip install "mcp<2" nếu muốn giữ nguyên code v1.

Lỗi ModuleNotFoundError mcp.server.fastmcp khi chạy code FastMCP cũ trên SDK mcp 2.x
Lỗi 1: code FastMCP cũ không chạy trên SDK 2.x.

2. Tool báo lỗi nhưng chỉ có "Error executing tool ..."

Ở lần thử đầu, mình dùng raise ValueError(...). Kết quả: client chỉ nhận "Error executing tool tinh_diem_trung_binh", câu giải thích bị giữ lại phía server và server ghi log UnexpectedToolError. SDK 2.x coi mọi exception thường là "lỗi bất ngờ" nên giấu nội dung. Muốn AI đọc được lý do, các bạn dùng ToolError như ở bước 4.

Kết quả isError true chỉ có Error executing tool tinh_diem_trung_binh khi tool raise ValueError, log server ghi UnexpectedToolError
Lỗi 2: dùng ValueError thì client không thấy lý do lỗi.

3. Dùng print() trong server chạy stdio

Mình thêm một dòng print("Dang tra mon:", ma_mon) để debug. Vì stdio dùng đầu ra chuẩn để truyền tin nhắn JSON-RPC, dòng chữ này chen vào giữa và client của SDK báo Failed to parse JSONRPC message from server. Trong lần thử của mình, client vẫn nhận được kết quả, còn Inspector thì bỏ qua dòng lạ, nhưng tài liệu chính thức ghi rõ: với server stdio, không bao giờ ghi ra stdout. Cách sửa: print(..., file=sys.stderr) hoặc dùng module logging (mặc định ghi ra stderr). Mình đã thử lại với file=sys.stderr và lỗi biến mất.

Client MCP báo Failed to parse JSONRPC message from server với input_value Dang tra mon MOB201 do print ra stdout
Lỗi 3: dòng print() lọt vào kênh JSON-RPC.
Tài liệu Build an MCP server ghi Never write to stdout cho server STDIO, đóng khung đỏ
Lỗi 3: cảnh báo "Never write to stdout" trong tài liệu chính thức.

4. Chạy python server.py thấy "treo"

Không phải lỗi. Server stdio đứng chờ host gửi lệnh. Muốn thử thì dùng client ở bước 5 hoặc Inspector ở bước 7, bấm Ctrl+C để thoát.

Câu hỏi thường gặp

MCP server khác API thông thường thế nào?

API REST thì mỗi ứng dụng phải tự viết code gọi. MCP là một giao thức chung: server tự mô tả mình có tool gì, tham số gì (như schema ở bước 5), nên bất kỳ host nào hỗ trợ MCP cũng dùng được mà không cần viết thêm code tích hợp.

Có cần trả tiền hay API key để viết MCP server không?

Không. SDK, Inspector và server trong bài đều mã nguồn mở và chạy trên máy. Chỉ phần host (ví dụ chat AI trong VS Code) mới cần tài khoản theo điều kiện của từng dịch vụ.

Mình học Android, viết MCP server bằng Kotlin được không?

Được. Trang "Build an MCP server" chính thức có hướng dẫn cho Python, TypeScript, Java, Kotlin, C#, Ruby, Rust và Go. Ý tưởng giống hệt bài này, chỉ khác cú pháp.

Có nên cài MCP server người khác viết sẵn không?

Có thể, nhưng hãy xem kỹ mã nguồn và quyền của nó. Server stdio chạy trên máy với quyền của tài khoản các bạn, nên có thể đọc file, gọi mạng. Tài liệu VS Code cũng nhắc chỉ tin những cấu hình mình đã xem qua.

Nguồn tham khảo

Ảnh chụp và thử nghiệm ngày 10/11/2026.