CodeGym /Các khóa học /ChatGPT Apps /Định dạng thông điệp MCP: requests, replies, notification...

Định dạng thông điệp MCP: requests, replies, notifications, tools/resources/prompts

ChatGPT Apps
Mức độ , Bài học
Có sẵn

1. MCP và JSON‑RPC: nền tảng “khô khan” bạn chỉ cần hiểu một lần

Ở bài trước, chúng ta đã nói MCP để làm gì và nó nằm ở đâu trong ngăn xếp Apps SDK. Ở bài này, ta thu hẹp trọng tâm vào lớp “khô khan” nhất — định dạng thông điệp MCP — để bạn có thể tự tin đọc log JSON thô và hiểu chính xác ChatGPT gửi gì tới server của bạn và server phản hồi thế nào.

MCP dùng JSON‑RPC 2.0 làm kênh vận chuyển dữ liệu: mọi request, reply và notification đều là các đối tượng JSON thông thường với sơ đồ có thể dự đoán.

Tức là, thay vì “mỗi dịch vụ tự chế một định dạng”, ta có một hợp đồng cơ bản:

  • một request có trường bắt buộc jsonrpc (thường là "2.0"), id duy nhất, tên phương thức dạng chuỗi method và đối tượng params chứa tham số;
  • reply gắn với request bằng id và chứa hoặc result, hoặc error;
  • notification trông giống request nhưng không có id, và sẽ không có reply.

Trông nó sẽ như thế này:


{
  "jsonrpc": "2.0",
  "id": 42,
  "method": "tools/list",
  "params": {
    "cursor": null
  }
}

Đây là request. Còn response khi thành công:

{
  "jsonrpc": "2.0",
  "id": 42,
  "result": {
    "tools": [],
    "nextCursor": null
  }
}

Nếu bạn nghĩ “đây chẳng phải RPC thông thường sao”, thì đúng vậy. MCP chỉ cố định những phương thức nào tồn tại (tools/list, tools/call, resources/list, prompts/list, …) và định dạng tham số/giá trị trả về của chúng.

Quan trọng là cảm nhận: JSON‑RPC là khung “request–reply–notification”. MCP là “cụ thể có những request nào và bên trong có gì”.

2. Request: MCP yêu cầu làm gì đó như thế nào

Bắt đầu với request. Luôn đi theo hướng “ai đó muốn làm điều gì”. Thông thường là client → server (ChatGPT → MCP‑server của bạn), nhưng MCP cũng cho phép request ngược lại, khi server yêu cầu client thực hiện sampling hoặc elicitation. Trong bài này, ta quan tâm chủ yếu đến trường hợp cổ điển: client yêu cầu server.

Mỗi MCP‑request có ba trường chính:

  1. jsonrpc — phiên bản giao thức JSON‑RPC, thường là "2.0".
  2. id — định danh request; có thể là bất kỳ kiểu JSON nào, nhưng thực tế thường là số hoặc chuỗi. Quan trọng là các id phải duy nhất trong số request đang hoạt động.
  3. method — chuỗi dạng "tools/list" hoặc "tools/call". MCP đặc tả tập phương thức hợp lệ.

Và có đối tượng params, nơi chứa tham số của phương thức cụ thể.

Ví dụ: yêu cầu danh sách công cụ

Giả sử ChatGPT vừa kết nối tới MCP‑server của bạn và muốn biết có những tools nào có thể gọi. Nó sẽ gửi request kiểu như sau:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {
    "cursor": null
  }
}

Trường cursor dùng cho phân trang — nếu có nhiều công cụ, server có thể trả từng phần.

Với ứng dụng học tập của chúng ta (gợi ý quà tặng) thì lúc này khá đơn giản: một hai công cụ, nhưng giao thức vẫn như vậy. Trước mắt cứ coi đây là ví dụ trực quan; các cấu trúc chính thức ta sẽ xem kỹ hơn ở phần tools bên dưới.

