Dùng AI

Kết nối AI với công cụ qua MCP server: ví dụ đọc tệp, gọi API

Một MCP server là chương trình nhỏ đưa công cụ của bạn cho AI dùng. Bài này viết một server bằng Python có bốn công cụ (liệt kê, đọc, tìm trong ghi chú và tra thông tin một kho GitHub), rồi thêm nó vào Claude Code bằng một lệnh.

Ý chính
  • Với SDK Python chính thức, mỗi công cụ là một hàm có @mcp.tool(): tên hàm thành tên công cụ, docstring và kiểu tham số thành mô tả và lược đồ đầu vào.
  • Server chạy qua stdio không được print() ra đầu ra chuẩn. Ghi nhật ký bằng logging (ra stderr), báo lỗi đã lường trước bằng ToolError để mô hình đọc được.
  • Thêm vào Claude Code bằng claude mcp add <tên> -- <lệnh chạy server>, kiểm tra bằng /mcp. Chỉ cho server quyền tối thiểu.

Bài này theo hướng dẫn dựng server của Model Context Protocol và tài liệu MCP của Claude Code, đọc ngày 11/11/2026. Đoạn code dưới đây đã chạy thử với SDK mcp phiên bản 2.3.0 trên Python 3.13, cả kiểu nối trong cùng tiến trình lẫn kiểu chạy qua stdio. SDK đổi nhanh; nếu tên lớp hay hàm khác với bản bạn cài, hãy xem lại trang hướng dẫn. Nếu chưa rõ MCP là gì, đọc trước MCP là gì.

Server ví dụ làm gì?

Bạn có một thư mục ghi chú học tập dạng Markdown, và muốn AI tra cứu trong đó khi làm bài: "trong ghi chú của tôi có nhắc gì về commit không?". Bạn cũng muốn AI tra nhanh thông tin một kho GitHub khi bạn hỏi về thư viện. Server ví dụ có bốn công cụ:

Công cụ Làm gì Loại
liet_ke_ghi_chu Liệt kê tên các tệp .md trong thư mục ghi chú Đọc tệp
doc_ghi_chu Đọc toàn bộ một tệp ghi chú Đọc tệp
tim_trong_ghi_chu Tìm các dòng chứa một từ khóa Đọc tệp
thong_tin_repo Tra mô tả, số sao, ngôn ngữ chính của một kho GitHub công khai Gọi API

Thư mục ghi chú đặt qua biến môi trường THU_MUC_GHI_CHU, và server chỉ cho đọc tệp .md nằm ngay trong thư mục đó. API GitHub dùng ở đây là API công khai, không cần khóa cho các kho công khai.

Nối AI với ghi chú của bạn qua MCP Ví dụ chạy thật: tìm từ "commit" trong thư mục ghi chú 1. Viết serverPython, SDK mcp4 công cụ@mcp.tool() 2. Đăng kýclaude mcp addchạy qua stdio 3. AI gọitim_trong_ghi_chutrả về git.md:2 Server stdio không được print() ra stdout
Nối AI với ghi chú của bạn qua MCP 1. Viết serverPython, SDK mcp4 công cụ@mcp.tool() 2. Đăng kýclaude mcp addchạy qua stdio 3. AI gọitim_trong_ghi_chutrả về git.md:2 Server stdio không được print() ra stdout
Hình 1. Ba bước nối AI với ghi chú của bạn: viết server Python với bốn công cụ, đăng ký server với Claude Code bằng claude mcp add, rồi AI gọi công cụ tim_trong_ghi_chu và nhận kết quả git.md:2.

Bước 1: chuẩn bị môi trường

Hướng dẫn chính thức yêu cầu Python 3.10 trở lên và dùng uv để quản lý dự án. Các lệnh trên macOS hoặc Linux:

uv init ghi-chu-mcp
cd ghi-chu-mcp
uv venv
source .venv/bin/activate
uv add "mcp[cli]"

Trên Windows PowerShell, thay dòng kích hoạt môi trường bằng .venv\Scripts\activate. Nếu không dùng uv, bạn có thể tạo môi trường ảo bằng python -m venv .venv rồi cài bằng pip install "mcp[cli]". Bài chạy dự án AI từ GitHub giải thích kỹ hơn về môi trường ảo.

Bước 2: viết server

Toàn bộ server nằm trong một tệp:

"""MCP server nhỏ: cho AI đọc ghi chú trong một thư mục được phép và tra thông tin một kho GitHub qua API công khai.

Cài: uv add "mcp[cli]"   (hoặc: pip install "mcp[cli]"), cần Python 3.10 trở lên.
Chạy thử: uv run server_ghi_chu.py   (server nói chuyện qua stdin/stdout, nên không in gì ra màn hình)
Thêm vào Claude Code: claude mcp add ghi-chu -- uv run --directory <thư mục chứa tệp này> server_ghi_chu.py
"""
import json
import logging
import os
import pathlib
import urllib.error
import urllib.request

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

# Server chạy qua stdio thì KHÔNG được print() ra stdout: sẽ làm hỏng thông điệp JSON-RPC.
# Dùng logging, mặc định ghi ra stderr.
logger = logging.getLogger(__name__)

THU_MUC = pathlib.Path(os.environ.get("THU_MUC_GHI_CHU", "ghi_chu")).resolve()
mcp = MCPServer("ghi-chu")


def _duong_dan_an_toan(ten: str) -> pathlib.Path:
    """Chỉ cho đọc tệp .md nằm ngay trong THU_MUC, chặn kiểu ../../etc/passwd."""
    p = (THU_MUC / ten).resolve()
    if p.parent != THU_MUC or p.suffix != ".md":
        # ToolError: lỗi đã lường trước, thông điệp được gửi về cho mô hình đọc để tự sửa.
        raise ToolError(f"chỉ được đọc tệp .md nằm ngay trong thư mục {THU_MUC.name}/")
    if not p.is_file():
        raise ToolError(f"không có tệp {ten}; gọi liet_ke_ghi_chu để xem tên đúng")
    return p


@mcp.tool()
def liet_ke_ghi_chu() -> list[str]:
    """Liệt kê tên các tệp ghi chú .md trong thư mục ghi chú."""
    return sorted(p.name for p in THU_MUC.glob("*.md"))


@mcp.tool()
def doc_ghi_chu(ten: str) -> str:
    """Đọc toàn bộ một tệp ghi chú.

    Args:
        ten: tên tệp trong thư mục ghi chú, ví dụ "mcp.md"
    """
    logger.info("đọc %s", ten)
    return _duong_dan_an_toan(ten).read_text(encoding="utf-8")


@mcp.tool()
def tim_trong_ghi_chu(tu_khoa: str) -> list[str]:
    """Tìm các dòng có chứa từ khóa (không phân biệt hoa thường) trong mọi tệp ghi chú.

    Args:
        tu_khoa: từ hoặc cụm từ cần tìm
    """
    ket_qua = []
    for p in sorted(THU_MUC.glob("*.md")):
        for so, dong in enumerate(p.read_text(encoding="utf-8").splitlines(), 1):
            if tu_khoa.lower() in dong.lower():
                ket_qua.append(f"{p.name}:{so}: {dong.strip()}")
    return ket_qua


def _lay_json(url: str) -> dict:
    yc = urllib.request.Request(url, headers={"User-Agent": "ghi-chu-mcp", "Accept": "application/vnd.github+json"})
    with urllib.request.urlopen(yc, timeout=15) as r:
        return json.loads(r.read().decode("utf-8"))


@mcp.tool()
def thong_tin_repo(chu: str, ten: str) -> dict:
    """Tra mô tả, số sao và ngôn ngữ chính của một kho GitHub công khai.

    Args:
        chu: tên tài khoản hoặc tổ chức sở hữu kho, ví dụ "modelcontextprotocol"
        ten: tên kho, ví dụ "python-sdk"
    """
    try:
        d = _lay_json(f"https://api.github.com/repos/{chu}/{ten}")
    except urllib.error.HTTPError as e:
        raise ToolError(f"GitHub trả lỗi {e.code} cho {chu}/{ten}: kiểm tra lại tên kho") from e
    return {"ten": d["full_name"], "mo_ta": d.get("description") or "", "sao": d["stargazers_count"],
            "ngon_ngu": d.get("language") or ""}


