Skip to content

MCP server

MCP server for ERP quote and order data

Give your own AI agent the use of your ERP. The ERP Agent MCP server exposes ten tools: identify products from a customer’s wording, search the catalogue, look up customers, and create offers. It is a remote MCP server over HTTP, so any MCP client can connect to it.

On this page

Overview

MCP — the Model Context Protocol — is how an AI agent picks up tools it did not ship with. Point your agent at this server and it can work your ERP the way a salesperson does: take a customer’s enquiry in the customer’s own words, work out which of your products it means, look the customer up, and draft the offer.

The server is remote and hosted by us, so there is nothing to install or run. It speaks plain HTTP and needs only an API key.

Endpoint
https://erp-agent.com/api/v1/mcp

Everything here sits on the same platform as the REST API: the same organization, the same keys, the same offers.

Connect

In Claude Code, add the server with one command. Anything a registered MCP server can do, your agent can then do in the course of a normal conversation.

Claude Code
claude mcp add erp-agent --transport http \
  https://erp-agent.com/api/v1/mcp \
  --header "Authorization: Bearer YOUR_API_KEY"

Claude Desktop and most frameworks take a JSON configuration instead. The shape varies slightly between clients, but the three things that matter are always the same: the transport is http, the URL, and the Authorization header.

Generic client configuration
{
  "mcpServers": {
    "erp-agent": {
      "type": "http",
      "url": "https://erp-agent.com/api/v1/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY"
      }
    }
  }
}

Any client that speaks MCP works — Claude Code, Claude Desktop, or an agent framework of your own. Once connected, ask your agent to call erp_whoami to confirm which organization the key is bound to before you let it do anything else.

Authentication

Authentication is the same sk_live_… API key as the REST API, sent as a bearer token. One key belongs to exactly one organization and cannot reach another tenant’s data.

Authorization header
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

The key must additionally carry the mcp:use scope. Scopes are granted per key by ERP Agent, not self-served: a key that works perfectly well against the REST API may still be missing this one. Without it every tool call comes back as insufficient_scope — if you see that, ask your ERP Agent contact to add the scope to the key.

Keys themselves are issued and scoped by ERP Agent. How they are created, stored and revoked is covered in the API documentation; treat the key as a password and keep it out of anything a user can read.

Transport

The server is stateless. There is no session id to carry between calls, and responses come back as plain JSON on the POST rather than as an SSE stream. That makes it safe to put behind a load balancer and cheap for a client to retry.

FieldDescription
TransportStreamable HTTP. POST JSON-RPC, receive JSON in the response body.
SessionsNone. No session id is issued and none is expected.
StreamingNo SSE stream. Every response is a complete JSON body.
Protocol versions2025-06-18, 2025-03-26 and 2024-11-05 are supported.

Search vs. match

Two tools look superficially alike and are not. Getting this right is most of what separates an agent that works from one that quietly returns the wrong products.

erp_product_search is a catalogue lookup. It answers does this code exist and what does the catalogue hold for these words. It is fast, synchronous and literal: it searches your catalogue and hands back the rows it finds.

erp_product_match is the identification agent. It answers a harder question — which of our products does this customer’s request mean — and it is the same pipeline that produces offers. It handles the customer’s own part numbers, competitor codes, standards and abbreviations, the things that never appear verbatim in your catalogue.

The rule: use erp_product_match whenever the input is a customer’s wording, and erp_product_search only when you already hold a code you trust, or want to see what the catalogue contains. Searching with a customer’s phrasing is the classic mistake; it finds something, and the something is often wrong.

Tool reference

Ten tools. Two of them start an agent run and return an id; the other eight answer immediately.

erp_whoamisync

Returns the organization the key is bound to, its scopes and its rate limit. Takes no arguments. Call it first when wiring up a new client — it is the cheapest way to prove the key and the mcp:use scope are both in place.

erp_product_searchsync

Catalogue lookup: code, name, unit and price. Use it to confirm a code exists or to see which catalogue rows match a few words. This is not identification — see Search vs. match.

FieldTypeDescription
querystringFree text to search the catalogue for.
codestringAn exact product code to look up.
limitinteger1–50, default 10.
erp_customer_searchsync

Find a customer in the ERP. Returns the customer number, name, address, contact and payment terms. Use it to resolve a company name into the number you then pass to erp_offer_create.

FieldTypeDescription
querystringCompany name to search for.
numberstringAn exact customer number to look up.
limitinteger1–50, default 10.
erp_product_matchasync

Runs the identification agent over a customer’s wording and attachments and returns a match id. Use it when you want the identified product lines but no offer — checking an enquiry, enriching a ticket, answering “do we sell this?” for a list. It leaves no offer behind.

FieldTypeDescription
textstringThe customer's request in their own words.
file_idsstring[]Ids returned by erp_file_create, for attachments uploaded beforehand.
fast_modebooleantrue trades part of the search for speed. false (default) searches thoroughly.
customer_numberstringOptional ERP customer number, so the agent matches in that context.
referencestringYour own identifier, echoed back on every read.
erp_product_match_getsync

Reads a match back: its status, and once the run has completed, the identified lines. Poll this after erp_product_match.

FieldTypeDescription
match_id (required)stringThe id erp_product_match returned.
erp_offer_createasync

Turns a request into a priced offer draft and returns an offer id. Use it when the outcome should be something a salesperson can review and send, rather than just a list of products.

FieldTypeDescription
textstringThe customer's request in their own words.
file_idsstring[]Ids returned by erp_file_create.
fast_modebooleantrue trades part of the search for speed. false (default) searches thoroughly.
customer_numberstringOptional ERP customer number. Given one, the agent looks the customer up directly.
customer_namestringOptional hint used when no customer number is supplied.
request_typestring"offer" or "order".
referencestringYour own identifier, echoed back on every read.
erp_offer_getsync

