Có một nhu cầu hay gặp khi dùng Claude Code lâu: bạn muốn nó biết thêm một cái gì đó bên ngoài filesystem cục bộ. Truy vấn database thật, đọc PR trên GitHub, gửi tin nhắn Slack, điều khiển browser. Bash tool đủ để gọi CLI, nhưng không đủ để expose một external system theo cách model có thể “hiểu” và tương tác có cấu trúc.

MCP (Model Context Protocol) là giải pháp cho vấn đề đó. Nó không phải tính năng của riêng CC, mà là một open protocol do Anthropic phát triển để standardize cách AI system kết nối với external tool và data source.

Bài này giải thích MCP là gì, cách cài, các server phổ biến nên biết, và khi nào tự build.

MCP là gì

MCP là protocol cho phép model AI gọi tool và đọc resource từ external process. Phía model thấy tool gọi được. Phía ngoài là một server process expose các tool và resource đó.

+-------------------------+       MCP protocol        +------------------+
|   Claude Code (client)  |  <-------------------->  |   MCP server     |
|   L3: tool list         |   list_tools / call_tool  |   (external      |
|   model gọi tool        |   read_resource           |    system)       |
+-------------------------+                           +------------------+

Khi bạn add một MCP server, tool list ở L3 của prompt mỗi turn sẽ dài thêm các tool mà server đó expose. Model nhìn thấy tool đó như mọi built-in tool khác, gọi nó theo cùng cơ chế tool use loop.

MCP server expose gì

Một MCP server có thể expose ba loại:

  • Tools: hành động model có thể gọi (chạy query, gửi message, tạo issue).
  • Resources: data model có thể đọc mà không cần gọi tool (file, doc, schema). Resources được nhét vào context trực tiếp.
  • Prompts: template prompt có thể trigger từ client.

Phần lớn server thực tế expose chủ yếu tools. Resources ít phổ biến hơn vì client phải chủ động fetch, không tự động vào context như memory.

Khác hooks ở chỗ nào

Người mới hay nhầm MCP và hooks vì cả hai đều “mở rộng” CC. Thực ra chúng can thiệp ở hai điểm khác nhau trong luồng xử lý.

User message
     |
     v
+------------+          Hook (PreToolUse, PostToolUse, Stop)
| Tool loop  |  <---    Shell script chạy ngoài model loop
|            |          Không phải tool model gọi
+------------+
     |
     v
  MCP tool   <---       Tool inject vào L3
  model gọi             Model tự quyết gọi khi nào
  như built-in
MCPHook
Vị tríL3: tool model tự gọiNgoài tool loop: trigger theo event
Cách viếtServer process (bất kỳ ngôn ngữ)Shell script
Khi nào chạyKhi model quyết định gọiKhi event xảy ra (pre/post tool, stop)
Thêm context vào promptTool call result vào L5Không trực tiếp
Dùng đểExpose external systemEnforce behavior, side-effect

Hook chạy không cần model quyết định. Ví dụ hook Stop chạy mỗi khi CC kết thúc turn, bất kể model đã làm gì. MCP tool chỉ chạy khi model chủ động gọi.

Bài 9 đi sâu hơn về hooks nếu bạn muốn đối chiếu.

Kiểu transport của MCP server

MCP spec định nghĩa ba kiểu transport:

stdio: server chạy như một process cục bộ, CC giao tiếp qua stdin/stdout. Phổ biến nhất. Ví dụ: npx @modelcontextprotocol/server-github.

HTTP (Streamable HTTP): server là HTTP endpoint. CC gửi request HTTP. Phù hợp cho remote server hoặc shared server nhiều người dùng chung.

SSE (Server-Sent Events): phiên bản trước của HTTP transport. Một số server cũ vẫn dùng. Đang được thay thế bởi Streamable HTTP trong spec mới.

Với use case cá nhân, stdio là đủ và đơn giản nhất. HTTP cần khi server phải chạy trên máy khác hoặc cần share giữa nhiều CC instance.

Cài MCP server

Qua CLI

claude mcp add <name> <command>

Ví dụ thêm GitHub MCP:

claude mcp add github npx -y @modelcontextprotocol/server-github

CC ghi vào ~/.claude/settings.json phần mcpServers. Server sẽ available cho mọi session trên máy này.

Qua settings.json

Sửa trực tiếp ~/.claude/settings.json:

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
      }
    }
  }
}

Per-project (settings.local.json)

Thay vì global, enable MCP chỉ cho project cụ thể bằng cách đặt config trong <project>/.claude/settings.local.json (gitignored). CC merge settings theo thứ tự: global ~/.claude/settings.json rồi project-level, project thắng nếu conflict.

Đây là best practice cho server nhạy cảm như database: không muốn MCP Postgres của project A vô tình available khi bạn đang làm project B.