Ví dụ: gọi công cụ (tools/call)

Giờ thú vị hơn chút. Giả sử ta đã có MCP‑tool suggest_gifts, bạn sẽ hiện thực trong bài về MCP‑server. Nó mong đợi các tham số:

  • occasion — dịp (Birthday, Wedding, …),
  • budget — số tiền tính bằng đô la,
  • recipient — chuỗi mô tả người nhận quà.

ChatGPT, khi quyết định dùng công cụ này, sẽ tạo MCP‑request:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "suggest_gifts",
    "arguments": {
      "occasion": "birthday",
      "budget": 100,
      "recipient": "friend who loves board games"
    }
  }
}

Lưu ý vài chi tiết.

Thứ nhất, tên công cụ lấy từ những gì bạn khai báo ở phía server (server.registerTool("suggest_gifts", …)). Thứ hai, đối tượng arguments phải tuân theo JSON Schema mà bạn đính kèm trong mô tả công cụ.

Nếu GPT cố gửi tham số sai schema (ví dụ, budget: "một trăm đô"), server có quyền trả lỗi ở tầng giao thức hoặc tầng nghiệp vụ tùy cách hiện thực. Trước mắt, hãy nắm được hình dạng tổng quát của request; ở phần tools phía dưới, ta sẽ nhìn những thông điệp này một cách hệ thống hơn.

Requests cho resources và prompts

Các request tới resources và prompts cũng tương tự. Đặc tả MCP quy định các phương thức:

  • resources/list — liệt kê các resource có sẵn;
  • resources/read (hoặc resources/get) — đọc một resource cụ thể theo URI;
  • prompts/list — lấy danh sách prompt khả dụng;
  • prompts/get — lấy nội dung của một prompt cụ thể.

Ví dụ request đọc resource chứa danh mục quà tặng:

{
  "jsonrpc": "2.0",
  "id": 15,
  "method": "resources/read",
  "params": {
    "uri": "mcp://gift-server/resources/gift_catalog"
  }
}

Trước mắt, hãy ghi nhớ hai điều. Thứ nhất, mỗi nguyên thủy đều có cặp phương thức */list*/get/*/read. Thứ hai, tên phương thức luôn nằm trong trường chuỗi method, còn toàn bộ nội dung nằm trong đối tượng params.

3. Reply: MCP trả lời như thế nào — resulterror

Reply luôn gắn với request bằng trường id. Nó giống correlationId trong nhiều hệ thống phân tán: bạn nhìn log và thấy request có id=7 nhận reply với id=7, tức là một cặp.

JSON‑RPC đặt ra quy tắc đơn giản: trong reply hoặcresult, hoặcerror, nhưng không phải cả hai. Trên đó, MCP làm rõ cấu trúc result cho các phương thức khác nhau (tools/list, tools/call v.v.) và khuyến nghị mã lỗi.

Reply thành công (result)

Xem ví dụ reply thành công cho tools/call của suggest_gifts. Server xử lý xong, tìm được quà phù hợp và trả danh sách trong trường result:

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Here are some gift ideas for your friend..."
      }
    ],
    "structuredContent": {
      "gifts": [
        { "name": "Board game: Catan", "price": 45 },
        { "name": "Dice set", "price": 20 }
      ]
    },
    "isError": false
  }
}

Có một vài điểm quan trọng.

  • Thứ nhất, contentstructuredContent chính là các phần của reply từ MCP‑tools mà bạn đã thấy trong Apps SDK. Mô hình dùng văn bản từ content, còn widget của bạn render dữ liệu từ structuredContent.
  • Thứ hai, cờ isError liên quan đến kết quả nghiệp vụ. Ở tầng giao thức, mọi thứ đều ổn: JSON hợp lệ, phương thức tồn tại, tham số đã phân tích. Nhưng nghiệp vụ có thể nói: “không tìm thấy ý tưởng quà tặng nào, xét về UX coi là lỗi”. Khi đó, bạn đặt isError: true và mô tả vấn đề trong content.
  • Thứ ba, đặc tả MCP cho các phương thức khác nhau (tools/list, tools/call, */list, */get) mô tả chi tiết các trường cần có trong result. Ví dụ, với tools/list, server trả một mảng mô tả công cụ với tên, tiêu đề, mô tả và JSON Schema của tham số đầu vào.

