MCP không có gì thần thánh: cuối cùng vẫn là API, input/output và business logic

Nhìn từ bên ngoài, MCP (Model Context Protocol) dễ khiến người ta nghĩ đây là một lớp hạ tầng đặc biệt cho AI agent: có server riêng, cơ chế execution riêng và cách giao tiếp hoàn toàn khác REST API.

Nhưng bóc từng lớp ra thì nó không bí ẩn đến vậy. MCP cung cấp một cách chuẩn để agent hoặc harness biết server có những function nào, input của chúng có dạng gì và gọi chúng ra sao. Database, business logic, authentication, payment—những phần quyết định hệ thống làm gì—vẫn nằm ở application như trước.

Nói chung tìm hiểu cho vui thôi. Cái này không practical, cái chuẩn này sẽ sớm oẳng.

MCP nằm ở đâu trong hệ thống?

Một hệ thống thông thường có thể trông như thế này:

1
2
3
4
5
6
7
Database

Business Logic

REST / GraphQL API

Client

Khi thêm MCP, ta có thêm một adapter dành cho agent:

1
2
3
4
5
6
7
8
9
Database

Business Logic

MCP Adapter

MCP Client / Agent Harness

LLM

MCP không thay database, business logic, authentication hay HTTP. Nó thêm một interface có format chuẩn để các MCP client biết cách khám phá và sử dụng capability của server.

Ví dụ: Rails server bán quyền đọc blog

Giả sử một Rails application có các bài viết premium. Agent cần ba tool:

1
2
3
search_articles
get_article
purchase_article

Service layer phía Rails vẫn là code application bình thường:

1
2
3
4
5
6
7
8
9
class ArticlesService
def self.search(query)
Article.where("title ILIKE ?", "%#{query}%")
end

def self.get(id, user)
# check permission
end
end

Phần thanh toán cũng không liên quan đến MCP:

1
2
3
4
5
6
class ArticlePurchaseService
def self.purchase(user:, article:)
# charge wallet / Stripe / stablecoin / whatever
# create ArticlePurchase
end
end

MCP chỉ đứng ở phía trước các service này và chuyển request vào đúng chỗ.

Có cần cài MCP gem không?

Không bắt buộc. Ruby SDK chủ yếu giúp viết sẵn các phần lặp lại của adapter:

  • nhận MCP request;
  • parse JSON;
  • validate schema;
  • route request;
  • format response;
  • xử lý lỗi protocol.

Nếu muốn tự kiểm soát mọi thứ, một Rails app có thể bắt đầu bằng một route:

1
2
# routes.rb
post "/mcp", to: "mcp#handle"

Controller tối giản:

1
2
3
4
5
6
7
8
9
10
11
12
class McpController < ApplicationController
def handle
case params[:method]
when "tools/list"
list_tools
when "tools/call"
call_tool
else
render_mcp_error
end
end
end

SDK giúp giảm boilerplate và tránh tự xử lý sai protocol. Nhưng nó không làm business logic thay cho application.

/mcp vẫn chỉ là một endpoint

Một request có thể trông như sau:

1
POST /mcp
1
2
3
4
5
6
{
"jsonrpc": "2.0",
"id": "abc-123",
"method": "tools/list",
"params": {}
}

id là request ID của JSON-RPC. Nó không phải database ID và cũng không phải idempotency key. Client có thể dùng số tuần tự hoặc UUID; server chỉ cần trả lại đúng giá trị đó trong response.

Không cần tạo riêng các route kiểu:

1
2
3
/mcp/tools/search
/mcp/articles
/mcp/purchase

Trong ví dụ này, methodparams.name làm nhiệm vụ routing bên trong một endpoint.

tools/list là phần đáng giá nhất

Server mô tả các tool của mình cho client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
"jsonrpc": "2.0",
"id": "abc-123",
"result": {
"tools": [
{
"name": "search_articles",
"description": "Search blog articles",
"inputSchema": {
"type": "object",
"properties": {
"query": {
"type": "string"
}
},
"required": ["query"]
}
},
{
"name": "get_article",
"description": "Get an article",
"inputSchema": {
"type": "object",
"properties": {
"article_id": {
"type": "string"
}
},
"required": ["article_id"]
}
},
{
"name": "purchase_article",
"description": "Purchase access to an article",
"inputSchema": {
"type": "object",
"properties": {
"article_id": {
"type": "string"
}
},
"required": ["article_id"]
}
}
]
}
}