MCP phổ biến nên biết

GitHub

Package @modelcontextprotocol/server-github. Tool đáng chú ý: đọc file trong repo, list issues, tạo PR, comment, search code.

Cần GITHUB_PERSONAL_ACCESS_TOKEN với scope phù hợp. Classic token với repo scope là đủ cho phần lớn use case đọc.

claude mcp add github npx -y @modelcontextprotocol/server-github

Khi có GitHub MCP, bạn có thể nói “đọc file src/api/auth.ts ở branch feature/login” và CC fetch thẳng từ API thay vì bạn phải clone hoặc paste.

Slack

Package @modelcontextprotocol/server-slack. Gửi message, đọc channel history, tìm user, list channels.

Cần Slack app token với OAuth scopes tương ứng. Phức tạp hơn GitHub để setup vì cần tạo Slack app riêng. Nhưng khi có rồi thì có thể dùng CC để draft + gửi update mà không cần mở Slack.

Postgres

Package @modelcontextprotocol/server-postgres. Tool: chạy SQL query, list tables, describe schema.

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres"],
      "env": {
        "DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb"
      }
    }
  }
}

Cẩn thận: tool này cho model chạy query tùy ý. Dùng read-only user hoặc ít nhất restrict quyền theo principle of least privilege. Không kết nối trực tiếp production DB nếu không có safeguard.

Linear

Package @linear/mcp-server. Tạo issue, update status, đọc cycle, assign người.

Phù hợp nếu team dùng Linear để track ticket. CC có thể tự tạo issue khi phát hiện bug trong code review, hoặc update status khi bạn nói “mark PROJ-123 là done”.

Browser (Playwright MCP)

Package @playwright/mcp. Điều khiển browser headless: navigate, click, fill form, screenshot, scrape content.

Đây là loại mạnh nhất và cũng dễ gây vấn đề nhất. Dùng để automate UI testing, scrape web page, hoặc đặt CC tự thao tác một workflow web phức tạp. Cần cài Playwright browsers riêng.

Filesystem ngoài workdir

Package @modelcontextprotocol/server-filesystem. Expose thêm directory ngoài workdir hiện tại.

CC vốn chỉ thấy filesystem trong cwd. Nếu bạn muốn nó đọc thêm ~/Documents/specs/ mà không cần chuyển cwd, MCP filesystem là cách làm.

Khi nào dùng server có sẵn

GitHub, Slack, Postgres, Linear đều có official hoặc well-maintained community server. Những case này nên dùng server có sẵn vì:

  • Đã handle authentication, retry, rate limit.
  • Tool schema đã chuẩn cho các use case phổ biến.
  • Cộng đồng maintain, fix bug nhanh hơn bạn tự build.

Tiêu chí để chọn server có sẵn: mở GitHub, xem số star, xem issue tracker có active không, xem last commit. Server 1 năm không commit với 30 open issue thì cân nhắc fork hoặc tự build.

Khi nào tự build MCP server

Tự build có lý do khi gặp một trong các trường hợp sau.

Workflow nội bộ không có server sẵn. Tool nội bộ của công ty, API tự xây, pipeline data riêng. Không ai viết server cho use case đó trừ bạn.

Secret handling đặc thù. Server có sẵn đôi khi giả định cách truyền credential qua env var. Nếu công ty yêu cầu đọc secret từ Vault, KMS, hoặc cần luân chuyển token theo cách riêng, bạn cần tự kiểm soát authentication layer.

Scope quá rộng. Một số server expose 40-50 tool nhưng bạn chỉ cần 3. Build server nhỏ chỉ expose đúng tool cần, tool list ngắn, model ít bị confuse hơn.

Performance yêu cầu cao. Server chuẩn dùng npx cold-start mỗi lần CC khởi động. Nếu server phải khởi nhanh hoặc hold connection persistent, bạn cần tự quản lý process.

Viết MCP server đơn giản

MCP SDK có cho Python và TypeScript. Ví dụ server TypeScript expose một tool:

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = new Server({ name: "my-tool", version: "1.0.0" }, {
  capabilities: { tools: {} }
});

server.setRequestHandler("tools/list", async () => ({
  tools: [{
    name: "get_sprint_status",
    description: "Lấy trạng thái sprint hiện tại từ hệ thống nội bộ",
    inputSchema: {
      type: "object",
      properties: { sprintId: { type: "string" } },
      required: ["sprintId"]
    }
  }]
}));

server.setRequestHandler("tools/call", async (request) => {
  if (request.params.name === "get_sprint_status") {
    const { sprintId } = request.params.arguments as { sprintId: string };
    // gọi API nội bộ
    const result = await fetchSprintStatus(sprintId);
    return { content: [{ type: "text", text: JSON.stringify(result) }] };
  }
  throw new Error("Unknown tool");
});