Reply lỗi (error)

Nếu có vấn đề ở tầng giao thức hoặc server, thay vì result sẽ trả về đối tượng error. Thông thường có:

  • code — mã lỗi dạng số;
  • message — mô tả dễ đọc;
  • data — dữ liệu bổ sung tùy chọn (stack trace, chi tiết, …).

Ví dụ: mô hình gọi phương thức không tồn tại:

{
  "jsonrpc": "2.0",
  "id": 99,
  "error": {
    "code": -32601,
    "message": "Method not found: tools/col"
  }
}

-32601 — là “method not found” kinh điển của JSON‑RPC.

Có một ranh giới tinh tế nhưng quan trọng giữa hai loại lỗi.

Lỗi giao thức — khi vi phạm quy tắc MCP/JSON‑RPC: phương thức không biết, kiểu sai trong trường params, JSON không hợp lệ. Khi đó nên trả error ở tầng trên cùng.

Lỗi nghiệp vụ — khi giao thức hợp lệ, nhưng thao tác không thành công do nguyên nhân miền nghiệp vụ: danh mục trống, không có quyền với resource, mã định danh nghiệp vụ sai. Khi đó MCP thường khuyến nghị trả về result hợp lệ, nhưng đánh dấu isError: true và mô tả vấn đề trong nội dung.

Sự phân tách này giúp ChatGPT và công cụ gỡ lỗi rất nhiều: nhìn log là thấy ngay đây là hỏng kỹ thuật hay từ chối có chủ đích của nghiệp vụ.

4. Notifications: thông điệp một chiều

Notification là “thư không chờ hồi âm”. Trong JSON‑RPC, notification trông như request bình thường nhưng không có id. Client không nên gửi reply cho chúng.

Trong MCP, notification dùng cho các sự kiện: thay đổi danh sách tools/resources/prompts, tiến độ tác vụ dài, log, v.v.

Ví dụ đơn giản bạn chắc chắn sẽ gặp — notification cho biết danh sách công cụ đã thay đổi. Đặc tả MCP cho tools mô tả capability listChanged và notification tools/list_changed mà server gửi khi tập công cụ khả dụng thay đổi.

Notification có thể như sau:

{
  "jsonrpc": "2.0",
  "method": "tools/list_changed",
  "params": {
    "reason": "New tool 'suggest_gift_cards' was added"
  }
}

Không cần reply cho nó. Khi nhận được notification này, client có thể quyết định: “à, cần gọi lại tools/list để làm mới cache công cụ”.

Các notification điển hình khác của MCP (chúng ta sẽ nói kỹ trong mô-đun về luồng và sự kiện):

  • sự kiện tiến độ (notifications/progress) cho tác vụ dài;
  • log của server (notifications/logging/message);
  • thay đổi resources (resources/list_changed) và prompts (prompts/list_changed).

Tạm thời điều quan trọng là: notification = request không có id và không có reply. Nếu bạn thấy trong log một JSON không có id, rất có thể đó là notification.

Insight

Thực nghiệm cho thấy ChatGPT App bỏ qua các thông điệp gửi tới nó (MCP‑notification). Tuy nhiên, vì ChatGPT Apps mới ở giai đoạn khởi đầu, khả năng hỗ trợ đầy đủ mọi mặt của giao thức MCP trong tương lai gần là rất lớn. Vì vậy mình khuyên bạn vẫn nên học phần này của giao thức MCP.

5. Tools/resources/prompts trông thế nào trong thông điệp