Client lấy metadata này rồi đưa tool definitions vào context của model. Khi đó model biết server có thể tìm bài, lấy bài hoặc mua quyền đọc bài. Nếu không có bước này, model không tự biết API nào đang tồn tại.

MCP client layer trong coding harness

Nếu coding harness như Pi chưa hỗ trợ MCP native, chỉ cần một extension mỏng làm bridge:

1
2
3
4
5
6
7
MCP Server

MCP Client Extension

Pi Harness

LLM

Extension khởi động bằng cách gọi tools/list, sau đó đăng ký các tool với Pi—mỗi MCP tool là một tool riêng, hoặc gói tất cả vào một generic tool như mcp(name, arguments).

Khi model yêu cầu gọi tool, extension chỉ convert request thành tools/call, gửi POST /mcp, parse response rồi trả result về cho Pi. Pi đưa result vào context và để model quyết định bước tiếp theo: trả lời, gọi tool khác hoặc kết thúc.

1
2
Pi extension: call API → parse response → return result
LLM: chọn tool → quyết định gọi tiếp hay dừng

Client layer không cần intelligence. Nó chỉ là bridge giữa harness và MCP server—đồng thời là nơi dễ làm tool list phình vào context nếu đăng ký quá nhiều tool.

Model chọn tool, harness thực thi

Nếu user hỏi:

1
Find me articles about Bitcoin monetary policy.

Model có thể yêu cầu gọi search_articles. Model không tự gửi HTTP; harness nhận tool call và gửi request đến MCP server:

1
2
3
4
5
6
7
8
9
10
11
{
"jsonrpc": "2.0",
"id": "req-456",
"method": "tools/call",
"params": {
"name": "search_articles",
"arguments": {
"query": "Bitcoin monetary policy"
}
}
}

Rails nhận request rồi gọi service tương ứng:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def call_tool
name = params.dig(:params, :name)
arguments = params.dig(:params, :arguments)

result =
case name
when "search_articles"
ArticlesService.search(arguments["query"])

when "get_article"
ArticlesService.get(
arguments["article_id"],
current_agent
)

when "purchase_article"
ArticlePurchaseService.purchase(
user: current_agent,
article: Article.find(arguments["article_id"])
)
end

render_mcp_result(result)
end

Từ đây trở đi, việc tìm bài, kiểm tra quyền đọc hay charge tiền đều là việc của application.

Response chỉ là data có thêm envelope

Một response cho get_article có thể chứa dữ liệu như sau:

1
2
3
4
5
6
7
8
9
10
11
{
"jsonrpc": "2.0",
"id": "req-456",
"result": {
"structuredContent": {
"id": "123",
"title": "Bitcoin and Monetary Policy",
"content": "..."
}
}
}

MCP chuẩn hóa envelope và một số field như jsonrpc, id, result, error, content hay structuredContent. Nó không thể chuẩn hóa domain của từng ứng dụng.

Blog có thể trả:

1
2
3
4
5
{
"article_id": "123",
"title": "...",
"content": "..."
}

Polymarket có thể trả:

1
2
3
4
{
"market_id": "...",
"probability": 0.62
}

GitHub có thể trả:

1
2
3
4
{
"pull_request": 123,
"status": "open"
}

Schema không khiến model khỏi phải suy luận. Nó cho model biết capability nhận input gì và dữ liệu trả về có hình dạng nào trước khi gọi.

MCP khác REST API ở điểm nào?

REST có thể expose những endpoint rất rõ ràng:

1
2
3
GET /articles
GET /articles/:id
POST /articles/:id/purchase

Vấn đề là agent harness không mặc nhiên biết các endpoint đó tồn tại. Ta phải viết plugin, dùng OpenAPI adapter, viết custom tool hoặc hard-code API definition vào harness.

MCP đưa capability discovery vào protocol thông qua tools/list. Client có thể hỏi server đang có tool gì, lấy schema của chúng rồi expose cho model. Sau đó tools/call cung cấp một convention chung để gọi tool.

Đó là giá trị chính của MCP: capability discovery và standardized tool calling. Nó không biến REST API thành thứ khác; nhiều MCP server thực chất vẫn chỉ là lớp chuyển đổi từ API hiện có:

1
REST API → MCP tools

Ví dụ một Polymarket MCP server không tạo ra Polymarket mới. Nó chỉ map các API như:

1
2
3
GET /markets
GET /events
POST /orders

thành những tool mà Claude, Cursor, Pi hoặc agent khác có thể khám phá và gọi. Nếu Polymarket có official MCP server đủ tốt, adapter bên ngoài sẽ chỉ còn lý do tồn tại khi cung cấp thêm capability riêng.

Capability discovery cũng có cái giá của nó

tools/list nghe tiện, nhưng mỗi tool đi kèm name, description và schema. Tất cả đống metadata này thường được đưa vào context của model. Có vài tool thì không sao. Có vài chục hoặc vài trăm tool, context bắt đầu phình ra và model phải chọn trong một danh sách dài những thứ có thể chẳng liên quan đến request hiện tại.

Đó là chưa kể metadata bị gửi lại qua nhiều lượt gọi, description viết không nhất quán hoặc hai tool có tên gần giống nhau. Khi ấy MCP không còn giúp model “biết mình có gì” một cách gọn gàng nữa; nó biến context thành một catalog API khá dài.

Với một hệ thống chỉ có vài endpoint ổn định, cách đơn giản hơn có thể là đưa API document hoặc OpenAPI spec làm source of truth, rồi dùng một HTTP client/tool để gọi endpoint cần thiết. Không có capability discovery đẹp bằng MCP, nhưng dễ debug hơn, ít metadata hơn và không phải expose toàn bộ tool list trong mọi request.

MCP vẫn hợp lý khi cần interoperability giữa nhiều client, tool có schema rõ ràng hoặc capability thay đổi động. Nhưng đó không phải lý do để mặc định bọc mọi REST API bằng MCP.

Authentication và payment vẫn là việc của application

Không thể mở endpoint rồi cho bất kỳ agent nào mua bài bằng tài khoản của người khác. Remote MCP vẫn có thể dùng Bearer token như các API khác:

1
2
POST /mcp
Authorization: Bearer eyJ...

Rails vẫn authenticate request rồi kiểm tra token, user, scope, permission, balance và purchase history. Chẳng hạn:

1
2
3
articles:search
articles:read
articles:purchase

MCP không thay authentication system. Nó chỉ tạo ra một convention để client và server interoperable.

Với purchase_article, flow có thể là:

1
2
3
4
5
6
7
8
9
Check user

Check existing purchase

Charge wallet

Create ArticlePurchase

Grant access

Payment rail có thể là Stripe, credits, stablecoin, x402 hoặc internal wallet. MCP không cần biết.

purchase_article có side effect, application vẫn nên tự xử lý idempotency. JSON-RPC id chỉ dùng để ghép request với response, không đảm bảo một operation chỉ chạy một lần.

Kết luận

Một MCP server tối giản cho Rails vẫn gồm những phần quen thuộc:

  1. Internal logic: database, CRUD và service layer.
  2. Public interface: HTTP endpoint.
  3. Authentication và authorization.
  4. Input/output: schema, validation và serialization.
  5. Routing: map tool name vào application logic.

MCP bổ sung cách chuẩn để:

  • khám phá capability;
  • mô tả input;
  • gọi tool;
  • trả kết quả về client.

Nhưng capability discovery không miễn phí. Tool list càng lớn thì context càng phình, và lợi ích của convention có thể không bù được chi phí token, độ nhiễu và công sức debug.

Với server bán bài viết, một endpoint và service layer hiện tại là đủ để thử nghiệm. Còn có nên thêm MCP hay không thì phải nhìn vào use case thật: cần nhiều client khác nhau và discovery động thì có thể đáng; chỉ cần nối một agent vào vài API nội bộ thì API document cùng một HTTP tool thường thực dụng hơn. Không phải project nào cũng cần thêm một adapter chỉ để chứng minh rằng nó có thể thêm adapter.

Sau khi tìm hiểu cho biết, tôi không thấy MCP là thứ cần phải nhét vào mọi hệ thống AI. Nó là một convention hữu ích trong một số trường hợp, nhưng cũng có thể nhanh chóng trở thành lớp bloat nằm giữa model và API. Nếu phần lớn use case chỉ là đưa một đống endpoint vào context rồi chờ model chọn, một API document tốt và cách gọi trực tiếp có khi đã đủ.

REST hoặc API vẫn làm công việc thực tế. MCP chỉ là một cách khác để agent biết API nào tồn tại—và đôi khi là một cách hơi đắt để làm việc đó.