CheckSlip MCP
A remote MCP server for verifying Thai bank transfer slips using slip images, QR payloads, or transaction references, with OAuth 2.1 authentication and duplicate detection.
README
CheckSlip MCP
A deployable remote MCP server (Streamable HTTP) for verifying Thai bank transfer slips. It exposes MCP tools over HTTP so any MCP-compatible client can verify a slip's amount, receiver, transfer date, and detect duplicate usage.
This server runs on Replit and is built into the monorepo's Express service. The MCP endpoint is served at the root path (
/mcp), protected by a full OAuth 2.1 flow so MCP clients (e.g. Claude's "Connect") can sign in./health,/, and the OAuth discovery/authorization endpoints are public.
Endpoints
POST /mcp— MCP Streamable HTTP (initialize + requests). Requires a valid OAuth access token (Authorization: Bearer <token>).GET /mcp— MCP server-to-client stream (requires an active session)DELETE /mcp— terminate an MCP sessionGET /health—{ "ok": true, "name": "CheckSlip MCP", "version": "1.0.0" }GET /— basic server infoGET /admin— password-gated merchant console (create/list/revoke/rotate keys)- OAuth (public):
GET /.well-known/oauth-protected-resource,GET /.well-known/oauth-authorization-server,POST /register(DCR),GET|POST /authorize,POST /token
Authentication (OAuth 2.1)
The /mcp endpoint is protected by an OAuth 2.1 Authorization Code flow with
PKCE, designed for MCP clients like Claude's custom connector:
- The client reads
/.well-known/oauth-protected-resource(advertised via theWWW-Authenticateheader on a401) to discover the authorization server. - It registers dynamically via
POST /register(DCR), then opens/authorizein a browser. - The user signs in with their Merchant Name and API Key (issued from
the
/adminconsole). On success an authorization code is redirected back. - The client exchanges the code at
/token(verifying PKCE) for a short-lived JWT access token (signed withSESSION_SECRET, audience<base>/mcp) plus a rotating refresh token.
Merchants and their API keys are managed in the /admin console, gated by
ADMIN_PASSWORD. A static MCP_AUTH_TOKEN is still accepted as a fallback for
server-to-server callers when set, but is no longer required.
MCP tools
check_slip
Verifies a slip via a slip image, a QR payload, or a transaction reference.
Input:
{
"slip_image_base64": "base64 PNG/JPEG (optional)",
"qr_payload": "string (optional)",
"transaction_ref": "string (optional)",
"expected_amount": "number > 0 (optional)",
"expected_receiver_name": "string (optional)",
"expected_receiver_account_last4": "4 digits (optional)",
"expected_transfer_date": "YYYY-MM-DD (optional)",
"order_id": "string (optional)",
"mark_as_used": "boolean (default false)"
}
At least one of slip_image_base64, qr_payload, or transaction_ref is
required. When only slip_image_base64 is given (raw base64 or a
data:image/...;base64, data URL), the server decodes the QR code from the
image and uses it as the QR payload. The result is
returned as JSON text content with status one of
verified | failed | duplicate | provider_error | config_error.
Comparison rules:
- Amount: exact numeric match.
- Receiver name: case-insensitive, whitespace-trimmed.
- Receiver account last 4: digits only.
- Date: compares the
YYYY-MM-DDpart of the slip's transfer datetime. - Fields you don't pass are omitted from
checks(not reported asfalse).
When mark_as_used is true and all checks pass, the slip's transaction id is
recorded so future checks of the same slip return status: "duplicate".
slip_checker_status
Returns configuration/health without exposing secrets (provider, whether an API URL/key is set, duplicate store path, auth config (OAuth enabled + whether the static token fallback is set), allowed-origins count).
Setup on Replit
-
The project already runs on Replit. Add these Secrets / env vars:
Key Required Notes SESSION_SECRETYes Signs OAuth access tokens and admin sessions. Server refuses to start without it. ADMIN_PASSWORDYes Gates the /adminmerchant console. Server refuses to start without it.MCP_AUTH_TOKENNo Optional static Bearer token fallback for server-to-server callers. SLIP_PROVIDERNo mock(default),uex, orgeneric.ALLOWED_ORIGINSNo Comma-separated allowlist. Empty allows requests with no Origin. UEX_ACCOUNT_NOuex only Secret. UEX bank account number (used in the request path). UEX_LICENSE_KEYuex only Secret. UEX x-license-key.UEX_ACCESS_KEYuex only Secret. UEX access-key.UEX_API_BASE_URLNo UEX base URL (default https://api-fin.uexchange.io/v1/fin/bank).SLIP_API_URLgeneric only Custom provider verify endpoint. SLIP_API_KEYgeneric only Custom provider API key. SLIP_API_AUTH_HEADERNo Header name for the key (default Authorization).SLIP_API_KEY_PREFIXNo Prefix for the key value (default Bearer).SHOP_RECEIVER_NAMENo Default receiver name for the mock provider. SHOP_RECEIVER_ACCOUNT_LAST4No Default receiver last 4 for the mock provider. -
Run (development): the workflow runs the server automatically. To run the commands manually:
pnpm --filter @workspace/api-server run dev -
MCP endpoint (after publishing):
https://<your-replit-url>/mcp -
Authentication: point your MCP client at the
/mcpURL and use its "Connect" flow. It will discover the OAuth endpoints, open the browser login, and ask for your Merchant Name and API Key (created in/admin). The client then attachesAuthorization: Bearer <access_token>automatically.To create a merchant + API key, open
https://<your-replit-url>/admin, sign in withADMIN_PASSWORD, add a merchant, and copy the one-time API key.
Mock test
With SLIP_PROVIDER=mock, call check_slip with:
{
"qr_payload": "MOCK_OK",
"expected_amount": 1590,
"expected_receiver_name": "ABC SHOP",
"expected_receiver_account_last4": "1234",
"order_id": "ORDER-TEST-001",
"mark_as_used": true
}
Mock values:
MOCK_OK→ verified slip (amount 1590).MOCK_890→ verified slip (amount 890).MOCK_FAIL→ provider reports not verified.
UEX provider (real Thai bank verification)
Verifies slips through the UEX (uexchange.io) Qr Scan API.
SLIP_PROVIDER=uex
UEX_ACCOUNT_NO=<your account number> # secret
UEX_LICENSE_KEY=<your x-license-key> # secret
UEX_ACCESS_KEY=<your access-key> # secret
# UEX_API_BASE_URL defaults to https://api-fin.uexchange.io/v1/fin/bank
How it works:
check_slip'sqr_payload(or the QR decoded fromslip_image_base64, ortransaction_ref) is sent as theqrStringtoPOST {UEX_API_BASE_URL}/{UEX_ACCOUNT_NO}/qrScanwith thex-license-keyandaccess-keyheaders.- A response with inner
statusCode: "0000"is a verified slip; its fields (transaction ref, amount, receiver name/account, transfer date, bank) are mapped into the internal slip shape. 8xxdata errors such as8404("Transaction Reference does not exist") are reported asfailed(the slip is invalid/not found). System errors are reported asprovider_error.
Switching to a custom provider
SLIP_PROVIDER=generic
SLIP_API_URL=https://your-slip-provider.example.com/verify
SLIP_API_KEY=your_api_key
The generic provider POSTs { qr_payload, transaction_ref } and normalizes
common provider field names into the internal slip format.
Automated tests
Vitest + Supertest cover the OAuth 2.1 security boundary and the admin/merchant
console. They run against the dev Postgres database (DATABASE_URL) and create
their own uniquely-named merchants, cleaning up afterward.
pnpm --filter @workspace/api-server run test
Covered: DCR, authorize → token with PKCE (happy path + wrong verifier),
single-use auth codes, refresh-token rotation invalidating the old token, exact
aud/iss enforcement on /mcp, invalid/expired token rejection, merchant
credential validation, and the admin password gate + create/revoke/rotate. The
suite is registered as the test validation (CI) check.
Smoke test
Checks /health and prints example check_slip input (no MCP client needed):
pnpm --filter @workspace/scripts run smoke
Warning
This server verifies slips through a provider API, never from the picture alone.
When you pass slip_image_base64, the server only reads the slip's QR code from
the image and forwards that QR payload to the provider; the rest of the image is
not trusted. Slips without a readable QR code must be supplied as a qr_payload
or transaction_ref instead.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.