VietQR payment infrastructure for AI agents — MCP server for collecting VietQR payments from users inside a conversation
AgentPay VN provides well-structured payment tools with clear naming, comprehensive descriptions, and proper JSON Schema definitions. All 4 tools are explicitly registered with the @mcp.tool() decorator in agentpay/mcp_server.py and include both input schemas and descriptions. Tool names follow verb_noun conventions (create_payment_request, check_payment, await_settlement, list_recent_payments). Descriptions are detailed and contextual (100-300 chars), explaining WHAT the tool does, WHEN to use it, and key prerequisites. Parameters include type definitions, descriptions, and sensible defaults. Error handling via raise_for_status() is present but error messages to the LLM are not customized, the tool will return HTTP status errors without recovery guidance. Output is rendered via _render() into human+machine-readable text, though the schema documentation could be more explicit in the tool docstrings about the exact fields returned.
Wait for a payment request to be settled (polls on behalf of the agent). Polls every 5 seconds until status != pending or the timeout is reached (maximum 600 s). Call this after sending the QR to the payer. If the timeout expires while still pending, ask the user whether to keep waiting or cancel. Args: payment_request_id: The ``id`` returned by ``create_payment_request``. timeout_seconds: How long to wait in seconds (10–600, default 180).
Check the current status of a payment request. Returns: One of: pending | settled | underpaid | expired | cancelled, along with the amount received so far. Only treat a payment as complete when status=settled.
Create a VietQR payment request. Args: amount: Amount in VND, minimum 1 000. description: Short description for the payer (e.g. "Order #123 — 2 kg coffee"). ttl_minutes: QR validity window in minutes (5–1 440, default 60). metadata_note: Internal note from the agent (order id, conversation id, etc.) — echoed back in webhook events. Returns: id, pay_code, QR image URL, and checkout page URL. Send ``qr_image_url`` or ``checkout_url`` to the payer, then call ``await_settlement(id)`` to wait for the money to arrive.
List the most recently settled transactions (quick reconciliation). Args: limit: Number of transactions to return (1–50, default 10).
Error responses lack recovery guidance. HTTP errors (e.g., 404, 401, 5xx) are raised as bare exceptions without LLM-actionable messages. When a payment_request_id is invalid, the LLM receives an HTTP 404 with no hint to try check_payment() or create_payment_request().
Output schema not formally documented in docstrings. The _render() function produces structured text (id, status, amount, pay_code, qr_image_url, checkout_url, expires_at, settled_at, livemode), but the tool descriptions do not explicitly declare the output structure. LLMs must infer field names from the rendered text.
Parameter validation and error messages are generic. The code validates ranges (ttl_minutes 5 - 1440, timeout_seconds 10 - 600) internally but does not return descriptive errors to the LLM when bounds are violated. E.g., if timeout_seconds=1000, it silently clamps to 600 without informing the LLM.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 64 | 2026-07-28+ | v2 |
Missing pagination documentation for list_recent_payments. The tool accepts a 'limit' parameter (1 - 50, default 10) but does not document whether more results exist, how to fetch the next page, or what the total count is. Large datasets may be silently truncated.
Metadata note parameter uses underscore naming (metadata_note) in the Python function signature but is converted to 'metadata' → 'note' in the payload. This mapping is not documented and could confuse LLMs if they inspect the request structure.