Giờ đến phần thú vị: chính xác bên trong thông điệp MCP mô tả các tools, resources và prompts — những thứ ta nhắc mãi — như thế nào.

Tools: mô tả và gọi

Ở cấp giao thức, tools có hai quy trình chính:

  1. discovery — client biết có những công cụ nào;
  2. invocation — client gọi một công cụ cụ thể.

Ta đã lướt qua tools/listtools/call ở trên. Giờ xem hệ thống hơn: chúng bao trùm quy trình nào và result chứa gì.

5.1.1. Danh sách công cụ — tools/list

Ta đã thấy request cho tools/list. Hãy xem cấu trúc reply. Đặc tả MCP nói: trong result.tools phải trả về một mảng đối tượng, mỗi đối tượng mô tả một công cụ. Công cụ bắt buộc có:

  • name — tên duy nhất, dùng để gọi tools/call sau này;
  • title — tiêu đề ngắn (cả người dùng và mô hình đều thấy);
  • description — mô tả chi tiết hơn về tool, như thể bạn giải thích cho đồng nghiệp;
  • inputSchema — JSON Schema cho tham số đầu vào của công cụ.

Với suggest_gifts của chúng ta, reply cho tools/list có thể trông như sau (đã giản lược):

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "suggest_gifts",
        "title": "Gift ideas generator",
        "description": "Suggests gift ideas for a given occasion and budget.",
        "inputSchema": {
          "type": "object",
          "properties": {
            "occasion": { "type": "string" },
            "budget": { "type": "number" },
            "recipient": { "type": "string" }
          },
          "required": ["occasion", "budget"]
        }
      }
    ],
    "nextCursor": null
  }
}

Nếu bạn trong Apps SDK đã từng viết inputSchema khi đăng ký công cụ, thì bạn đã gần như thấy đối tượng này rồi — chỉ là “từ trên xuống” dưới dạng đối tượng TypeScript. MCP chỉ cần chuyển nó qua giao thức cho client.

5.1.2. Gọi công cụ — tools/call

Chúng ta đã đụng qua định dạng gọi. Đặc tả MCP mô tả rằng params cần chứa:

  • name — tên công cụ;
  • arguments — đối tượng tuân theo inputSchema.

Ví dụ:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "suggest_gifts",
    "arguments": {
      "occasion": "wedding",
      "budget": 150,
      "recipient": "coworker from marketing"
    }
  }
}

Và reply từ server trả về result với content, structuredContent và, tùy chọn, _meta (ví dụ, chỉ định openai/outputTemplate nếu bạn muốn ghép tool này với một widget cụ thể).

Cặp tools/listtools/call là vòng đời cơ bản của MCP‑tools: trước là discovery, sau là sử dụng.

Resources: dữ liệu có địa chỉ

Resources trong MCP là mọi mảnh dữ liệu mà client có thể truy cập qua URI: tệp, bản ghi DB, config, danh mục, v.v.

Chúng có bộ thao tác chuẩn:

  • resources/list — để biết có những resource nào;
  • resources/read — để đọc một resource cụ thể (hoặc một phần của nó).

Giả sử có resource gift_catalog, mô tả danh mục quà tặng cơ bản: danh mục, thương hiệu, giá tối thiểu và tối đa. Server có thể công bố nó với URI "mcp://gift-server/resources/gift_catalog".

Reply cho resources/list có thể như sau (giản lược):

{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "resources": [
      {
        "uri": "mcp://gift-server/resources/gift_catalog",	//chuỗi duy nhất đơn thuần. mcp-khong phai giao thuc.
        "name": "gift_catalog",
        "description": "Base catalog of gifts with categories and prices",
        "mimeType": "application/json"
      }
    ],
    "nextCursor": null
  }
}

Và đọc resource — resources/read:

{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "resources/read",
  "params": {
    "uri": "mcp://gift-server/resources/gift_catalog"
  }
}

Reply có thể chứa chính nội dung và metadata:

{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "contents": [
      {
        "uri": "mcp://gift-server/resources/gift_catalog",
        "mimeType": "application/json",
        "text": "{\"categories\":[\"boardgames\",\"books\"]}"
      }
    ]
  }
}

Ý tưởng chính: resource là dữ liệu có thể định địa chỉ, còn tools là các thao tác. MCP làm cả hai trở nên tường minh trong giao thức.

Prompts: mẫu có thể tái sử dụng

Prompts là “các gợi ý được chuẩn bị sẵn” hoặc template mà server có thể cung cấp cho client. MCP xem chúng như một nguyên thủy có:

  • tên;
  • tiêu đề/mô tả dễ đọc;
  • nội dung (thường là template system‑prompt hoặc bộ ví dụ few‑shot).

Và, như dự đoán, có hai phương thức:

  • prompts/list — biết có những prompt nào;
  • prompts/get — lấy nội dung của một prompt.

Ví dụ, bạn muốn đặt một phong cách riêng để tạo lời chúc mừng đi cùng quà. Khi đó trong MCP‑server có thể khai báo prompt gift_congrats_style.

Reply cho prompts/list có thể như sau:

{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "prompts": [
      {
        "name": "gift_congrats_style",
        "description": "Style guide for birthday congratulations in a friendly tone"
      }
    ]
  }
}

Còn prompts/get sẽ trả chính văn bản (hoặc nội dung có cấu trúc), mà client có thể đưa tiếp vào LLM như một phần system‑prompt. Ví dụ request và reply:

{
  "jsonrpc": "2.0",
  "id": 11,
  "method": "prompts/get",
  "params": {
    "name": "gift_congrats_style"
  }
}
{
  "jsonrpc": "2.0",
  "id": 11,
  "result": {
    "prompt": {
      "name": "gift_congrats_style",
      "messages": [
        {
          "role": "system",
          "content": [
            {
              "type": "text",
              "text": "You are a friendly assistant that writes short, warm birthday congratulations..."
            }
          ]
        }
      ]
    }
  }
}

6. Điều này liên quan gì tới Apps SDK và widget của chúng ta

Hiện tại MCP‑JSON có thể vẫn trông hơi cồng kềnh. Hãy liên hệ nó với những gì bạn đã làm qua Apps SDK.

Nhắc lại, ở frontend của widget bạn có thể có đoạn mã:

// bên trong component React trong sandbox của ChatGPT
async function fetchGifts() {
  const result = await window.openai.callTool("suggest_gifts", {
    occasion: "birthday",
    budget: 50,
    recipient: "friend who loves sci-fi"
  });

  console.log(result);
}

Ở tầng Apps SDK, đó là một hàm tiện lợi, nó:

  1. biết URL của MCP‑server (từ cấu hình ứng dụng);
  2. có thể tìm mô tả công cụ theo tên suggest_gifts;
  3. đóng gói lời gọi của bạn thành MCP‑request tools/call;
  4. gửi nó qua kênh đã chọn (HTTP/SSE);
  5. chờ MCP‑reply, giải nén result và trả lại cho bạn dưới dạng result trong JavaScript.

Nếu vẽ thành sơ đồ, sẽ như sau:

sequenceDiagram
    participant Widget
    participant AppsSDK as Apps SDK
    participant MCP as MCP-server

    Widget->>AppsSDK: window.openai.callTool("suggest_gifts", {...})
    AppsSDK->>MCP: JSON { id:7, method:"tools/call", params:{...} }
    MCP-->>AppsSDK: JSON { id:7, result:{ content, structuredContent } }
    AppsSDK-->>Widget: result (ToolOutput)
    Widget->>Widget: setState(toolOutput)

Hiểu định dạng MCP mang lại cho bạn hai kỹ năng tuyệt vời.

Thứ nhất, bạn có thể xem log MCP thô (ví dụ trong MCP Inspector, chúng ta sẽ có bài riêng) và thấy: chính xác tools/call nào đã được gửi, trong đó có tham số gì, và result hay error trả về như thế nào.