const transport = new StdioServerTransport();
await server.connect(transport);

Compile, đặt vào ~/.claude/mcp-servers/ hoặc bất kỳ path nào, rồi add vào settings:

{
  "mcpServers": {
    "internal": {
      "command": "node",
      "args": ["/Users/me/.claude/mcp-servers/internal/index.js"]
    }
  }
}

Anti-pattern: load 20 server cho mọi session

Tool list dài ảnh hưởng thực tế:

  • Token tốn cho schema: mỗi tool có description + input schema. 20 server với trung bình 5 tool mỗi server = 100 tool schema nạp vào L3 mỗi turn.
  • Model chậm decide: với quá nhiều tool available, model mất thêm bước để chọn tool đúng. Đôi khi confuse giữa tool tương tự từ hai server.
  • Khởi động chậm: stdio server spin up một process mỗi khi CC khởi động. 20 server = 20 process cần sẵn sàng.

Nguyên tắc thực tế: enable đúng server cần cho project đang làm. Project code JavaScript thì cần GitHub MCP, có thể cần Postgres. Không cần Slack, không cần Linear, không cần filesystem extra.

Best practice: per-project, không global

Đặt MCP config ở <project>/.claude/settings.local.json thay vì ~/.claude/settings.json:

{
  "mcpServers": {
    "postgres": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-postgres"],
      "env": {
        "DATABASE_URL": "postgresql://dev:dev@localhost:5432/myapp_dev"
      }
    }
  }
}

File settings.local.json gitignored theo convention của CC. Không ai khác thấy config này khi clone repo. Database URL nằm trong file đó, không leak vào git.

Khi chuyển sang project khác, server Postgres đó không load. Tool list gọn hơn, session khởi động nhanh hơn.

Pattern Deferred Tools

Một cơ chế liên quan đến MCP nhưng ít được nói đến: deferred tools.

Một số tool không load vào L3 ngay từ đầu session. Chúng chỉ available dưới dạng tên. Khi model cần, nó phải gọi ToolSearch để fetch schema, sau đó mới gọi được tool đó.

Session khởi động
     |
     v
L3: [Bash, Read, Edit, Write, ...]   <- built-in, load ngay
     |
     v
<system-reminder>: "Deferred tools: WebFetch, mcp__github__..."
     |
     v
Model đọc reminder, biết có WebFetch
Model gọi ToolSearch("select:WebFetch")  <- fetch schema
     |
     v
L3 mở rộng: [Bash, Read, ..., WebFetch]  <- giờ mới gọi được

Mục đích: tiết kiệm token. Tool dùng hiếm không cần tốn token cho schema mỗi turn. Chỉ tốn khi thực sự cần.

Với MCP server có nhiều tool, bạn có thể config server chỉ expose một phần tool sẵn, để phần còn lại là deferred. Không phải tất cả MCP SDK hỗ trợ điều này tốt, nhưng là hướng optimize đáng nghĩ nếu bạn build server lớn.

Liên kết với permission

MCP tool chịu cùng hệ thống permission như built-in tool. Khi model gọi postgres__run_query, CC hỏi bạn confirm (ở ask mode) hoặc tự chạy nếu tool nằm trong allowlist.

Để allow MCP tool không hỏi mỗi lần, thêm vào settings.json:

{
  "permissions": {
    "allow": ["mcp__postgres__describe_table", "mcp__github__get_file_contents"]
  }
}

Pattern mcp__<server-name>__<tool-name> là format CC dùng để identify MCP tool trong permission system. Bài 4 về permission model giải thích chi tiết cách allowlist và denylist phối hợp với nhau.

Tóm tắt và bài tiếp theo

  • MCP là protocol để model tương tác với external system (GitHub, Slack, DB, browser) qua tool và resource.
  • MCP inject tool vào L3. Hooks can thiệp ở event loop bên ngoài. Hai cơ chế khác nhau, phối hợp không thay thế nhau.
  • Ba kiểu transport: stdio (local process, phổ biến nhất), HTTP, SSE.
  • Enable per-project trong settings.local.json, không global. Tool list dài tốn token và làm model chậm quyết định.
  • Dùng server có sẵn cho GitHub, Slack, Postgres, Linear. Tự build khi workflow nội bộ, secret handling đặc thù, hoặc cần scope hẹp hơn.
  • MCP tool cũng nằm dưới permission system. Bài 4 có chi tiết về cách allow/deny tool cụ thể.

Bài 16 sẽ nói về spawning patterns: làm thế nào để CC tự spawn subagent, phân chia task song song, và chia sẻ context giữa các agent trong cùng session.


Bài thuộc series Claude Code từ zero. Series plan tại bài giới thiệu.