baselinker-mcp
Enables natural language interaction with BaseLinker (orders, invoices, returns, inventory, CRM, and more) through the Model Context Protocol, with optional write access.
README
baselinker-mcp
An MCP server that puts the whole BaseLinker API — orders, invoices, returns, couriers, CRM, warehouses, products — in front of an LLM client such as Claude Code, Claude Desktop or Cursor.
- Complete. All 179 documented API methods, none of them stubbed.
- Read-only until you say otherwise. The 92 write methods stay invisible unless you
opt in; with writes off, every tool reports
readOnlyHint: true. - Local or remote. stdio for a client on your machine, or Streamable HTTP with OAuth 2.1 (Keycloak) for a shared endpoint on the internet.
"How many orders came in yesterday that aren't paid yet?"
"Which catalog products dropped below 5 in stock this week?"
"Pull the courier label for order 1234567 and tell me the tracking number."
Contents
- Quickstart
- Connecting a client
- Tools
- Write methods
- Behaviour worth knowing
- Remote deployment (HTTP + OAuth)
- Configuration reference
- Troubleshooting
- Development
Quickstart
Requirements: Node.js 20 or newer, and a BaseLinker API token from the BaseLinker panel under Account & other → My account → API.
git clone https://github.com/PiotrRaszkowski/baselinker-mcp.git
cd baselinker-mcp
npm install
npm run build
cp .env.example .env # paste your token into BASELINKER_API_TOKEN
The token can also come straight from the environment, which takes precedence over .env.
.env is read from the package root, so the server starts correctly no matter which
directory your MCP client launches it from.
Connecting a client
Claude Code
claude mcp add baselinker -e BASELINKER_API_TOKEN=your-token -- node /path/to/baselinker-mcp/dist/index.js
Claude Desktop, Cursor, or any mcpServers config
{
"mcpServers": {
"baselinker": {
"command": "node",
"args": ["/path/to/baselinker-mcp/dist/index.js"],
"env": { "BASELINKER_API_TOKEN": "your-token" }
}
}
}
For a shared endpoint reachable from claude.ai, see Remote deployment.
Tools
179 separate tools would swamp a model's context and its ability to choose between them, so
methods are grouped the way BaseLinker itself groups them: ten tools, one per API
category. Each takes a method name and a parameters object, and each tool's
description lists the methods it accepts along with their parameters and pagination hints.
Counts below are read + write; write methods only appear when
BASELINKER_ALLOW_WRITES=true.
| Tool | Scope | Methods |
|---|---|---|
baselinker_orders |
Orders, statuses, payments, journal, PickPack carts | 15 + 22 |
baselinker_invoices |
Invoices, invoice files, numbering series, receipts | 6 + 6 |
baselinker_returns |
Order returns, statuses, reasons, payments, journal | 8 + 13 |
baselinker_courier |
Couriers, packages, labels, protocols, documents | 11 + 4 |
baselinker_crm |
CRM clients and statuses | 5 + 6 |
baselinker_inventory |
Catalogs, warehouses, locations, categories, manufacturers, suppliers, payers, tags | 18 + 24 |
baselinker_products |
Product lists, data, stock, prices, logs | 5 + 5 |
baselinker_documents |
Warehouse documents, purchase orders, fulfillment deliveries | 10 + 9 |
baselinker_connect |
Base Connect integrations and contractor credit | 3 + 2 |
baselinker_external_storage |
External storages (shops, wholesalers) | 6 + 1 |
| 87 + 92 |
Parameters are validated against a Zod schema per method before anything is sent, so a malformed call comes back as a readable error instead of a BaseLinker error code. Unknown keys are forwarded untouched — BaseLinker adds parameters without warning, and the server does not break when it does.
Write methods
Disabled by default. To enable:
BASELINKER_ALLOW_WRITES=true
While disabled, write methods are neither listed in any tool's method enum nor callable.
Enabling turns on all 92 at once — creating, updating and deleting orders, products,
stock, prices, invoices, shipments, returns and warehouse documents. Some of them delete
records; some dispatch real courier shipments that cost real money. There is no per-method
gating, so enable writes only for a client you trust, and consider running a second
read-only instance for everything else.
Behaviour worth knowing
Rate limiting. BaseLinker allows 100 requests per minute. A client-side sliding-window limiter enforces it — excess calls wait their turn rather than failing.
Pagination. List responses are capped (typically 100 items for orders, invoices and
returns; 1000 for catalog products). Each method's description carries the specific hint,
for example getOrders wants date_confirmed_from set to the last returned order's
date_confirmed plus one second, while getInventoryProductsList takes a 1-based page.
File downloads. getLabel, getProtocol, getCourierDocument, getInvoiceFile,
getInventoryDocumentFile and getInventoryFulfillmentDeliveryLabels return the file as an
MCP embedded resource with a real MIME type. Pass the extra save_to_path parameter —
handled locally, never sent to BaseLinker — to decode it to disk instead and get back
{ saved_to, extension, bytes }. This only makes sense over stdio, where the server runs on
your own machine; over HTTP it is rejected with an explanatory error.
Remote deployment (HTTP + OAuth)
With --transport http the server speaks Streamable HTTP and acts as an OAuth 2.0
Resource Server (RFC 9728): it publishes
protected resource metadata, answers unauthenticated calls with 401 plus a
WWW-Authenticate challenge, and verifies every access token as an RS256 JWT against a
Keycloak realm's JWKS. Clients discover the realm from that metadata and register
themselves through Dynamic Client Registration, so no client ID or secret is configured
on either side.
node dist/index.js --transport http --host 0.0.0.0 --port 8000 --path /mcp
| Path | Auth | Purpose |
|---|---|---|
POST /mcp |
Bearer | MCP Streamable HTTP, stateless — a fresh server per request |
GET / DELETE /mcp |
Bearer | 405; stateless mode has no server-initiated streams |
/.well-known/oauth-protected-resource[/mcp] |
public | RFC 9728 resource metadata |
/healthz |
public | Liveness probe |
HTTP transport refuses to start without an auth realm unless you opt out explicitly
with BASELINKER_MCP_AUTH_DISABLED=true. That is deliberate: with writes enabled, an
unauthenticated endpoint hands the internet your BaseLinker account.
deploy/ has the full guide — Keycloak realm setup, a hardened Compose service
with Traefik labels, reverse-proxy snippets for Caddy and nginx, verification commands and
a threat model. The short version:
docker build -t baselinker-mcp:0.2.0 .
docker run -d --name baselinker-mcp -p 8000:8000 \
-e BASELINKER_API_TOKEN=your-token \
-e BASELINKER_MCP_AUTH_REALM_URL=https://keycloak.example.com/realms/myrealm \
-e BASELINKER_MCP_AUTH_BASE_URL=https://mcp.example.com \
baselinker-mcp:0.2.0
Then point a client at it:
claude mcp add --transport http baselinker https://mcp.example.com/mcp
In claude.ai it is Settings → Connectors → Add custom connector, URL
https://mcp.example.com/mcp, with Client ID and Client Secret left empty.
One thing to be clear about before you expose it: the BaseLinker token is shared. Everyone who can log into the realm operates on the same BaseLinker account. See SECURITY.md for the rest of the boundaries.
Configuration reference
Everything is an environment variable; .env in the package root is loaded automatically.
Always
| Variable | Default | Purpose |
|---|---|---|
BASELINKER_API_TOKEN |
— | Required. BaseLinker API token |
BASELINKER_ALLOW_WRITES |
false |
true exposes all 92 write methods |
Transport
CLI flags win over these.
| Variable | Flag | Default | Purpose |
|---|---|---|---|
BASELINKER_MCP_TRANSPORT |
--transport |
stdio |
stdio or http |
BASELINKER_MCP_HOST |
--host |
0.0.0.0 |
Bind address, HTTP only |
BASELINKER_MCP_PORT |
--port |
8000 |
Bind port, HTTP only |
BASELINKER_MCP_PATH |
--path |
/mcp |
Endpoint path, HTTP only |
OAuth — required when transport is http
| Variable | Default | Purpose |
|---|---|---|
BASELINKER_MCP_AUTH_REALM_URL |
— | Keycloak realm issuing tokens, e.g. https://keycloak.example.com/realms/myrealm |
BASELINKER_MCP_AUTH_BASE_URL |
— | Public URL of this server; with the path it forms the OAuth resource identifier |
BASELINKER_MCP_AUTH_AUDIENCE |
unset | Audience(s) a token must carry. Needs an audience mapper in Keycloak; unset skips the check |
BASELINKER_MCP_AUTH_REQUIRED_SCOPES |
openid |
Scopes every token must carry. openid guarantees a sub claim |
BASELINKER_MCP_AUTH_DISABLED |
false |
true starts HTTP with no authentication. Never on a public address |
BASELINKER_MCP_ALLOWED_HOSTS |
unset | DNS-rebinding protection: accepted Host headers. Redundant behind a host-routing proxy |
BASELINKER_MCP_ALLOWED_ORIGINS |
unset | DNS-rebinding protection: accepted Origin headers |
Lists accept commas or spaces.
Troubleshooting
| Symptom | Cause |
|---|---|
Missing BASELINKER_API_TOKEN |
No token in the environment or in .env at the package root |
BaseLinker API error [ERROR_AUTH_TOKEN] |
Token rejected by BaseLinker — regenerate it in the panel |
| A write method is "unknown" | BASELINKER_ALLOW_WRITES is not true |
| Calls get slower under load | The rate limiter is pacing you to 100 requests/minute. Working as intended |
HTTP transport requires BASELINKER_MCP_AUTH_REALM_URL |
Set the realm and base URL, or opt out with BASELINKER_MCP_AUTH_DISABLED |
401 no applicable key found in the JSON Web Key Set |
Token was not signed by the configured realm |
403 insufficient_scope |
Token lacks openid |
More OAuth-specific cases are in deploy/README.md.
Development
npm run dev # run from sources (tsx), stdio transport
npm run start:http # built server, HTTP transport
npm test # unit tests — fully offline, no live API calls
npm run check # format check + typecheck + tests, what CI runs
npm run smoke # manual smoke test against the live API (uses .env)
npm run inspect # MCP Inspector against the built server
CONTRIBUTING.md covers how the tool registry is put together and what to watch out for when adding a method.
License
MIT. Not affiliated with or endorsed by BaseLinker.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。