Thứ hai, khi thiết kế công cụ và resource, bạn có thể nghĩ không chỉ theo kiểu type của TypeScript, mà còn theo kiểu schema của MCP: nó sẽ trông ra sao trong JSON, có tiện cho các client khác (ví dụ các agent cũng có thể kết nối vào MCP‑server của bạn) hay không.

7. Mini‑practice: đọc và “sửa” MCP‑JSON

Để định dạng MCP trở nên quen thuộc, tốt nhất là tự tay phân tích một vài thông điệp. Lấy ví dụ một đoạn hội thoại đầy đủ tools/listtools/call → kết quả.

Client muốn danh sách công cụ

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list",
  "params": {}
}

Ta thấy gì:

  • đây là request (có id);
  • phương thức tools/list, tức là đang discovery công cụ;
  • tham số trống, không phân trang.

Server trả lời:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "suggest_gifts",
        "title": "Gift ideas generator",
        "description": "Suggests gift ideas",
        "inputSchema": { "type": "object", "properties": { "occasion": { "type": "string" } } }
      }
    ]
  }
}

Thấy ngay đây là reply cho đúng request đó (cùng id: 1), giao thức thành công (result có, error không), và giờ client biết có tool suggest_gifts.

Client gọi công cụ

Tiếp theo client gọi tools/call:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "suggest_gifts",
    "arguments": {
      "occasion": "anniversary"
    }
  }
}

Nếu server còn mong đợi budget nhưng mô hình không cung cấp, server có thể:

  • hoặc trả lỗi giao thức (ví dụ error với mã “invalid params”);
  • hoặc tự quyết định mặc định (ví dụ dùng mức ngân sách trung bình) và trả result bình thường.

Theo thuật ngữ đã nêu, phương án đầu là lỗi giao thức (error ở tầng trên), phương án sau là phần nghiệp vụ: bạn vẫn trả result hợp lệ và quyết định có coi đó là lỗi nghiệp vụ (isError: true) hay hành vi bình thường.

Reply khi lỗi tham số có thể như sau:

{
  "jsonrpc": "2.0",
  "id": 2,
  "error": {
    "code": -32602,
    "message": "Missing required property 'budget' in arguments"
  }
}

Một lần nữa, phân biệt với lỗi nghiệp vụ: giao thức bị vi phạm (tham số không đúng schema), vì vậy phù hợp là error.

Ví dụ hỏng: tìm bug

Đây là JSON mà người mới đôi khi tạo ra:

{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "tool": "suggest_gifts",
    "args": {
      "occasion": "birthday",
      "budget": 100
    }
  }
}

Thoạt nhìn có vẻ hợp lý, nhưng nếu so với đặc tả MCP, bạn sẽ thấy các trường toolargs không khớp với namearguments như mong đợi.

Client/server MCP‑SDK rất có thể sẽ không bao giờ sinh ra JSON như vậy, nhưng nếu bạn tích hợp thủ công mà không nắm rõ đặc tả, thì lỗi như thế là có thật. Đó là lý do trong khóa học này ta phân tích giao thức “trần trụi”, chứ không chỉ các wrapper của SDK.

8. Lỗi thường gặp khi làm việc với thông điệp MCP

Lỗi số 1: trộn lẫn lỗi giao thức và lỗi nghiệp vụ.
Nhiều lập trình viên theo thói quen gói mọi thứ “không như ý” vào error tầng trên — cả thiếu resource, tham số sai, lẫn DB sập. Trong bối cảnh MCP, hữu ích là tách bạch: nếu cấu trúc JSON và schema lời gọi bị vi phạm (sai phương thức, sai trường, sai kiểu), đó là lúc trả error. Nếu công cụ không thực hiện được thao tác miền nghiệp vụ (không có quà cho ngân sách đó, không tìm thấy người dùng), tốt hơn là trả result hợp lệ với isError: true và thông điệp rõ ràng trong content. Khi đó cả mô hình ChatGPT và công cụ gỡ lỗi đều phân biệt đúng “hỏng kênh liên lạc” và “server từ chối có chủ đích”.

