{"product_id":"xay-dựng-mcp-server-dầu-tien-với-typescript-hướng-dẫn-từng-bước","title":"Xây dựng MCP Server đầu tiên với TypeScript — Hướng dẫn từng bước","description":"\n\u003cp\u003eModel Context Protocol (MCP) là giao thức mở cho phép các ứng dụng AI như Claude kết nối với dữ liệu và công cụ bên ngoài một cách chuẩn hóa. Thay vì viết integration riêng cho từng ứng dụng, bạn xây dựng một MCP Server và mọi client tương thích đều có thể sử dụng. Trong bài hướng dẫn này, chúng ta sẽ xây dựng MCP Server đầu tiên với TypeScript — từ thiết lập dự án đến triển khai thực tế.\u003c\/p\u003e\n\n\u003ch2\u003e1. Điều kiện tiên quyết\u003c\/h2\u003e\n\u003cp\u003eTrước khi bắt đầu, hãy đảm bảo bạn đã cài đặt:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eNode.js 18+\u003c\/strong\u003e — MCP SDK yêu cầu tối thiểu Node.js 18. Kiểm tra bằng \u003ccode\u003enode --version\u003c\/code\u003e\n\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eTypeScript 5.0+\u003c\/strong\u003e — Cài đặt toàn cục qua \u003ccode\u003enpm install -g typescript\u003c\/code\u003e\n\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eClaude Code\u003c\/strong\u003e — Để test MCP Server trực tiếp. Cài đặt qua \u003ccode\u003enpm install -g @anthropic-ai\/claude-code\u003c\/code\u003e\n\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eKiến thức cơ bản về TypeScript\u003c\/strong\u003e — Hiểu type, interface, async\/await\u003c\/li\u003e\n\u003c\/ul\u003e\n\u003cp\u003eMCP hoạt động theo mô hình client-server: Claude Code (hoặc Claude Desktop) là client, còn server của bạn cung cấp tools (hàm thực thi), resources (dữ liệu đọc) và prompts (template). Giao tiếp qua stdio hoặc HTTP với Server-Sent Events.\u003c\/p\u003e\n\n\u003ch2\u003e2. Thiết lập dự án\u003c\/h2\u003e\n\u003cp\u003eTạo thư mục dự án và khởi tạo:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003emkdir my-mcp-server\ncd my-mcp-server\nnpm init -y\nnpm install @modelcontextprotocol\/sdk zod\nnpm install -D typescript @types\/node\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eTạo file \u003ccode\u003etsconfig.json\u003c\/code\u003e:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"module\": \"Node16\",\n    \"moduleResolution\": \"Node16\",\n    \"outDir\": \".\/dist\",\n    \"rootDir\": \".\/src\",\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true,\n    \"declaration\": true\n  },\n  \"include\": [\"src\/**\/*\"]\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eCập nhật \u003ccode\u003epackage.json\u003c\/code\u003e:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e{\n  \"type\": \"module\",\n  \"bin\": {\n    \"my-mcp-server\": \".\/dist\/index.js\"\n  },\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"start\": \"node dist\/index.js\",\n    \"dev\": \"tsc --watch\"\n  }\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eTạo file entry point \u003ccode\u003esrc\/index.ts\u003c\/code\u003e:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e#!\/usr\/bin\/env node\nimport { McpServer } from \"@modelcontextprotocol\/sdk\/server\/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol\/sdk\/server\/stdio.js\";\n\nconst server = new McpServer({\n  name: \"my-mcp-server\",\n  version: \"1.0.0\",\n  description: \"MCP Server đầu tiên của tôi\"\n});\n\n\/\/ Tools và resources sẽ được đăng ký ở đây\n\nasync function main() {\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n  console.error(\"MCP Server đang chạy trên stdio\");\n}\n\nmain().catch(console.error);\u003c\/code\u003e\u003c\/pre\u003e\n\u003cp\u003eLưu ý: dùng \u003ccode\u003econsole.error\u003c\/code\u003e thay vì \u003ccode\u003econsole.log\u003c\/code\u003e vì stdio transport sử dụng stdout cho giao tiếp JSON-RPC. Mọi log phải ghi vào stderr.\u003c\/p\u003e\n\n\u003ch2\u003e3. Định nghĩa Tool đầu tiên: Weather Lookup\u003c\/h2\u003e\n\u003cp\u003eTool trong MCP là hàm mà AI có thể gọi để thực hiện hành động. Hãy tạo một tool tra cứu thời tiết đơn giản:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003eimport { z } from \"zod\";\n\nserver.tool(\n  \"get_weather\",\n  \"Tra cứu thời tiết hiện tại cho một thành phố\",\n  {\n    city: z.string().describe(\"Tên thành phố, ví dụ: Hanoi, Ho Chi Minh City\"),\n    unit: z.enum([\"celsius\", \"fahrenheit\"]).default(\"celsius\")\n      .describe(\"Đơn vị nhiệt độ\")\n  },\n  async ({ city, unit }) =\u0026gt; {\n    \/\/ Trong thực tế, gọi API thời tiết ở đây\n    const weatherData: Record\u0026lt;string, { temp: number; condition: string }\u0026gt; = {\n      \"hanoi\": { temp: 28, condition: \"Nhiều mây\" },\n      \"ho chi minh city\": { temp: 33, condition: \"Nắng nóng\" },\n      \"da nang\": { temp: 30, condition: \"Có mưa rào\" }\n    };\n\n    const key = city.toLowerCase();\n    const data = weatherData[key];\n\n    if (!data) {\n      return {\n        content: [{ type: \"text\", text: `Không tìm thấy dữ liệu thời tiết cho \"${city}\"` }],\n        isError: true\n      };\n    }\n\n    const temp = unit === \"fahrenheit\"\n      ? (data.temp * 9\/5 + 32).toFixed(1) + \"°F\"\n      : data.temp + \"°C\";\n\n    return {\n      content: [{\n        type: \"text\",\n        text: `Thời tiết tại ${city}: ${temp}, ${data.condition}`\n      }]\n    };\n  }\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eCấu trúc một tool gồm 4 phần: tên định danh (snake_case), mô tả ngắn gọn cho AI hiểu khi nào nên dùng, schema tham số định nghĩa bằng Zod, và hàm xử lý trả về kết quả. Schema Zod được tự động chuyển thành JSON Schema để AI biết cần truyền gì.\u003c\/p\u003e\n\n\u003ch2\u003e4. Định nghĩa Resource đầu tiên: Database Query\u003c\/h2\u003e\n\u003cp\u003eResource trong MCP cung cấp dữ liệu có cấu trúc mà AI có thể đọc — tương tự REST endpoint nhưng dành cho AI. Tạo resource truy vấn danh sách sản phẩm:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003eserver.resource(\n  \"products\",\n  \"products:\/\/list\",\n  {\n    description: \"Danh sách sản phẩm trong cơ sở dữ liệu\",\n    mimeType: \"application\/json\"\n  },\n  async (uri) =\u0026gt; {\n    \/\/ Trong thực tế, truy vấn database ở đây\n    const products = [\n      { id: 1, name: \"Cà phê Robusta\", price: 85000, stock: 150 },\n      { id: 2, name: \"Cà phê Arabica\", price: 120000, stock: 80 },\n      { id: 3, name: \"Trà Ô Long\", price: 95000, stock: 200 }\n    ];\n\n    return {\n      contents: [{\n        uri: uri.href,\n        mimeType: \"application\/json\",\n        text: JSON.stringify(products, null, 2)\n      }]\n    };\n  }\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eResource có thể sử dụng URI template để hỗ trợ tham số động:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003eserver.resource(\n  \"product-detail\",\n  \"products:\/\/{id}\",\n  { description: \"Chi tiết một sản phẩm theo ID\" },\n  async (uri, { id }) =\u0026gt; {\n    \/\/ Truy vấn sản phẩm theo id\n    const product = await fetchProductById(id);\n    return {\n      contents: [{\n        uri: uri.href,\n        mimeType: \"application\/json\",\n        text: JSON.stringify(product, null, 2)\n      }]\n    };\n  }\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eĐiểm khác biệt chính: tool dùng để \u003cem\u003ethực hiện hành động\u003c\/em\u003e (gửi email, tạo bản ghi, gọi API), resource dùng để \u003cem\u003eđọc dữ liệu\u003c\/em\u003e (lấy cấu hình, danh sách, trạng thái). AI sẽ tự quyết định dùng tool hay resource dựa trên ngữ cảnh câu hỏi.\u003c\/p\u003e\n\n\u003ch2\u003e5. Xử lý Tool Call với Input Validation\u003c\/h2\u003e\n\u003cp\u003eZod tự động validate input trước khi hàm xử lý được gọi. Tuy nhiên, bạn nên thêm business logic validation:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003eserver.tool(\n  \"create_order\",\n  \"Tạo đơn hàng mới\",\n  {\n    customer_name: z.string().min(2).max(100)\n      .describe(\"Tên khách hàng\"),\n    items: z.array(z.object({\n      product_id: z.number().int().positive(),\n      quantity: z.number().int().min(1).max(999)\n    })).min(1).max(50)\n      .describe(\"Danh sách sản phẩm trong đơn hàng\"),\n    shipping_address: z.string().min(10)\n      .describe(\"Địa chỉ giao hàng đầy đủ\"),\n    note: z.string().optional()\n      .describe(\"Ghi chú đơn hàng\")\n  },\n  async ({ customer_name, items, shipping_address, note }) =\u0026gt; {\n    \/\/ Business validation\n    const invalidItems: number[] = [];\n    for (const item of items) {\n      const product = await getProduct(item.product_id);\n      if (!product) {\n        invalidItems.push(item.product_id);\n      } else if (product.stock \u0026lt; item.quantity) {\n        return {\n          content: [{\n            type: \"text\",\n            text: `Sản phẩm \"${product.name}\" chỉ còn ${product.stock} trong kho, không đủ ${item.quantity} đơn vị.`\n          }],\n          isError: true\n        };\n      }\n    }\n\n    if (invalidItems.length \u0026gt; 0) {\n      return {\n        content: [{\n          type: \"text\",\n          text: `Không tìm thấy sản phẩm với ID: ${invalidItems.join(\", \")}`\n        }],\n        isError: true\n      };\n    }\n\n    \/\/ Tạo đơn hàng\n    const order = await createOrderInDB({\n      customer_name, items, shipping_address, note\n    });\n\n    return {\n      content: [{\n        type: \"text\",\n        text: `Đơn hàng #${order.id} đã được tạo thành công cho ${customer_name}. Tổng giá trị: ${order.total.toLocaleString(\"vi-VN\")}đ`\n      }]\n    };\n  }\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eMột số best practice cho validation: sử dụng Zod schema chi tiết với \u003ccode\u003e.min()\u003c\/code\u003e, \u003ccode\u003e.max()\u003c\/code\u003e, \u003ccode\u003e.regex()\u003c\/code\u003e; luôn thêm \u003ccode\u003e.describe()\u003c\/code\u003e để AI hiểu mục đích từng tham số; trả về \u003ccode\u003eisError: true\u003c\/code\u003e khi có lỗi business logic thay vì throw exception; và giữ thông báo lỗi rõ ràng, cụ thể để AI có thể thử lại hoặc thông báo cho người dùng.\u003c\/p\u003e\n\n\u003ch2\u003e6. Testing với Claude Code\u003c\/h2\u003e\n\u003cp\u003eSau khi code xong, build và test trực tiếp với Claude Code:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e# Build TypeScript\nnpm run build\n\n# Test nhanh với MCP Inspector (công cụ debug chính thức)\nnpx @modelcontextprotocol\/inspector node dist\/index.js\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eMCP Inspector mở giao diện web tại \u003ccode\u003ehttp:\/\/localhost:5173\u003c\/code\u003e, cho phép bạn xem danh sách tools\/resources, gọi thử từng tool với tham số tùy chỉnh và kiểm tra response format.\u003c\/p\u003e\n\n\u003cp\u003eĐể test thực tế với Claude Code, thêm server vào cấu hình:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e# Thêm MCP server vào Claude Code (scope project)\nclaude mcp add my-server node \/đường-dẫn-tuyệt-đối\/dist\/index.js\n\n# Hoặc thêm vào scope global\nclaude mcp add --scope user my-server node \/đường-dẫn-tuyệt-đối\/dist\/index.js\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eSau khi thêm, khởi động Claude Code và thử:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e# Trong Claude Code, gõ:\nThời tiết Hà Nội hôm nay thế nào?\n# Claude sẽ tự động gọi tool get_weather với city=\"Hanoi\"\n\n# Kiểm tra resource:\nCho tôi xem danh sách sản phẩm trong database\n# Claude sẽ đọc resource products:\/\/list\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eKiểm tra danh sách server đã đăng ký:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003eclaude mcp list\n# Kết quả:\n# my-server: node \/path\/to\/dist\/index.js (local)\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eNếu cần debug chi tiết, bật log verbose:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003eCLAUDE_DEBUG=1 claude\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003e7. Xử lý lỗi và Logging\u003c\/h2\u003e\n\u003cp\u003eMột MCP Server production cần xử lý lỗi chặt chẽ. Tạo module logging riêng:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ src\/logger.ts\nimport { createWriteStream } from \"fs\";\n\nconst logFile = createWriteStream(\"mcp-server.log\", { flags: \"a\" });\n\nexport function log(level: \"info\" | \"warn\" | \"error\", message: string, data?: unknown) {\n  const entry = {\n    timestamp: new Date().toISOString(),\n    level,\n    message,\n    ...(data ? { data } : {})\n  };\n  \/\/ Ghi vào file (không dùng stdout vì stdio transport)\n  logFile.write(JSON.stringify(entry) + \"\\n\");\n  \/\/ Cũng ghi vào stderr để debug\n  console.error(`[${level.toUpperCase()}] ${message}`);\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eWrap tool handler với error handling:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003efunction withErrorHandling(\n  toolName: string,\n  handler: (...args: any[]) =\u0026gt; Promise\u0026lt;any\u0026gt;\n) {\n  return async (...args: any[]) =\u0026gt; {\n    try {\n      log(\"info\", `Tool called: ${toolName}`, { args: args[0] });\n      const result = await handler(...args);\n      log(\"info\", `Tool completed: ${toolName}`);\n      return result;\n    } catch (error) {\n      const message = error instanceof Error ? error.message : String(error);\n      log(\"error\", `Tool failed: ${toolName}`, { error: message });\n      return {\n        content: [{\n          type: \"text\" as const,\n          text: `Lỗi khi thực thi ${toolName}: ${message}`\n        }],\n        isError: true\n      };\n    }\n  };\n}\n\n\/\/ Sử dụng:\nserver.tool(\n  \"get_weather\",\n  \"Tra cứu thời tiết\",\n  { city: z.string() },\n  withErrorHandling(\"get_weather\", async ({ city }) =\u0026gt; {\n    \/\/ logic ở đây\n  })\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eCác loại lỗi cần xử lý: network timeout khi gọi API bên ngoài (đặt timeout hợp lý, thường 10-30 giây); rate limiting (trả về thông báo rõ ràng và thời gian chờ); invalid data từ nguồn bên ngoài (validate response trước khi trả về); và lỗi kết nối database (retry logic với exponential backoff).\u003c\/p\u003e\n\n\u003ch2\u003e8. Triển khai: Docker và npm publish\u003c\/h2\u003e\n\n\u003ch3\u003eĐóng gói với Docker\u003c\/h3\u003e\n\u003cp\u003eTạo \u003ccode\u003eDockerfile\u003c\/code\u003e:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003eFROM node:20-slim AS builder\nWORKDIR \/app\nCOPY package*.json .\/\nRUN npm ci\nCOPY tsconfig.json .\/\nCOPY src\/ .\/src\/\nRUN npm run build\n\nFROM node:20-slim\nWORKDIR \/app\nCOPY package*.json .\/\nRUN npm ci --omit=dev\nCOPY --from=builder \/app\/dist .\/dist\nENTRYPOINT [\"node\", \"dist\/index.js\"]\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cpre\u003e\u003ccode\u003e# Build và chạy\ndocker build -t my-mcp-server .\ndocker run -i my-mcp-server\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eFlag \u003ccode\u003e-i\u003c\/code\u003e (interactive) quan trọng vì MCP Server giao tiếp qua stdin\/stdout. Không cần \u003ccode\u003e-t\u003c\/code\u003e (tty).\u003c\/p\u003e\n\n\u003ch3\u003ePublish lên npm\u003c\/h3\u003e\n\u003cp\u003eCập nhật \u003ccode\u003epackage.json\u003c\/code\u003e:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e{\n  \"name\": \"@your-scope\/my-mcp-server\",\n  \"version\": \"1.0.0\",\n  \"description\": \"MCP Server cho ...\",\n  \"bin\": {\n    \"my-mcp-server\": \".\/dist\/index.js\"\n  },\n  \"files\": [\"dist\"],\n  \"keywords\": [\"mcp\", \"mcp-server\", \"claude\"]\n}\u003c\/code\u003e\u003c\/pre\u003e\n\u003cpre\u003e\u003ccode\u003enpm run build\nnpm publish --access public\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003cp\u003eSau khi publish, người dùng cài đặt và đăng ký với Claude Code:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003enpx @your-scope\/my-mcp-server\n# Hoặc\nclaude mcp add my-server npx @your-scope\/my-mcp-server\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003e9. Đăng ký trong Claude Code Settings\u003c\/h2\u003e\n\u003cp\u003eMCP Server có thể được đăng ký ở nhiều scope khác nhau trong Claude Code:\u003c\/p\u003e\n\n\u003ch3\u003eProject scope (khuyến nghị cho team)\u003c\/h3\u003e\n\u003cp\u003eTạo file \u003ccode\u003e.mcp.json\u003c\/code\u003e tại root dự án:\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e{\n  \"mcpServers\": {\n    \"my-server\": {\n      \"command\": \"node\",\n      \"args\": [\"\/đường-dẫn\/dist\/index.js\"],\n      \"env\": {\n        \"API_KEY\": \"your-api-key\"\n      }\n    }\n  }\n}\u003c\/code\u003e\u003c\/pre\u003e\n\u003cp\u003eKhi bất kỳ thành viên nào mở Claude Code trong thư mục dự án, server sẽ tự động được nhận diện. File này nên commit vào repo (trừ phần env chứa secret).\u003c\/p\u003e\n\n\u003ch3\u003eUser scope (cá nhân)\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003eclaude mcp add --scope user my-server node \/đường-dẫn\/dist\/index.js\u003c\/code\u003e\u003c\/pre\u003e\n\u003cp\u003eServer sẽ khả dụng trong mọi dự án bạn mở với Claude Code.\u003c\/p\u003e\n\n\u003ch3\u003eTruyền biến môi trường\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003eclaude mcp add my-server -e API_KEY=xxx -e DB_HOST=localhost node dist\/index.js\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eQuản lý server\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e# Xem danh sách\nclaude mcp list\n\n# Xem chi tiết\nclaude mcp get my-server\n\n# Xóa server\nclaude mcp remove my-server\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003e10. Ví dụ thực tế: MCP Server dữ liệu chứng khoán Việt Nam\u003c\/h2\u003e\n\u003cp\u003eHãy xây dựng một MCP Server thực tế cung cấp dữ liệu chứng khoán Việt Nam — VN-Index, HNX-Index và thông tin cổ phiếu. Server này sử dụng API công khai từ các nguồn dữ liệu chứng khoán Việt Nam.\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003e\/\/ src\/index.ts\n#!\/usr\/bin\/env node\nimport { McpServer } from \"@modelcontextprotocol\/sdk\/server\/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol\/sdk\/server\/stdio.js\";\nimport { z } from \"zod\";\n\nconst server = new McpServer({\n  name: \"vnstock-mcp\",\n  version: \"1.0.0\",\n  description: \"MCP Server dữ liệu chứng khoán Việt Nam\"\n});\n\n\/\/ Helper: gọi API với timeout và retry\nasync function fetchWithRetry(url: string, retries = 3): Promise\u0026lt;any\u0026gt; {\n  for (let i = 0; i \u0026lt; retries; i++) {\n    try {\n      const controller = new AbortController();\n      const timeout = setTimeout(() =\u0026gt; controller.abort(), 10000);\n      const res = await fetch(url, { signal: controller.signal });\n      clearTimeout(timeout);\n      if (!res.ok) throw new Error(`HTTP ${res.status}`);\n      return await res.json();\n    } catch (err) {\n      if (i === retries - 1) throw err;\n      await new Promise(r =\u0026gt; setTimeout(r, 1000 * (i + 1)));\n    }\n  }\n}\n\n\/\/ Tool 1: Lấy chỉ số thị trường\nserver.tool(\n  \"get_market_index\",\n  \"Lấy chỉ số thị trường chứng khoán Việt Nam (VN-Index, HNX-Index, UPCOM)\",\n  {\n    index: z.enum([\"VNINDEX\", \"HNX\", \"UPCOM\"]).default(\"VNINDEX\")\n      .describe(\"Mã chỉ số thị trường\")\n  },\n  async ({ index }) =\u0026gt; {\n    try {\n      const data = await fetchWithRetry(\n        `https:\/\/api.example.com\/market\/${index}`\n      );\n      return {\n        content: [{\n          type: \"text\",\n          text: [\n            `📊 ${index}`,\n            `Điểm số: ${data.value.toFixed(2)}`,\n            `Thay đổi: ${data.change \u0026gt; 0 ? \"+\" : \"\"}${data.change.toFixed(2)} (${data.changePct.toFixed(2)}%)`,\n            `Khối lượng: ${(data.volume \/ 1e6).toFixed(1)} triệu CP`,\n            `Giá trị: ${(data.totalValue \/ 1e9).toFixed(0)} tỷ VNĐ`,\n            `Cập nhật: ${data.time}`\n          ].join(\"\\n\")\n        }]\n      };\n    } catch (error) {\n      return {\n        content: [{ type: \"text\", text: `Không thể lấy dữ liệu ${index}. Vui lòng thử lại.` }],\n        isError: true\n      };\n    }\n  }\n);\n\n\/\/ Tool 2: Tra cứu thông tin cổ phiếu\nserver.tool(\n  \"get_stock_info\",\n  \"Tra cứu thông tin chi tiết một mã cổ phiếu trên sàn HOSE, HNX hoặc UPCOM\",\n  {\n    symbol: z.string().min(3).max(5).toUpperCase()\n      .describe(\"Mã cổ phiếu, ví dụ: VNM, FPT, VIC, HPG\"),\n  },\n  async ({ symbol }) =\u0026gt; {\n    try {\n      const data = await fetchWithRetry(\n        `https:\/\/api.example.com\/stock\/${symbol}`\n      );\n      return {\n        content: [{\n          type: \"text\",\n          text: [\n            `🏢 ${symbol} — ${data.companyName}`,\n            `Sàn: ${data.exchange} | Ngành: ${data.industry}`,\n            `Giá hiện tại: ${data.price.toLocaleString(\"vi-VN\")} VNĐ`,\n            `Thay đổi: ${data.change \u0026gt; 0 ? \"+\" : \"\"}${data.change.toLocaleString(\"vi-VN\")} (${data.changePct.toFixed(2)}%)`,\n            `Trần: ${data.ceiling.toLocaleString(\"vi-VN\")} | Sàn: ${data.floor.toLocaleString(\"vi-VN\")} | TC: ${data.ref.toLocaleString(\"vi-VN\")}`,\n            `KL khớp lệnh: ${(data.volume \/ 1e3).toFixed(1)}K`,\n            `Vốn hóa: ${(data.marketCap \/ 1e12).toFixed(2)} nghìn tỷ VNĐ`,\n            `P\/E: ${data.pe?.toFixed(2) ?? \"N\/A\"} | P\/B: ${data.pb?.toFixed(2) ?? \"N\/A\"}`,\n          ].join(\"\\n\")\n        }]\n      };\n    } catch (error) {\n      return {\n        content: [{ type: \"text\", text: `Không tìm thấy mã cổ phiếu \"${symbol}\". Kiểm tra lại mã và thử lại.` }],\n        isError: true\n      };\n    }\n  }\n);\n\n\/\/ Tool 3: Top cổ phiếu tăng\/giảm\nserver.tool(\n  \"get_top_movers\",\n  \"Lấy danh sách top cổ phiếu tăng\/giảm mạnh nhất phiên\",\n  {\n    type: z.enum([\"gainers\", \"losers\"]).describe(\"Loại: gainers (tăng) hoặc losers (giảm)\"),\n    exchange: z.enum([\"HOSE\", \"HNX\", \"UPCOM\"]).default(\"HOSE\")\n      .describe(\"Sàn giao dịch\"),\n    limit: z.number().int().min(5).max(20).default(10)\n      .describe(\"Số lượng kết quả\")\n  },\n  async ({ type, exchange, limit }) =\u0026gt; {\n    try {\n      const data = await fetchWithRetry(\n        `https:\/\/api.example.com\/market\/top\/${type}?exchange=${exchange}\u0026amp;limit=${limit}`\n      );\n\n      const label = type === \"gainers\" ? \"🟢 TOP TĂNG\" : \"🔴 TOP GIẢM\";\n      const lines = data.stocks.map((s: any, i: number) =\u0026gt;\n        `${i + 1}. ${s.symbol.padEnd(5)} ${s.price.toLocaleString(\"vi-VN\").padStart(8)} (${s.changePct \u0026gt; 0 ? \"+\" : \"\"}${s.changePct.toFixed(2)}%) KL: ${(s.volume \/ 1e3).toFixed(0)}K`\n      );\n\n      return {\n        content: [{\n          type: \"text\",\n          text: `${label} — Sàn ${exchange}\\n${lines.join(\"\\n\")}`\n        }]\n      };\n    } catch (error) {\n      return {\n        content: [{ type: \"text\", text: \"Không thể lấy dữ liệu top cổ phiếu. Vui lòng thử lại.\" }],\n        isError: true\n      };\n    }\n  }\n);\n\n\/\/ Resource: Danh sách mã cổ phiếu theo sàn\nserver.resource(\n  \"stock-list\",\n  \"stocks:\/\/list\/{exchange}\",\n  { description: \"Danh sách tất cả mã cổ phiếu trên một sàn giao dịch\" },\n  async (uri, { exchange }) =\u0026gt; {\n    const data = await fetchWithRetry(\n      `https:\/\/api.example.com\/stock\/list?exchange=${exchange}`\n    );\n    return {\n      contents: [{\n        uri: uri.href,\n        mimeType: \"application\/json\",\n        text: JSON.stringify(data.symbols, null, 2)\n      }]\n    };\n  }\n);\n\n\/\/ Khởi động server\nasync function main() {\n  const transport = new StdioServerTransport();\n  await server.connect(transport);\n  console.error(\"vnstock-mcp server đang chạy\");\n}\n\nmain().catch(console.error);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eĐăng ký vnstock-mcp với Claude Code\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e# Build\ncd vnstock-mcp \u0026amp;\u0026amp; npm run build\n\n# Đăng ký\nclaude mcp add vnstock node \/path\/to\/vnstock-mcp\/dist\/index.js\n\n# Sử dụng trong Claude Code\n# \"VN-Index hôm nay thế nào?\"\n# \"Cho tôi xem thông tin cổ phiếu FPT\"\n# \"Top 10 cổ phiếu tăng mạnh nhất sàn HOSE hôm nay\"\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eMở rộng thêm\u003c\/h3\u003e\n\u003cp\u003eBạn có thể bổ sung nhiều tính năng hữu ích khác cho server chứng khoán này:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eDữ liệu lịch sử:\u003c\/strong\u003e Tool lấy biểu đồ giá theo ngày\/tuần\/tháng, tính SMA, EMA, RSI\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003ePhân tích tài chính:\u003c\/strong\u003e Resource trả về báo cáo tài chính (doanh thu, lợi nhuận, EPS) từ các quý gần nhất\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eCảnh báo giá:\u003c\/strong\u003e Tool đăng ký thông báo khi cổ phiếu chạm ngưỡng giá nhất định\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eSo sánh cổ phiếu:\u003c\/strong\u003e Tool so sánh 2-3 mã cổ phiếu theo nhiều chỉ tiêu cùng lúc\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eTin tức thị trường:\u003c\/strong\u003e Resource tổng hợp tin tức mới nhất liên quan đến mã cổ phiếu\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch2\u003eTổng kết và bước tiếp theo\u003c\/h2\u003e\n\u003cp\u003eBạn đã hoàn thành xây dựng MCP Server đầu tiên với TypeScript. Hãy tóm tắt những gì đã học:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eKiến trúc MCP:\u003c\/strong\u003e Giao thức client-server chuẩn hóa, giao tiếp qua stdio hoặc HTTP SSE\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eTools vs Resources:\u003c\/strong\u003e Tools cho hành động (có side effect), resources cho đọc dữ liệu (read-only)\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eValidation:\u003c\/strong\u003e Sử dụng Zod cho type-safe input validation, kết hợp business logic validation\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eError handling:\u003c\/strong\u003e Log vào stderr\/file, trả về isError cho AI xử lý graceful\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eTriển khai:\u003c\/strong\u003e Docker cho production, npm publish cho distribution, .mcp.json cho team\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003cp\u003eMCP đang phát triển nhanh chóng với hệ sinh thái ngày càng phong phú. Ngoài tools và resources, bạn có thể khám phá thêm prompts (template tái sử dụng) và sampling (cho phép server yêu cầu AI xử lý trung gian). Xem thêm tài liệu chính thức tại \u003ca href=\"https:\/\/modelcontextprotocol.io\" target=\"_blank\" rel=\"noopener\"\u003emodelcontextprotocol.io\u003c\/a\u003e và danh sách MCP Server cộng đồng tại \u003ca href=\"https:\/\/github.com\/modelcontextprotocol\/servers\" target=\"_blank\" rel=\"noopener\"\u003eGitHub\u003c\/a\u003e.\u003c\/p\u003e\n\n\u003cp\u003eHãy bắt đầu bằng một server đơn giản giải quyết vấn đề thực tế của bạn, sau đó mở rộng dần. Khám phá thêm các hướng dẫn phát triển với Claude tại \u003ca href=\"\/en\/collections\/ung-dung\"\u003eThư viện Ứng dụng Claude\u003c\/a\u003e.\u003c\/p\u003e\n","brand":"Minh Tuấn","offers":[{"title":"Default Title","offer_id":47730161156308,"sku":null,"price":0.0,"currency_code":"VND","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0821\/0264\/9044\/files\/xay-d_ng-mcp-server-d_u-tien-v_i-typescript-h_ng-d_n-t_ng-b_c.jpg?v=1774718109","url":"https:\/\/claude.vn\/en\/products\/xay-d%e1%bb%b1ng-mcp-server-d%e1%ba%a7u-tien-v%e1%bb%9bi-typescript-h%c6%b0%e1%bb%9bng-d%e1%ba%abn-t%e1%bb%abng-b%c6%b0%e1%bb%9bc","provider":"CLAUDE.VN","version":"1.0","type":"link"}