· mcp / claude-code / typescript
Cách tạo và sử dụng MCP server với Claude Code (2026)
MCP là cách nhanh nhất để cung cấp context cho Claude Code mà không cần paste file mỗi phiên. Đây là hướng dẫn thực tế từ scaffold đến tool hoàn chỉnh.
Bởi Ethan
1.768 từ · 9 phút đọc
MCP là cách nhanh nhất để cung cấp context cho Claude Code mà không phải paste file mỗi phiên. Bạn chỉ cần viết một TypeScript server nhỏ một lần, đăng ký trong .mcp.json, và mọi phiên làm việc sau đó đều có thể truy cập schema, API nội bộ, cùng bất cứ thứ gì bạn muốn expose — không tốn context cho các đoạn paste tĩnh.
Kết thúc hướng dẫn này, bạn sẽ có một MCP server tùy chỉnh hoạt động và được kết nối với Claude Code. Bạn cũng sẽ biết khi nào nên dùng MCP thay vì CLAUDE.md hay pre-tool-use hook.
Dành cho ai
Người dùng Claude Code muốn có context tool cố định: DB schema, CLI wrapper, API nội bộ. Nếu bạn đã nắm cơ bản về Claude Code và biết TypeScript, bạn đã sẵn sàng. Nếu bạn chưa quen Claude Code, hãy đọc đánh giá Claude Code 2026 trước. Nếu bạn muốn giao diện chat thay vì CLI, bài này không dành cho bạn.
Yêu cầu
- Node.js 20+
- Claude Code đã cài (
npm install -g @anthropic-ai/claude-code) - TypeScript cơ bản
MCP là gì
Model Context Protocol (MCP) là một giao thức mở cho phép language model gọi các tool bên ngoài và đọc tài nguyên bên ngoài. Claude Code khởi động MCP server của bạn như một child process, gửi JSON-RPC message qua stdio, và sử dụng các tool cùng resource mà server của bạn khai báo. Bạn định nghĩa model có thể gọi gì; model tự quyết định khi nào gọi.
Đó là toàn bộ mental model. MCP không phải plugin system hay agent framework — đây là cách có cấu trúc để model vươn ra ngoài context window và thực hiện các tác vụ.
Hướng dẫn thực hành
Bước 1 — Scaffold server
Tạo thư mục cho server và cài dependencies:
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/[email protected] zod@3
npm install -D typescript @types/node
mkdir src && touch src/index.ts
Thêm các field sau vào package.json:
{
"type": "module",
"scripts": { "build": "tsc && chmod 755 build/index.js" },
"files": ["build"]
}
Tạo tsconfig.json ở thư mục gốc:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
Lỗi thường gặp: module: "Node16" là bắt buộc để các import .js hoạt động đúng với ESM. Dùng "CommonJS" ở đây sẽ gây lỗi import lúc runtime với @modelcontextprotocol/sdk.
Bước 2 — Viết một tool tối giản
Mở src/index.ts và viết server với một tool duy nhất:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "my-server", version: "1.0.0" });
server.registerTool(
"hello",
{
description: "Returns a greeting",
inputSchema: { name: z.string().describe("Name to greet") },
},
async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}!` }],
})
);
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Server running on stdio"); // chỉ dùng stderr — xem bước 3
Build:
npm run build
Quan trọng: tuyệt đối không gọi console.log() bên trong MCP server dùng stdio transport. Giao thức MCP gửi JSON-RPC message qua stdout; bất kỳ byte thừa nào trên stdout đều làm hỏng luồng message và server sẽ lỗi âm thầm. Dùng console.error() cho mọi log — nó đi vào stderr, nơi Claude Code thu thập riêng.
Bước 3 — Khai báo resource
Tool là cho các tác vụ model kích hoạt theo yêu cầu. Resource là cho dữ liệu model có thể đọc. Sự khác biệt có ý nghĩa: resource là một tài liệu có URI (như file hay DB row); tool là một function có thể gọi được.
Đây là resource expose Drizzle schema hiện tại:
import path from "node:path";
import fs from "node:fs/promises";
server.registerResource(
"schema",
"schema://current",
{ description: "Current Drizzle schema" },
async (uri) => {
const schemaPath = path.join(
process.env.CLAUDE_PROJECT_DIR ?? ".",
"drizzle/schema.ts"
);
const content = await fs.readFile(schemaPath, "utf-8");
return {
contents: [{ uri: uri.href, mimeType: "text/plain", text: content }],
};
}
);
CLAUDE_PROJECT_DIR được Claude Code inject khi khởi động server của bạn. Nó trỏ đến thư mục gốc của project — thư mục chứa .mcp.json. Dùng biến này để resolve các đường dẫn trong project mà không cần hardcode.
Rebuild sau khi thêm resource: npm run build.
Bước 4 — Đăng ký server trong .mcp.json
Cách đăng ký được khuyến nghị là file .mcp.json commit vào thư mục gốc của project. Mọi developer clone repo đều có ngay cùng bộ tool.
{
"mcpServers": {
"my-schema-tool": {
"type": "stdio",
"command": "node",
"args": ["./mcp-servers/schema-tool/build/index.js"],
"env": {
"DATABASE_URL": "${DATABASE_URL}"
}
}
}
}
Cú pháp expand env var là ${VAR} (bắt buộc) hoặc ${VAR:-default} (có fallback). Expansion hoạt động trong command, args, env, url, và headers.
Một tên cần tránh: workspace. Claude Code bỏ qua bất kỳ server nào có tên workspace khi load và hiện warning yêu cầu đổi tên. Chọn bất kỳ tên nào khác.
Thay thế cho dùng riêng cá nhân: nếu bạn không muốn commit server vào git, dùng CLI:
claude mcp add --transport stdio my-server -- node ./build/index.js
Lệnh này ghi vào ~/.claude.json và chỉ tồn tại trên máy của bạn. Với team, .mcp.json mới là lựa chọn đúng.
Bước 5 — Kiểm tra Claude Code đã nhận server
Mở phiên Claude Code trong thư mục project. Chạy lệnh /mcp:
/mcp
Lệnh này mở panel liệt kê tất cả server đã cấu hình cùng trạng thái:
✓ Connected— server đã khởi động và handshake thành công✗ Failed— tiến trình server bị crash khi khởi động; xem stderr quaclaude mcp get <name>⏸ Pending approval— server từ.mcp.jsoncần được duyệt một lần trước khi Claude Code sử dụng
Nếu thấy Pending approval, hãy duyệt trong panel. Đây là cơ chế bảo mật: Claude Code sẽ không chạy server từ project nếu chưa có sự đồng ý của người dùng. Duyệt một lần trên mỗi máy là đủ, sau đó tự động được chấp thuận.
Để liệt kê và xem thông tin server từ terminal:
claude mcp list # tất cả server với trạng thái
claude mcp get my-schema-tool # chi tiết một server
Nếu server hiển thị ✗ Failed, nguyên nhân phổ biến nhất là: đường dẫn sai trong args, chưa chạy npm run build, hoặc có lời gọi console.log() làm hỏng stdout.
Bước 6 — Ví dụ thực tế: tool get_schema
Tool trọng tâm trong bài này là get_schema — trả về Drizzle schema của project hiện tại khi cần. Dùng tool thay vì resource vì model có thể gọi tự nhiên (“show me the schema”) mà không cần biết URI của resource.
import path from "node:path";
import fs from "node:fs/promises";
server.registerTool(
"get_schema",
{
description: "Returns the Drizzle ORM schema for the current project",
inputSchema: {},
},
async () => {
const dir = process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
const schemaFile = path.join(dir, "drizzle/schema.ts");
try {
const text = await fs.readFile(schemaFile, "utf-8");
return { content: [{ type: "text", text }] };
} catch {
return {
content: [{ type: "text", text: `Schema file not found at ${schemaFile}` }],
};
}
}
);
Sau khi đăng ký tool này, bạn chỉ cần hỏi “project này có những bảng gì?” và Claude Code sẽ tự động gọi get_schema, đọc schema rồi trả lời — không cần paste gì cả.
Pattern tương tự áp dụng cho mọi thứ trong project: get_openapi_spec, run_query, get_env_vars. Mỗi tool là một function Claude Code có thể gọi khi cần context đó.
Khi nào dùng MCP, CLAUDE.md, hay hook
| Dùng… | Khi bạn cần… |
|---|---|
| MCP tool | Claude chạy tác vụ hoặc lấy dữ liệu động (query DB, gọi API, đọc file thay đổi liên tục) |
| MCP resource | Dữ liệu có thể đọc cố định mà Claude kéo về theo yêu cầu |
CLAUDE.md | Context tĩnh không thay đổi (coding standard, quyết định kiến trúc, tài liệu project) |
| Pre-tool-use hook | Chặn hoặc validate các tool call hiện có (chặn lệnh bash nguy hiểm, log mọi lần edit) |
Sự đánh đổi cốt lõi: nội dung trong CLAUDE.md được load vào mỗi phiên, luôn tốn context budget. MCP tool và resource chỉ được kéo về khi model gọi — model chỉ trả context cost khi thực sự cần. Nếu context theo project của bạn lớn hoặc thay đổi thường xuyên, MCP là lựa chọn đúng.
MCP cũng có lợi hơn khi bạn cần cùng context trên nhiều project. Đăng ký server ở user scope (claude mcp add --scope user ...) và mọi project trên máy bạn đều có ngay.
MCP thua khi dữ liệu hoàn toàn tĩnh và nhỏ. Một CLAUDE.md 20 dòng mô tả format commit message thì nhanh và đơn giản hơn nhiều so với một MCP server đọc cùng file đó.
Để hiểu sâu hơn về sự khác biệt kiến trúc giữa MCP và REST API trong ngữ cảnh AI agent, xem MCP vs REST: Khi nào nên dùng cái nào cho AI agent?.
Lưu ý
- MCP server chạy như child process. Với project lớn có nhiều server, mỗi server thêm latency khi khởi động. Giữ server nhỏ gọn và khởi động nhanh.
- TypeScript server cần
npm run buildtrước khi thay đổi có hiệu lực. Không có hot-reload — bạn phải rebuild và khởi động lại Claude Code sau khi chỉnh sửa. - Server local chạy với đầy đủ quyền của tiến trình. Không đăng ký server không tin cậy từ internet qua
.mcp.json; server có quyền truy cập filesystem, env var, và network của bạn. - v2 của
@modelcontextprotocol/sdk(dự kiến stable vào tháng 7 năm 2026) tách package thành@modelcontextprotocol/servervà@modelcontextprotocol/client. Hướng dẫn này dùng v1.29.0, bản stable hiện tại. Kiểm tra trang npm trước khi bắt đầu project mới.
Tham khảo
- MCP Architecture — loại transport, primitive, phiên bản giao thức 2025-06-18
- MCP Server Quickstart (TypeScript) — code TypeScript đầy đủ với
registerTool,StdioServerTransport - MCP Resources spec — URI scheme,
registerResource, template - Claude Code MCP reference — định dạng
.mcp.json, scope, lệnh/mcp, expand env var - Claude Code MCP quickstart — quy trình add/verify/use, xử lý sự cố
- npm: @modelcontextprotocol/sdk — phiên bản stable hiện tại 1.29.0
- GitHub: modelcontextprotocol/typescript-sdk — source, README pre-alpha v2, docs v1