Lỗi số 2: bỏ qua trường id và tương quan request.
Đôi khi trong log MCP‑server bạn sẽ thấy ghi tay mà không có id hoặc trùng id cho các request đang hoạt động khác nhau. Trong hello‑world đơn luồng thì còn chịu được, nhưng khi có lời gọi song song hoặc retry, sẽ khó hiểu reply nào thuộc request nào. JSON‑RPC đặc biệt yêu cầu id duy nhất trong vòng đời của request, và MCP dựa vào quy tắc này. Nếu dùng SDK chính thức, bạn không phải bận tâm về id, nhưng ngay khi tự viết transport hoặc logging, đừng quên lưu và in id — đó là thứ đầu tiên bạn bám vào khi debug lỗi lạ.

Lỗi số 3: cấu trúc result không ổn định cho cùng một phương thức.
Đôi khi ta “hơi hơi” đổi định dạng reply theo tình huống: lúc trả mảng quà, lúc trả đối tượng với một chuỗi, lúc chỉ text không có structuredContent. Mô hình có thể tạm chấp nhận, nhưng widget và các client MCP khác thì khó. Đặc tả MCP mô tả cấu trúc result dự đoán được cho mỗi phương thức; hãy bám theo. Nếu cần định dạng khác, tốt hơn công bố một tool khác hoặc phiên bản khác, thay vì đổi schema “tại trận”.

Lỗi số 4: thừa hoặc thiếu trường trong params.
Vấn đề điển hình của hiện thực tùy biến — thêm thứ MCP không mong đợi vào params hoặc quên trường bắt buộc. Ví dụ, gửi toolName thay vì name trong tools/call, hoặc resourceId thay vì uri trong resources/read. MCP‑SDK thường sẽ validate và ném ngoại lệ dễ hiểu, nhưng nếu bạn làm gần giao thức hơn, có thể mất nhiều thời gian mới hiểu “vì sao server không hiểu tôi”. Một mẹo hay — giữ một ví dụ JSON request đúng từ đặc tả hoặc log của client chạy chuẩn cạnh handler và so sánh với cái bạn gửi.

Lỗi số 5: cố dùng notifications như “kênh trả lời thứ hai”.
Đôi khi lập trình viên, thấy có notifications, bèn gửi kết quả thao tác qua notification thay vì reply thông thường: “chúng ta đã ở MCP và có SSE rồi, đẩy hết qua notification đi”. Vấn đề là JSON‑RPC‑notification theo định nghĩa không gắn với id cụ thể và không được client coi là reply cho request nào. Kết quả là khó gỡ lỗi và không thể biết thông điệp thuộc lời gọi công cụ nào. Notification rất hợp cho sự kiện (danh sách tools/resources/prompts đổi, có tiến độ mới, log đến), nhưng không dùng cho reply của tools/call và các phương thức tương tự.

Lỗi số 6: không xem MCP‑log và inspector.
Lỗi “rất con người” — cố gỡ tích hợp chỉ qua UI ChatGPT: “bấm nút, cái gì đó không đến, để khi khác xem”. Khi bạn chưa nhìn thấy thông điệp MCP thô (requests, replies, notifications), rất khó hiểu vấn đề nằm ở đâu: mô hình không gọi tool, Apps SDK không tới được MCP‑server, server trả sai JSON, hay hỏng ở bước render widget. MCP Inspector / Jam và logging có cấu trúc cho thông điệp MCP — là bạn đồng hành tốt nhất. Sau khi một lần bạn thấy tools/calltools/list sống trong log, định dạng thông điệp MCP sẽ không còn là “phép màu” mà trở thành thói quen kỹ sư bình thường.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION