Harnix

Xây dựng MCP server

Tiêu chuẩn, từ khoá và cách xử lý xác thực để một MCP server do bạn viết nối được vào Harnix như một connector.

Trang này dành cho đội phát triển muốn tự viết một MCP server (API nội bộ, hệ thống đơn hàng, CRM…) để agent trên Harnix gọi được qua Connector. Harnix là một MCP client từ xa: nó gọi server của bạn qua HTTPS, thay mặt tổ chức đã thêm connector.

Tóm tắt những gì server cần có:

Hạng mụcTiêu chuẩn
Giao thứcModel Context Protocol, dùng SDK chính thức (Python, TypeScript…)
TransportStreamable HTTP ở một URL duy nhất (ví dụ https://mcp.example.com/mcp); SSE cũ vẫn được hỗ trợ. Không hỗ trợ stdio
Mạnghttps, tên miền công khai, không chuyển hướng (redirect) URL MCP
Xác thựcMột trong bốn cách: không xác thực, header cố định, OAuth (authorization code + PKCE), OAuth client credentials
ToolTên snake_case, description rõ ràng, inputSchema là JSON Schema, khai readOnlyHint: true cho tool chỉ đọc
Kết quảTrả nội dung dạng text, tối đa 64 KB, dùng isError: true cho lỗi nghiệp vụ

Yêu cầu kết nối

  • URL. Một endpoint MCP, ví dụ https://mcp.example.com/mcp. URL phải là https, trỏ tới địa chỉ công khai; địa chỉ private/nội bộ (10.x, 192.168.x, localhost…) và URL chứa username/password bị từ chối. Harnix không đi theo redirect: trả 3xx cho URL MCP sẽ thành lỗi.
  • Transport. Ở chế độ Tự động, Harnix thử Streamable HTTP trước rồi mới tới SSE. Server mới nên dùng Streamable HTTP.
  • Thời gian. Bắt tay (initialize) và liệt kê tool (tools/list) phải xong trong 10 giây. Một lần gọi tool được phép chạy tối đa 120 giây.
  • Phiên bản giao thức. Harnix khởi tạo với protocolVersion 2025-06-18; SDK chính thức tự thương lượng phiên bản, bạn không cần làm gì thêm.
  • Danh sách tool được đọc lại mỗi khi một run bắt đầu. Tool mới bạn thêm vào server được bật sẵn cho mọi agent đang dùng connector (admin có thể tắt từng tool trên console).

Chọn cách xác thực

Cách (trên console)Chọn khiServer phải làm
Không xác thựcDữ liệu công khai, hoặc server chỉ cho dải IP tin cậyKhông gì
HeaderAPI nội bộ, một khoá dùng chung cho cả tổ chức. Cách nhanh nhất cho team nội bộKiểm tra một header cố định
Client credentialsServer-to-server qua Identity Provider có sẵn (Keycloak, Auth0, Entra ID…), không cần người đăng nhậpLà OAuth resource server: discovery + kiểm tra access token
Tự động → OAuthTool phải chạy bằng quyền của một tài khoản người dùng (Google Drive, CRM theo user…)Là OAuth resource server đầy đủ theo MCP Authorization

Server dùng Header thì chọn Header, đừng để Tự động

Ở chế độ Tự động, Harnix gửi initialize không kèm gì; nếu server trả 401, Harnix hiểu là server đòi OAuth và đi tìm tài liệu discovery. Một server chỉ kiểm tra API key sẽ không có tài liệu đó, và connector báo “Không đọc được cấu hình kết nối của máy chủ này” (discovery_failed).

Header cố định

Admin nhập tên và giá trị header trong sheet connector, ví dụ Authorization: Bearer <khoá> hoặc X-API-Key: <khoá>. Harnix gửi header đó trên mọi request tới URL MCP. Giá trị được mã hoá khi lưu và không bao giờ hiện lại.

Phía server:

  • Từ chối request thiếu hoặc sai header bằng 401 (hoặc 403).
  • So sánh khoá bằng phép so sánh thời gian hằng (hmac.compare_digest, crypto.timingSafeEqual), không log giá trị header.
  • Muốn xoay khoá: chấp nhận cả khoá cũ và mới trong một khoảng thời gian, cập nhật header trên console, rồi bỏ khoá cũ.

Mọi cuộc hội thoại dùng connector này đều đi bằng cùng một khoá — nếu cần phân quyền theo phòng ban, tạo nhiều connector với khoá khác nhau.

OAuth: Harnix đăng nhập thay người dùng

Đây là luồng MCP Authorization: OAuth 2.1, authorization code + PKCE. Server MCP của bạn là resource server; việc cấp token do một authorization server đảm nhận (có thể là chính server đó, hoặc Keycloak/Auth0/Entra ID… của bạn).

Các chuẩn Harnix dùng:

ChuẩnDùng đểBắt buộc?
RFC 9728 — OAuth Protected Resource MetadataServer MCP chỉ ra authorization server của nóCó
RFC 8414 — Authorization Server Metadata (hoặc OpenID Connect Discovery)Harnix đọc endpoint, phương thức hỗ trợCó
RFC 7636 — PKCE, phương thức S256Chống đánh cắp codeCó
RFC 8707 — Resource Indicators (resource)Token được cấp riêng cho server MCP của bạnCó (Harnix luôn gửi)
RFC 7591 — Dynamic Client RegistrationHarnix tự đăng ký clientKhông — thiếu thì admin nhập Client ID tay
RFC 7009 — Token RevocationThu hồi token khi admin bấm Ngắt kết nốiKhông
RFC 9207 — tham số iss khi redirect vềChống nhầm authorization serverKhông — có thì Harnix kiểm tra

Bước 1 — Trả 401 kèm WWW-Authenticate

Request tới URL MCP không có token (hoặc token sai, hết hạn) trả về:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource/mcp", scope="orders.read"
  • resource_metadata — URL tài liệu Protected Resource Metadata. Không có, Harnix thử https://mcp.example.com/.well-known/oauth-protected-resource/mcp (theo path của URL MCP) rồi tới /.well-known/oauth-protected-resource.
  • scope (không bắt buộc) — scope cần xin.

Bước 2 — Protected Resource Metadata

{
  "resource": "https://mcp.example.com/mcp",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["orders.read", "orders.write"]
}
  • resource là chính URL MCP mà admin nhập vào Harnix.
  • Harnix dùng phần tử đầu tiên của authorization_servers.

Bước 3 — Authorization Server Metadata

Harnix đọc https://auth.example.com/.well-known/oauth-authorization-server (có path thì thêm path phía sau), rồi tới /.well-known/openid-configuration.

{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/authorize",
  "token_endpoint": "https://auth.example.com/token",
  "registration_endpoint": "https://auth.example.com/register",
  "revocation_endpoint": "https://auth.example.com/revoke",
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none", "client_secret_basic", "client_secret_post"],
  "grant_types_supported": ["authorization_code", "refresh_token"]
}
  • issuer phải khớp URL authorization server ở bước 2.
  • code_challenge_methods_supported phải có S256, nếu không connector báo lỗi pkce_unsupported.
  • Mọi endpoint phải là https công khai — cùng luật với URL MCP.
  • Harnix cache tài liệu này tối đa 24 giờ.

Bước 4 — Đăng ký client

Có registration_endpoint: Harnix tự đăng ký (RFC 7591) với

{
  "client_name": "Harnix",
  "redirect_uris": ["https://app.harnix.vn/connectors/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

token_endpoint_auth_method là none (public client) nếu server hỗ trợ, nếu không là client_secret_basic.

Không có registration_endpoint: tạo client trên authorization server với redirect URI https://app.harnix.vn/connectors/callback, grant authorization_code + refresh_token, rồi đưa OAuth Client ID (và OAuth Client Secret nếu là confidential client) cho admin nhập ở mục Nâng cao. Thiếu cả hai, connector báo registration_unsupported.

Bước 5 — Đăng nhập và đổi code lấy token

Admin bấm Kết nối; Harnix mở authorization_endpoint với:

Tham sốGiá trị
response_typecode
client_idclient đã đăng ký
redirect_urihttps://app.harnix.vn/connectors/callback
statechuỗi ngẫu nhiên, trả nguyên về
code_challenge, code_challenge_methodPKCE, S256
resourceURL MCP
scopescope admin cấu hình; không có thì lấy scope của bước 1; không có nữa thì mọi scopes_supported

Sau khi người dùng đồng ý, redirect về redirect_uri kèm code và state (thêm iss nếu bạn hỗ trợ RFC 9207). Lượt đăng nhập có hiệu lực 10 phút. Harnix gọi token_endpoint (form application/x-www-form-urlencoded):

POST /token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)   ← chỉ khi có secret

grant_type=authorization_code&code=…&redirect_uri=https://app.harnix.vn/connectors/callback
&code_verifier=…&resource=https://mcp.example.com/mcp&client_id=…

Client có secret: Harnix gửi client_secret_basic nếu metadata liệt kê nó (hoặc không nói gì), không thì client_secret_post. Public client chỉ gửi client_id.

Response token:

{
  "access_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "…",
  "scope": "orders.read"
}

Nên trả expires_in và refresh_token. Không có refresh_token, khi token hết hạn admin phải bấm Kết nối lại.

Bước 6 — Kiểm tra token trên mỗi request MCP

Harnix gửi Authorization: Bearer <access_token> trên mọi request. Server MCP:

  1. Xác thực token (chữ ký JWT với JWKS, hoặc token introspection).
  2. Kiểm tra hạn dùng.
  3. Kiểm tra audience: token phải được cấp cho chính URL MCP của bạn (giá trị resource). Không chấp nhận token cấp cho dịch vụ khác.
  4. Kiểm tra scope cho từng tool.
Tình huốngTrả vềHarnix làm gì
Token thiếu, sai, hết hạn401 + WWW-Authenticate như bước 1Refresh một lần rồi gửi lại; vẫn 401 → Cần kết nối lại
Token đúng nhưng thiếu quyền403 + WWW-Authenticate: Bearer error="insufficient_scope", scope="orders.write"Cần kết nối lại; lần kết nối sau xin thêm scope đó

Refresh và thu hồi

  • Harnix refresh trước khi token hết hạn 60 giây (grant_type=refresh_token, kèm resource), hoặc ngay khi nhận 401.
  • Xoay vòng refresh token (mỗi lần refresh trả refresh token mới) được hỗ trợ; Harnix không bao giờ dùng song song một refresh token.
  • Authorization server trả 4xx khi refresh (ví dụ invalid_grant) → connector chuyển Cần kết nối lại. 5xx hay 429 được coi là lỗi tạm thời, Harnix thử lại ở run sau.
  • Admin bấm Ngắt kết nối: Harnix gọi revocation_endpoint (nếu có) cho refresh token rồi access token.

Client credentials

Dùng khi không cần người đăng nhập: Harnix tự lấy token bằng Client ID và Client secret admin nhập trên console. Server vẫn phải làm bước 1–3 ở trên (trả 401 + Protected Resource Metadata + Authorization Server Metadata, metadata vẫn phải liệt kê S256) và bước 6 (kiểm tra token). Harnix gọi:

POST /token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(client_id:client_secret)

grant_type=client_credentials&resource=https://mcp.example.com/mcp&scope=orders.read&client_id=…

Không có refresh token: khi token hết hạn hoặc bị 401, Harnix xin token mới. Authorization server trả 4xx → connector báo Lỗi (client_credentials_rejected) — admin kiểm tra lại Client ID/secret.

Thiết kế tool

Model đọc tên, mô tả và schema của tool để quyết định gọi tool nào, với tham số gì. Tool viết tốt quan trọng ngang code chạy đúng.

  • Tên: snake_case, ASCII, động từ + danh từ (get_order_status, create_ticket). Model thấy tên dạng mcp_<tên connector>_<tên tool>, cắt ở 64 ký tự — giữ tên tool ngắn.

  • description: tool làm gì, khi nào nên dùng, giới hạn của nó. Viết cho model đọc, không phải cho người.

  • inputSchema: JSON Schema đầy đủ — kiểu, required, enum, mô tả từng trường. Kiểm tra lại tham số phía server; đừng tin model.

  • Annotation — quyết định có hỏi người dùng trước khi chạy hay không (xem Xác nhận trước khi gọi tool):

    AnnotationHarnix
    readOnlyHint: trueChạy ngay
    Không khai, hoặc readOnlyHint: falseHỏi người dùng cuối trước khi chạy

    Chỉ đánh dấu readOnlyHint: true khi tool thật sự không đổi gì.

  • title (không bắt buộc): tên dễ đọc, hiện trên console.

Trả kết quả

  • Trả nội dung dạng text. Harnix chỉ đọc các khối text và nối chúng lại; ảnh và tài nguyên nhị phân bị bỏ qua. Dữ liệu có cấu trúc thì trả JSON dưới dạng text.
  • Kết quả quá 64 KB bị cắt kèm ghi chú. Phân trang hoặc tóm tắt thay vì trả cả bảng dữ liệu.
  • Lỗi nghiệp vụ (“không tìm thấy đơn A-1003”) → trả kết quả với isError: true và một câu giải thích; model đọc được và trả lời người dùng. Đừng ném exception cho lỗi nghiệp vụ.
  • Không đưa bí mật, token, stack trace vào kết quả: kết quả vào thẳng prompt và hiện trên trace.
  • Idempotency: nếu run bị gián đoạn giữa lúc tool đang chạy, Harnix có thể gọi lại tool đó một lần. Tool ghi dữ liệu nên chống trùng (ví dụ nhận một mã đơn do client sinh, hoặc kiểm tra trùng trước khi tạo).

Ví dụ

# pip install "mcp[cli]"
from mcp.server.fastmcp import FastMCP
from mcp.types import ToolAnnotations

mcp = FastMCP("orders", stateless_http=True)

@mcp.tool(annotations=ToolAnnotations(readOnlyHint=True))
def get_order_status(order_id: str) -> str:
    """Trạng thái giao hàng của một đơn. Dùng khi khách hỏi đơn đang ở đâu."""
    return f"Đơn {order_id} đã giao cho DHL ngày 2026-10-10."

@mcp.tool()  # không khai readOnlyHint → Harnix hỏi người dùng trước
def create_order(sku: str, quantity: int) -> str:
    """Tạo đơn hàng mới cho một SKU."""
    return f"Đã tạo đơn: {quantity} x {sku}."

if __name__ == "__main__":
    mcp.run(transport="streamable-http")  # phục vụ ở /mcp

Phần OAuth (bước 1–6) thường do một authorization server có sẵn đảm nhận; SDK chính thức có sẵn phần kiểm tra token và Protected Resource Metadata cho resource server — xem tài liệu của SDK bạn dùng.

Kiểm tra trước khi giao

  1. Chạy MCP Inspector (npx @modelcontextprotocol/inspector) với URL của bạn: kết nối được, thấy đủ tool, gọi thử từng tool.

  2. Với OAuth, kiểm tra discovery bằng curl:

    curl -i -X POST https://mcp.example.com/mcp \
      -H 'Content-Type: application/json' \
      -H 'Accept: application/json, text/event-stream' \
      -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test","version":"1"}}}'
    # → 401 và WWW-Authenticate có resource_metadata
    curl https://mcp.example.com/.well-known/oauth-protected-resource/mcp
    curl https://auth.example.com/.well-known/oauth-authorization-server
    # → code_challenge_methods_supported có "S256"
  3. Thêm connector trên console, chọn đúng cách Xác thực, bấm Kiểm tra: phải thấy Tìm thấy N công cụ.

  4. Gán cho một agent thử, hỏi một câu cần tool, mở Lượt chạy & Trace xem tham số và kết quả.

Mã lỗi và cách sửa phía server

Mã (console hiển thị câu tương ứng)Nguyên nhân thường gặpSửa
discovery_failedKhông có Protected Resource Metadata hoặc Authorization Server Metadata; issuer không khớp; server dùng header nhưng connector để Tự độngLàm đủ bước 1–3, hoặc chọn Header
pkce_unsupportedMetadata không liệt kê S256Bật PKCE S256 trên authorization server
registration_unsupportedKhông có registration_endpoint và chưa nhập Client IDBật Dynamic Client Registration, hoặc tạo client tay và nhập ở Nâng cao
token_exchange_failedToken endpoint từ chối code — sai redirect_uri, code_verifier, client authKiểm tra redirect URI https://app.harnix.vn/connectors/callback và phương thức client auth
refresh_rejectedRefresh token bị thu hồi/hết hạn, hoặc server vẫn trả 401 sau khi refreshKiểm tra audience/hạn token; admin bấm Kết nối lại
insufficient_scopeServer trả 403 insufficient_scopeĐúng ý đồ: admin Kết nối lại để cấp thêm scope
client_credentials_rejectedClient ID/secret sai, hoặc token mới cấp vẫn bị 401Kiểm tra client và audience của token
connection timed outKhông trả lời trong 10 giây (kết nối) / 120 giây (gọi tool)Tăng tốc initialize/tools/list, kiểm tra firewall
Tên lớp lỗi (ví dụ ConnectError, ProbeStatusError)Không kết nối được, hoặc trả mã HTTP không mong đợi (3xx, 5xx)Kiểm tra URL, chứng chỉ TLS, redirect

Harnix không bao giờ hiện nguyên văn thông báo lỗi của server (nó có thể chứa bí mật) — chỉ mã ở trên hoặc tên lớp lỗi. Muốn biết chi tiết, xem log phía server của bạn.

Trên trang này