Reads one offer: its status, and once the run has finished, the resolved customer and every line item. Poll this after erp_offer_create.

FieldTypeDescription
offer_id (required)stringThe id erp_offer_create returned.
erp_offer_listsync

Every offer your organization created, newest first. This is the recovery path: if an agent loses track of an id mid-conversation, it can find the run again here.

FieldTypeDescription
limitinteger1–100, default 25.
statusstringReturn only offers in this status.
created_afterstringISO-8601 timestamp; only offers created after it.
cursorstringThe cursor from the previous page.
erp_match_listsync

The same listing for matches rather than offers, newest first.

FieldTypeDescription
limitintegerHow many matches to return.
created_afterstringISO-8601 timestamp; only matches created after it.
cursorstringThe cursor from the previous page.
erp_file_createsync

Reserves an attachment and returns an id plus a presigned upload_url and upload_fields. POST the bytes to that URL as multipart/form-data, then pass the id in file_ids. See Files.

FieldTypeDescription
filename (required)stringThe file name, including its extension.
content_typestringThe MIME type of the file.
size (required)integerSize in bytes. 25 MB is the maximum.

Asynchronous tools

erp_product_match and erp_offer_create start real work. They return an id immediately and the result arrives minutes later: roughly one to three minutes in fast mode for about 50 lines, three to eight minutes otherwise. Read the result with erp_product_match_get or erp_offer_get.

This is worth telling your agent in its own instructions, because an agent that has not been told will treat the id as a failure and try again. The pattern that works is: call, tell the user roughly how long it takes, then poll rather than re-submit.

An agent conversation
User
  Here's an enquiry from Sähkötukku. Can you quote it?
  "Tarvitsemme 20 kpl 16mm2 kuparikaapelia ja 50 kpl KAA 6x1.5"

Agent → erp_customer_search { "query": "Sähkötukku" }
     ← { "number": "K-10042", "name": "Sähkötukku Oy", … }

Agent → erp_offer_create {
         "text": "Tarvitsemme 20 kpl 16mm2 kuparikaapelia ja 50 kpl KAA 6x1.5",
         "customer_number": "K-10042"
       }
     ← { "id": "9f1c0e2a-…", "status": "processing" }

Agent
  Started the offer for Sähkötukku Oy — it usually takes a few minutes.
  I'll check back.

  … a few minutes later …

Agent → erp_offer_get { "offer_id": "9f1c0e2a-…" }
     ← { "status": "processing" }

     … waits, then polls again …

Agent → erp_offer_get { "offer_id": "9f1c0e2a-…" }
     ← { "status": "completed", "result": { "line_items": [ … ] } }

Agent
  Two lines, both matched: MCM-CU16-100 ×20 and KAA-6X15 ×50.
  The draft is waiting for approval in the dashboard.

Offers are drafts

Nothing an agent does here reaches the end customer, and nothing is booked in the ERP. erp_offer_create produces a draft, and the draft stays a draft until a salesperson opens it in the ERP Agent dashboard and approves it.

This is the property that makes it sane to hand these tools to an agent at all. The worst case of a confused agent is a draft nobody approves, not a wrong price in a customer’s inbox or a bad row in your ERP.

Matching without an offer

erp_product_match runs the same pipeline as an offer and deletes its draft when the run finishes. The practical consequence, stated plainly: while the run is in progress the draft is briefly visible in the dashboard, and then it is gone. Expect to see it there; do not act on it.

Files

Attachments are uploaded out of band, not through the tool call. Ask for an upload with erp_file_create, POST the bytes to the presigned URL it returns, then reference the id in file_ids on erp_product_match or erp_offer_create.

Upload flow
# 1. reserve
erp_file_create {
  "filename": "bom.xlsx",
  "content_type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  "size": 18300000
}

{
  "id": "1c7a…",
  "upload_url": "https://…",
  "upload_fields": { "key": "…", "policy": "…", "x-amz-signature": "…" }
}

# 2. POST the bytes as multipart/form-data:
#    every upload_fields entry first, file last

# 3. reference the id
erp_offer_create { "file_ids": ["1c7a…"], "customer_number": "K-10042" }

A file may be up to 25 MB, and one request may carry up to 20 of them.

Errors

A tool call that is refused comes back as an MCP tool result with isError: true, whose text is the same error envelope the REST API uses. Branch on error.code, never on the message text.

Tool error result
{
  "error": {
    "type": "invalid_request_error",
    "code": "missing_query",
    "message": "Provide 'query' or 'code'."
  }
}
FieldDescription
insufficient_scopeThe key does not carry mcp:use. Ask your ERP Agent contact to add it.
rate_limit_exceededToo many requests for this key in the last minute. Back off and retry.
too_many_concurrent_runsThis key already has 5 agent runs in progress. Wait for one to finish.
erp_not_configuredThis organization has no ERP connection set up yet.
missing_queryA search tool was called with nothing to search for.
unsupported_file_typeThe attachment is not a type the agent can read.
match_not_foundNo such match for this organization.
offer_not_foundNo such offer for this organization.

Protocol-level problems are different. An unknown tool name or malformed JSON-RPC comes back as a JSON-RPC error rather than as a tool result — the call never reached a tool, so there is no tool result to carry an envelope.

Limits

Each key is limited to 60 requests per minute by default. Exceeding it returns rate_limit_exceeded.

Separately, one key may have 5 agent runs in progress at a time — erp_product_match and erp_offer_create counted together. Starting a sixth returns too_many_concurrent_runs, so have your agent queue its work rather than firing a batch at once.

Attachments are capped at 25 MB per file and 20 files per request.

Need a higher limit?

Email lauri@erp-agent.com and we’ll set it up.