Skip to content

MCP connector (AI assistant) ​

ZERPY can be connected to an AI chat — Claude (claude.ai, desktop, mobile) or ChatGPT — as a remote MCP server. People can then ask questions about the business ("which invoices are overdue?", "best-selling products this quarter?") or, when allowed, give instructions ("translate the sauna descriptions into Finnish", "make a draft order for Customer OÜ: 2 × S1").

This page is for developers and IT administrators. For the step-by-step user guide see AI assistant.

Overview ​

MCP endpointhttps://zerpy.app/api/mcp
TransportStreamable HTTP, stateless, JSON responses (POST only)
AuthenticationOAuth 2.1 — authorization code + PKCE (S256), dynamic client registration
Scopeszerpy:read, zerpy:write
Rate limit60 requests per minute per connection

The MCP connector is separate from the REST API v1: it uses OAuth tokens tied to a person, not API keys tied to a company.

Setting it up ​

  1. Company switch. An owner or admin opens Integrations → AI assistant and turns on Allow AI chat connections. Optionally also Allow changes (without it every connection is read-only). Both are off by default.
  2. Add the connector in the chat app with the URL https://zerpy.app/api/mcp:
    • Claude: Settings → Connectors → Add custom connector → URL → Connect.
    • ChatGPT: Settings → Connectors (developer mode / custom connector) → URL → Connect.
  3. Consent. The chat app opens ZERPY. The person signs in, chooses the company and Read only or Read and change, and allows the connection. Read and change is offered only when their role may change products, partners or sales.
  4. Ending a connection: the person in user menu → AI assistant; owners and admins can end any connection of the company in Integrations → AI assistant (End connection). Turning the company switch off ends all connections at once.

Every new connection sends a security e-mail to the company's owners.

OAuth 2.1 flow ​

Chat apps that support MCP do all of this automatically; the details matter only if you build your own client.

Discovery ​

An unauthenticated request to /api/mcp returns 401 with:

http
WWW-Authenticate: Bearer resource_metadata="https://zerpy.app/.well-known/oauth-protected-resource/api/mcp", error="invalid_token"
URLStandard
GET /.well-known/oauth-protected-resource and /.well-known/oauth-protected-resource/api/mcpProtected Resource Metadata (RFC 9728)
GET /.well-known/oauth-authorization-serverAuthorization Server Metadata (RFC 8414)
json
{
  "issuer": "https://zerpy.app",
  "authorization_endpoint": "https://zerpy.app/oauth/authorize",
  "token_endpoint": "https://zerpy.app/api/oauth/token",
  "registration_endpoint": "https://zerpy.app/api/oauth/register",
  "revocation_endpoint": "https://zerpy.app/api/oauth/revoke",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
  "revocation_endpoint_auth_methods_supported": ["none"],
  "scopes_supported": ["zerpy:read", "zerpy:write"]
}

Endpoints ​

EndpointPurpose
POST /api/oauth/registerDynamic client registration (RFC 7591). Public clients only (token_endpoint_auth_method: "none"), 1–10 redirect_uris, max 10 registrations per hour per IP
GET /oauth/authorizeConsent page: sign in, choose company and access
POST /api/oauth/tokengrant_type=authorization_code (with code_verifier) or refresh_token; form-encoded or JSON
POST /api/oauth/revokeToken revocation (RFC 7009) — revokes the whole connection

