·

Điều phối Nhiều MCP Server

Tìm hiểu các mẫu thiết kế cốt lõi để kết nối nhiều MCP server cùng lúc, giúp AI agent chuyển giữa các công cụ mà không mất ngữ cảnh.

Hầu hết tutorial về MCP (Model Context Protocol — giao thức mở kết nối AI agent với các nguồn dữ liệu và công cụ bên ngoài) chỉ dạy bạn cắm một server, gọi vài tool, xong. Thực tế production khác hẳn: một agent hữu ích thường phải nói chuyện đồng thời với GitHub, Jira, Sentry, Figma, Playwright... trong cùng một phiên chat. Khi đó xuất hiện cả loạt vấn đề mà tutorial single-server không chuẩn bị cho bạn — tool bị đá nhầm server, context window (cửa sổ ngữ cảnh) bị ăn hết bởi schema tool, log lỗi không biết server nào gây ra. Bài này đi thẳng vào cách tư duy và cấu hình để chạy nhiều MCP server cùng lúc mà agent vẫn chọn đúng tool, đúng lúc, đúng chi phí token.

AI Agent Chọn Và Kết Hợp Tool Từ Nhiều MCP Server Như Thế Nào

Điều quan trọng nhất cần hiểu: khi bạn kết nối 5 MCP server, agent (LLM) không nhìn thấy "5 server" — nó nhìn thấy một danh sách phẳng (flat list) toàn bộ tool schema gộp lại, không phân ranh giới server nào rõ ràng ngoài cái tên tool. Model chọn tool để gọi hoàn toàn dựa trên tên, description, và ngữ cảnh cuộc trò chuyện — không có khái niệm "server ownership" gắn sẵn trong quá trình reasoning (suy luận) của nó.

Đây là nguồn gốc của phần lớn lỗi orchestration multi-MCP. Ví dụ kinh điển: bạn nối cả GitHub MCP server và Jira MCP server, cả hai đều có tool tên gần giống create_issue hoặc get_issue. Bạn gõ:

Đóng issue #123 lại giúp tôi.

Câu này ambiguous (mơ hồ) — agent phải đoán "#123" là GitHub issue hay Jira ticket dựa vào ngữ cảnh gần nhất trong hội thoại, không phải dựa vào ý định thật của bạn. Nếu trước đó bạn vừa nói về pull request, nó có thể chọn nhầm sang GitHub dù bạn đang nghĩ tới Jira PROJ-123.

Cách host application (Claude Code, Claude Desktop, Cursor...) xử lý việc này là gắn namespace (không gian tên) cho tool khi expose ra cho model, dạng mcp__<server-name>__<tool-name>, ví dụ mcp__github__create_issuemcp__jira__create_issue. Namespace này giúp bạn đọc log dễ hơn, nhưng nó không tự động giúp model chọn đúng — model vẫn phải dựa vào description và prompt của bạn để suy luận.

Quy tắc thực chiến: khi có nguy cơ trùng tên hoặc trùng domain (issue, ticket, comment, attachment đều tồn tại ở cả GitHub và Jira), luôn nêu rõ server hoặc định danh không mơ hồ trong prompt:

Kém rõ ràng:
"Đóng issue #123."

Rõ ràng, nên dùng:
"Dùng GitHub MCP server, đóng issue #123 trong repo acme/checkout-service."
"Chuyển trạng thái Jira ticket PROJ-123 sang Done."

Với workflow lặp lại nhiều lần (ví dụ pipeline tự động chạy hằng ngày), đừng phụ thuộc vào việc model "đoán đúng" — hãy viết system prompt hoặc slash command cố định nêu rõ ánh xạ: "issue number không kèm prefix chữ luôn là GitHub; ticket dạng PROJ-xxx luôn là Jira."

Mẹo: Khi thấy agent gọi nhầm tool giữa hai server có domain giống nhau, đừng cố sửa bằng cách đổi tên tool trong code server (thường bạn không sở hữu server đó). Sửa ở tầng prompt — thêm một câu duy nhất nêu rõ định danh (ID có prefix, tên server) là cách rẻ và hiệu quả nhất.

Các Mẫu Cấu Hình Để Chạy Nhiều MCP Server Đồng Thời

Cấu hình multi-MCP thường nằm trong một file JSON duy nhất — với Claude Code là .mcp.json ở project root hoặc ~/.claude.json ở scope user, với Claude Desktop là claude_desktop_config.json. Mỗi server là một entry độc lập, có thể là local process (transport stdio) hoặc remote (transport sse/http). Một setup thực tế kết hợp GitHub, Jira (qua Atlassian MCP), Sentry, Figma và Playwright trông như sau:

// .mcp.json — project-scoped, nên commit vào repo (không chứa secret)
{
  "mcpServers": {
    "github": {
      "type": "stdio",
      "command": "docker",
      "args": ["run", "-i", "--rm",
        "-e", "GITHUB_PERSONAL_ACCESS_TOKEN",
        "-e", "GITHUB_TOOLSETS=repos,issues,pull_requests",
        "ghcr.io/github/github-mcp-server"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_PAT}"
      }
    },
    "jira": {
      "type": "sse",
      "url": "https://mcp.atlassian.com/v1/sse",
      "headers": {
        "Authorization": "Bearer ${ATLASSIAN_MCP_TOKEN}"
      }
    },
    "sentry": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@sentry/mcp-server"],
      "env": {
        "SENTRY_AUTH_TOKEN": "${SENTRY_AUTH_TOKEN}",
        "SENTRY_ORG": "acme-corp",
        "SENTRY_PROJECT": "checkout-service"
      }
    },
    "figma": {
      "type": "sse",
      "url": "http://127.0.0.1:3845/sse"
    },
    "playwright": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest"]
    }
  }
}

Vài điểm cần lưu ý khi thiết kế config kiểu này:

  • Đừng để secret literal trong file commit. Dùng biến môi trường (${GITHUB_PAT}) và nạp qua .env hoặc secret manager của CI. File .mcp.json commit vào repo chỉ nên chứa cấu trúc, không chứa token thật.
  • Trộn transport tự do. stdio (server chạy như subprocess local, ví dụ container Docker hoặc npx package) và sse/http (server remote, ví dụ Atlassian hoặc Figma chạy dev-mode) sống chung bình thường trong cùng file — agent không quan tâm transport, chỉ quan tâm tool schema nó nhận được.
  • Scope tool ngay từ lúc khởi động server, đừng để mặc định "expose tất cả". GITHUB_TOOLSETS=repos,issues,pull_requests là ví dụ giới hạn GitHub MCP server chỉ bật 3 nhóm tool cần cho workflow, thay vì toàn bộ ~10 nhóm (Actions, Discussions, Projects, Security...).
  • Tách config theo scope: config ở ~/.claude.json (user, áp dụng mọi project) hợp cho server cá nhân dùng chung (ví dụ Figma cá nhân), còn config ở .mcp.json trong repo hợp cho server gắn với chính project đó (GitHub repo, Jira project cụ thể) để cả team dùng chung setup nhất quán.

Mẹo: Sau khi sửa file config, đừng giả định server đã kết nối — hãy chạy lệnh kiểm tra trạng thái (claude mcp list với Claude Code, hoặc gõ /mcp trong session) để xác nhận cả 5 server đều "connected" và có tool count > 0 trước khi bắt đầu workflow thật.

Quản Lý Context Window: Tránh Tràn Token Khi Dùng Nhiều MCP Server

Mỗi tool được một MCP server expose ra sẽ chiếm một phần context window ngay từ đầu phiên — không phải khi được gọi, mà ngay khi model nhìn thấy schema của nó (tên, description, JSON schema tham số) để có thể quyết định gọi hay không. Một tool trung bình chiếm khoảng 150-300 token schema. Nếu bạn nối 5 server với toolset mặc định "everything", tổng có thể lên tới 60-80 tool, tức 10-20K token bị "đóng băng" cho riêng phần khai báo tool — trước khi cuộc hội thoại thật sự bắt đầu.

Với model có context window 200K token, con số đó nghe có vẻ nhỏ, nhưng vấn đề là nó cộng dồn với mọi lượt trong session dài (agentic loop nhiều bước), và nó cạnh tranh trực tiếp với chỗ chứa cho: kết quả tool trả về (đặc biệt Playwright snapshot hay Figma frame JSON có thể vài nghìn token mỗi lần gọi), lịch sử hội thoại, và system prompt.

Ba kỹ thuật cắt giảm hiệu quả nhất, xếp theo mức độ nên ưu tiên:

1. Narrow toolset ngay tại tầng server, không đợi model tự "bỏ qua" tool không cần:

GITHUB_TOOLSETS=all

GITHUB_TOOLSETS=repos,issues,pull_requests

Sentry MCP server nên scope theo project qua biến môi trường SENTRY_PROJECT — không giảm số tool, nhưng giảm mạnh kích thước payload trả về (chỉ query trong 1 project thay vì toàn org).

2. Kiểm soát tần suất gọi tool "nặng payload". Playwright MCP có tool browser_snapshot trả về toàn bộ accessibility tree của trang — có thể vài nghìn token mỗi lần. Trong một flow 5 bước, gọi snapshot ở mọi bước là lãng phí:

Lãng phí — snapshot toàn trang ở mỗi bước của flow 5 bước:
click → snapshot → type → snapshot → click → snapshot → ...

Tốt hơn — chỉ snapshot ở điểm cần ra quyết định:
click → type → click (không snapshot giữa các thao tác chắc chắn) → snapshot (trước khi quyết định bước tiếp theo)