if __name__ == "__main__":
    mcp.run(transport="stdio")

Những điểm đáng chú ý, theo thứ tự trong tệp:

MCPServer("ghi-chu") tạo server có tên ghi-chu. Hướng dẫn chính thức giải thích lớp này dùng gợi ý kiểu (type hints) và docstring của Python để tự sinh định nghĩa công cụ. Vì vậy docstring không chỉ để người đọc: nó là thứ mô hình đọc để biết công cụ làm gì. Phần Args: mô tả từng tham số, và kiểu str, list[str], dict thành lược đồ đầu vào và đầu ra.

@mcp.tool() biến một hàm thành công cụ. Hàm vẫn là hàm Python bình thường, nên bạn gọi thử trực tiếp được khi viết test.

Không dùng print(). Đây là lỗi hay gặp nhất. Hướng dẫn chính thức nhấn mạnh: với server stdio, không bao giờ ghi ra đầu ra chuẩn, vì nó làm hỏng thông điệp JSON-RPC và làm server hỏng. Hãy dùng mô-đun logging, mặc định ghi ra stderr.

_duong_dan_an_toan chặn đọc ngoài thư mục ghi chú. Một yêu cầu như ../bi_mat.md hay ../../.ssh/id_rsa bị từ chối, vì sau khi chuẩn hóa đường dẫn, thư mục cha phải đúng là thư mục ghi chú và đuôi phải là .md. Đây là cách cấp quyền tối thiểu ngay trong code: AI chỉ thấy đúng những gì bạn muốn cho nó thấy.

ToolError dành cho lỗi bạn đã lường trước. Theo chú thích trong SDK, khi công cụ ném ToolError, lời gọi trả về kết quả đánh dấu lỗi kèm đúng thông điệp của bạn để mô hình đọc. Còn nếu công cụ gặp một ngoại lệ bất kỳ khác, mô hình chỉ thấy câu chung "Error executing tool" mà không biết vì sao. Vì vậy thông điệp lỗi nên nói cách sửa, ví dụ "không có tệp khong-co.md; gọi liet_ke_ghi_chu để xem tên đúng". Mô hình đọc câu đó và tự gọi công cụ liệt kê.

thong_tin_repo gọi API REST của GitHub, có thời gian chờ tối đa 15 giây, và đổi lỗi HTTP (như 404 khi gõ sai tên kho) thành ToolError có thông điệp rõ ràng.

mcp.run(transport="stdio") chạy server qua đầu vào và đầu ra chuẩn. Bạn không cần tự chạy lệnh này trong lúc dùng: Claude Code sẽ khởi động server như một tiến trình con.

Kết quả khi gọi thử từng công cụ

Với thư mục ghi chú có hai tệp git.md và mcp.md, khi nối với server bằng client của SDK và gọi thử, kết quả như sau:

Lời gọi Kết quả
tools/list 4 công cụ: liet_ke_ghi_chu, doc_ghi_chu, tim_trong_ghi_chu, thong_tin_repo, mỗi công cụ có mô tả lấy từ docstring
liet_ke_ghi_chu() git.md, mcp.md
tim_trong_ghi_chu(tu_khoa="commit") git.md:2: Commit nhỏ, thông điệp rõ.
doc_ghi_chu(ten="../thu.py") Lỗi: chỉ được đọc tệp .md nằm ngay trong thư mục ghi_chu/
doc_ghi_chu(ten="khong-co.md") Lỗi: không có tệp khong-co.md; gọi liet_ke_ghi_chu để xem tên đúng

Hai dòng cuối là điều nên học: lời gọi sai không làm sập server, và thông điệp lỗi đủ rõ để mô hình tự sửa ở bước sau.

Bước 3: thêm server vào Claude Code

Tài liệu Claude Code dùng cú pháp claude mcp add [tùy chọn] <tên> -- <lệnh> [tham số...] cho server cục bộ chạy qua stdio. Mọi thứ sau dấu -- được chuyển nguyên cho server; thiếu dấu này, Claude Code sẽ hiểu nhầm tham số của server là tham số của nó. Với server ví dụ (đổi đường dẫn cho đúng máy bạn):

claude mcp add --transport stdio --env THU_MUC_GHI_CHU=/duong/dan/toi/ghi_chu ghi-chu -- uv run --directory /duong/dan/toi/ghi-chu-mcp server_ghi_chu.py

Nên dùng đường dẫn tuyệt đối cho cả thư mục ghi chú lẫn thư mục code, vì server được khởi động từ một thư mục làm việc mà bạn không kiểm soát. Tùy chọn --env truyền biến môi trường cho server; đây cũng là cách đưa khóa API vào server mà không viết thẳng khóa vào code.

Chọn phạm vi

Phạm vi Tùy chọn Lưu ở đâu Chia sẻ với nhóm
Cục bộ (mặc định) --scope local ~/.claude.json, theo đường dẫn dự án hiện tại Không
Dự án --scope project .mcp.json ở gốc dự án Có, qua git
Người dùng --scope user ~/.claude.json Không; dùng được ở mọi dự án của bạn

Theo tài liệu, với server ở phạm vi dự án, Claude Code hỏi bạn chấp thuận trong phiên tương tác trước khi dùng. Đây là lớp bảo vệ hợp lý: ai đó thêm server vào .mcp.json của kho thì bạn vẫn phải đồng ý mới chạy. Nếu commit .mcp.json, đừng ghi khóa bí mật thẳng vào đó; xem giữ khóa API an toàn.

Kiểm tra và quản lý

claude mcp list
claude mcp get ghi-chu
claude mcp remove ghi-chu

Trong một phiên Claude Code, lệnh /mcp cho biết trạng thái từng server và dùng để đăng nhập với các server cần xác thực. Nếu server báo lỗi kết nối, ba nguyên nhân hay gặp nhất là: có print() ra đầu ra chuẩn, đường dẫn tương đối sai, hoặc thiếu dấu -- trong lệnh thêm.

Server từ xa qua HTTP

Với dịch vụ trực tuyến có MCP server, tài liệu khuyên dùng kiểu HTTP, ví dụ:

claude mcp add --transport http notion https://mcp.notion.com/mcp

Server cần đăng nhập OAuth thì xác thực qua /mcp, lệnh này mở trình duyệt để đăng nhập. Tài liệu lưu ý: gỡ một server từ xa chỉ xóa token đã lưu trên máy chứ không thu hồi token ở phía máy chủ xác thực; muốn thu hồi, chạy claude mcp logout <tên> trước khi gỡ.

Bước 4: để AI dùng công cụ

Sau khi thêm server, bạn không cần gọi tên công cụ. Cứ hỏi tự nhiên, ví dụ "trong ghi chú của tôi có nhắc gì về commit không?". Agent thấy công cụ tim_trong_ghi_chu có mô tả khớp, gọi nó với từ khóa phù hợp, rồi trả lời dựa trên kết quả. Với câu "thư viện python-sdk của modelcontextprotocol có bao nhiêu sao trên GitHub?", agent gọi thong_tin_repo.

Mặc định Claude Code hỏi bạn trước khi dùng công cụ mới. Hãy đọc tên công cụ và tham số trước khi đồng ý, nhất là với công cụ có tác dụng ghi hay gửi đi. Bài giới hạn quyền khi cho AI tự chạy lệnh nói kỹ về cách đặt quyền.

Thiết kế công cụ để AI dùng đúng