Rules ​

  • PKCE with S256 is required. Plain challenges are refused.
  • Redirect URIs must match a registered URI exactly; https, or http only on localhost.
  • Authorization codes: single use, valid 5 minutes.
  • Access tokens: valid 1 hour. Refresh tokens: valid 30 days and rotating — every refresh returns a new refresh token. Reusing an old refresh token revokes the whole connection.
  • resource: when sent, it must be the MCP URL (https://zerpy.app/api/mcp) or the app base URL.
  • Scopes come from the person's choice on the consent page: Read only = zerpy:read, Read and change = zerpy:read zerpy:write.
  • Only SHA-256 hashes of codes and tokens are stored.
  • /api/mcp accepts only Authorization: Bearer tokens — a browser session cookie is never accepted.

Security model ​

RuleWhat it means
One company per connectionA connection = one person + one company + scopes. Tools only see that company's data; ids, numbers and SKUs of other companies are "not found".
Same permissions as the appEvery request re-reads the person's membership and roles. Each tool checks the same role areas as the ZERPY app. A person removed from the team loses access immediately. Cost figures are removed for roles that do not see costs.
Changes are opt-inWrite tools need the zerpy:write scope and Allow changes on the company. Otherwise they answer "This connection is read-only".
Kill switchAllow AI chat connections off = every request gets 403 and all connections are ended.
Prompt injectionThe server tells the AI that returned data (names, notes, e-mails) is business data, never instructions. Write tools are marked readOnlyHint: false, so chat apps ask the user before running them.
Limits60 requests per minute per connection (429), lists max 100 rows, tool results cut at about 60 kB, request body max 1 MB.

Tools ​

Read ​

ToolRole areaWhat it returns
company_overviewany staffHeadline numbers: sales this and last month, open orders, unpaid / overdue amounts, orders to ship, overdue invoices, low stock, late purchase orders, planned production, 6-month trend. Money figures only for roles that see invoicing
search_productscatalogProducts by name / SKU (query), category, type, include_archived, limit ≤ 100: SKU, name, category, net price, VAT, stock, translated languages
get_productcatalogOne product by sku: description, translations, options, stock per warehouse, weight / HS code, minimum stock; cost price and suppliers only for roles with costs
stock_levelsinventoryOn hand / available / minimum stock in own warehouses; query, low_only, limit
search_partnerspartnersCustomers / suppliers by name, registry code or e-mail; kind (customer / supplier), include_archived, limit
get_partnerpartnersOne partner (partner = name, registry code or e-mail) with contacts, terms, notes, latest orders and open invoices
list_sales_orderssalesOrders newest first; status (an order status or open), customer, from / to, limit
get_sales_ordersalesOne order by number: lines with options, shipped quantities, totals, shipments with tracking, invoices; payment status for roles that see invoicing
list_invoicesinvoicingInvoices and credit notes; status = unpaid, overdue, paid, draft, all; customer, from / to, limit
get_invoiceinvoicingOne invoice / credit note by number: lines, totals, due date, reference number, open balance
sales_reportinvoicingNet sales from issued invoices and credit notes between from and to, in the company currency, grouped by month, product, customer or category (top 100)
monthly_reportinvoicing + costsThe accountant's monthly report for month (YYYY-MM): sales and VAT, payments, receivables / prepayments, stock value, VAT return figures, EU sales list

Change (need write access) ​

ToolRole areaWhat it does
create_productcatalogNew product: sku, name, type, description, category (created if missing), unit, sale_price_net, vat_rate, sold, translations. Never sets cost price
update_productcatalogChange a product by sku: name, description, category, unit, net sale price, VAT rate, sold, translations. Only the given fields change. Cannot archive, delete, or change cost price or stock
set_option_translationscatalogTranslations of an option group and its values
create_partnerpartnersNew customer and/or supplier (contacts, addresses, payment terms, document language, delivery terms, notes). A VAT number is checked in VIES automatically
update_partnerpartnersChange a partner's contacts, addresses, terms, document language, delivery terms or notes. Cannot archive or delete
create_sales_order_draftsalesA draft sales order: customer, lines (sku, quantity, optional unit_price_net, discount_pct, options as group + value names), customer_ref, notes, delivery_date. Prices default to the customer's price. Reserves nothing — a person confirms it in ZERPY
undo_last_change—Reverts the person's own most recent AI change (product, partner or option translations) from the last 24 hours, if nobody changed that record since

What is never possible ​

There are no tools that delete, archive, cancel, confirm, ship, issue invoices, send e-mails, record payments or move stock. Sales orders are created only as drafts. Cost prices cannot be changed. These actions always need a person in the ZERPY app.

Audit and revert ​

  • Every AI change is saved with the normal event, marked "via AI" with the chat app's name and the values before and after.
  • The Activity log (owners and admins) shows these changes with a badge and a Revert button.
  • In the chat, undo_last_change reverts the person's own last change (24 hours).
  • A revert is refused if the record was changed after the AI change. Created products, partners and order drafts are not removed by undo — archive or delete them in ZERPY.

Errors ​

HTTPBody errorMeaning
401invalid_tokenMissing, invalid or expired access token — refresh it or reconnect
403forbiddenAI access is switched off for the company, or the person is no longer a member
429rate_limitedMore than 60 requests per minute on this connection

Tool-level problems (no permission for an area, not found, invalid input, read-only connection) come back as MCP tool results with isError: true and a readable message, so the AI can explain them to the user.