3. Dọn context giữa các phase của workflow dài. Khi một multi-MCP session đi qua nhiều phase rõ rệt (ví dụ: phase 1 — đọc Sentry error; phase 2 — sửa code; phase 3 — mở PR), kết quả tool call ở phase 1 thường không còn cần giữ nguyên văn ở phase 3. Dùng /compact (Claude Code) hoặc chủ động yêu cầu agent tóm tắt lại thành vài dòng trước khi chuyển phase, thay vì để toàn bộ raw JSON response tồn suốt session.

Mẹo: Trước khi thêm server thứ 5, thứ 6 vào config, tự hỏi "workflow này có thực sự cần cả 5 luôn mở, hay chỉ cần mở lúc dùng?". Với server ít dùng, cân nhắc bật/tắt theo profile (Claude Code hỗ trợ nhiều file .mcp.json theo project) thay vì gộp hết vào một config toàn cục.

Debug Và Ghi Log Cho Workflow Multi-MCP

Khi một workflow chạy qua 3-4 MCP server, lỗi trở nên khó chẩn đoán hơn nhiều so với single-server: bạn phải xác định server nào gây lỗi, tool nào trả sai, và liệu lỗi có phải do agent chọn nhầm tool hay do server đó thật sự down.

Bắt đầu bằng việc xác nhận trạng thái kết nối trước khi nghi ngờ logic:

claude mcp list

/mcp

Một lỗi âm thầm rất hay gặp: server "connected" nhưng expose 0 tool — thường do biến môi trường auth bị gõ sai tên (GITHUB_TOKEN thay vì GITHUB_PERSONAL_ACCESS_TOKEN) hoặc token hết quyền. Server không crash, không báo lỗi rõ ràng, chỉ đơn giản là không có tool nào xuất hiện trong danh sách — agent im lặng "không thấy" server đó luôn, và bạn dễ nhầm là do model "quên" dùng tool.

Khi lỗi xảy ra giữa lúc gọi tool (không phải lúc connect), log chi tiết nằm ở:

~/Library/Logs/Claude/mcp*.log

claude --mcp-debug

Đọc log này, tìm theo tool_call_id để nối được request → response cho đúng tool, đúng server — đừng đoán dựa vào thứ tự xuất hiện trên terminal vì các server có thể trả lời không đồng bộ.

Ba lỗi phổ biến nhất theo kinh nghiệm chạy production:

  1. Token hết hạn giữa session dài — Jira/GitHub OAuth token sống vài giờ, nếu workflow chạy overnight (ví dụ agent tự động xử lý ticket), token hết hạn giữa chừng và lỗi trả về thường là generic 401 không rõ do token nào.
  2. Rate limit bị nuốt thành lỗi chung — Sentry và GitHub API đều có rate limit; khi 3-4 server cùng gọi API dồn dập trong một multi-step flow, lỗi 429 dễ bị agent hiểu nhầm là "tool không tồn tại" nếu server không forward rõ status code.
  3. Tool trùng tên giữa 2 server đã cấu hình lại (namespace bị đổi) — nếu bạn từng đổi tên server key trong file config (ví dụ từ jira sang atlassian), các câu lệnh cũ có prefix mcp__jira__* trong lịch sử/prompt lưu sẵn sẽ không còn khớp.

Mẹo: Tập thói quen chạy claude mcp list (hoặc tương đương) đầu mỗi phiên làm việc dài với multi-MCP, giống như kiểm tra docker ps trước khi debug một hệ microservice — thói quen nhỏ này cắt được phần lớn thời gian debug lãng phí do "tưởng server đã connect nhưng thực ra chưa".

Vài Lưu Ý Thêm

Tổng hợp nhanh những điều nên làm mặc định khi bắt đầu bất kỳ workflow multi-MCP mới:

  • Luôn nêu rõ server/domain trong prompt khi có khả năng trùng tên tool (issue, ticket, comment...).
  • Narrow toolset ngay từ config server, không dựa vào việc "model sẽ tự bỏ qua tool dư".
  • Theo dõi ngân sách token cho phần schema tool — nếu bạn thấy context window "hết" nhanh bất thường, kiểm tra số tool đang active trước khi nghi ngờ nội dung hội thoại.
  • Chuẩn hoá cách kiểm tra trạng thái server (claude mcp list / /mcp) thành bước đầu tiên, không phải bước debug cuối cùng.
  • Với workflow chạy dài/không giám sát, chủ động refresh token trước khi hết hạn, đừng để agent tự phát hiện lỗi 401 giữa chừng.

Mẹo: Khi mới bắt đầu một multi-MCP setup, đừng nối cả 5 server cùng lúc rồi mới test. Nối từng server một, xác nhận nó hoạt động độc lập đúng như mong đợi, rồi mới thêm server kế tiếp — cách này giúp bạn cách ly lỗi nhanh gấp nhiều lần so với bật hết rồi mới debug.