Anthropic, trong bài "Building effective agents", khuyên đầu tư vào giao diện giữa agent và công cụ kỹ như đầu tư vào giao diện cho người dùng. Áp dụng vào MCP server:

  1. Đặt mình vào vị trí mô hình. Nếu chỉ nhìn tên và mô tả mà không rõ cách dùng công cụ, mô hình cũng không rõ.
  2. Viết mô tả như docstring cho một lập trình viên mới: công cụ làm gì, tham số là gì, có ví dụ giá trị. Phân biệt rõ các công cụ giống nhau.
  3. Thiết kế để khó dùng sai. Ví dụ trong bài của Anthropic: bắt buộc dùng đường dẫn tuyệt đối đã loại bỏ lỗi do đường dẫn tương đối. Trong server ví dụ, giới hạn đọc trong một thư mục và một loại tệp là cùng ý đó.
  4. Thử với nhiều đầu vào, xem mô hình dùng sai ở đâu, rồi sửa mô tả hoặc tham số.
  5. Trả lỗi có ích: nói điều gì sai và cách sửa, thay vì một mã lỗi khó hiểu.
Cẩn thận
  • print() ra đầu ra chuẩn trong server stdio: kết nối hỏng mà không có thông báo rõ ràng.
  • Dùng đường dẫn tương đối cho thư mục dữ liệu: server chạy từ thư mục làm việc khác với bạn nghĩ.
  • Cho server đọc cả ổ đĩa "cho tiện": hãy giới hạn trong đúng thư mục cần.
  • Ném ngoại lệ thường cho lỗi đã lường trước: mô hình chỉ thấy "Error executing tool" và không biết sửa.
  • Viết khóa API vào code hoặc vào .mcp.json rồi commit lên git.
  • Nối server lạ mà không đọc mã nguồn: nội dung server trả về có thể chứa lời nhắc độc hại.

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

Có cần dùng uv không, dùng pip được không?

Được. Hướng dẫn chính thức dùng uv, nhưng bạn có thể tạo môi trường ảo bằng venv và cài mcp[cli] bằng pip. Khi thêm vào Claude Code, lệnh chạy server là đường dẫn tới Python trong môi trường ảo cộng tên tệp server.

Server MCP có chạy được với ứng dụng khác ngoài Claude Code không?

Có, nếu ứng dụng đó hỗ trợ MCP. Server nói chuyện bằng giao thức chung; cách đăng ký server thì mỗi ứng dụng có tệp cấu hình hoặc lệnh riêng.

Làm sao gỡ lỗi khi server không chạy?

Thử chạy server trực tiếp để xem lỗi Python, xem nhật ký trong stderr, kiểm tra lại đường dẫn tuyệt đối và dấu -- trong lệnh thêm, và dùng /mcp trong Claude Code để xem trạng thái.

Tools hay resources, nên dùng cái nào cho dữ liệu đọc?

Tài liệu MCP định nghĩa resources là dữ liệu để AI đọc làm ngữ cảnh, còn tools là hàm để AI làm việc. Ví dụ trong bài dùng tools cho đơn giản và vì tìm kiếm cần tham số; với dữ liệu tĩnh như cấu trúc bảng, resources là lựa chọn tự nhiên hơn.

Server gọi API có tốn tiền không?

Tùy API. Ví dụ trong bài dùng API công khai của GitHub cho kho công khai. Với API có phí hay có giới hạn số lần gọi, hãy đặt giới hạn trong server và theo dõi mức dùng.

Học tiếp

  • Muốn hiểu lại host, client, server và các thông điệp bên dưới, đọc MCP là gì.
  • Muốn đặt quyền cho agent khi dùng công cụ, đọc giới hạn quyền khi cho AI tự chạy lệnh.
  • Muốn giữ khóa API của server an toàn, đọc giữ khóa API an toàn.

Nếu bạn muốn học nền tảng AI và lập trình Python một cách có hệ thống, tham khảo khóa học AI của Big-O Coding.

Muốn học bài bản cùng giảng viên?

Khóa AI – Introduction to Machine Learning: nền tảng Máy học từ thuật toán cơ bản đến Neural Networks và LLM, học bằng Python. Học thử buổi 1 miễn phí.

Xem Khóa AI

Nguồn tham khảo

Chia sẻ: Facebook LinkedIn
Nhắn Zalo tư vấnZalo Gọi 0937 401 483