moysklad-analytics-mcp
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@moysklad-analytics-mcpWhat were our top selling products this month?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MoySklad Analytics
Interactive dashboard over the MoySklad JSON API 1.2 — sales & profit by product, stock & restocking, customers & channels, and trends over time.
Host note
moysklad.uz is a localized front-end for the same MoySklad platform — its login
button points at online.moysklad.ru, and there is no api.moysklad.uz
(the hostname does not resolve). Tokens issued through the .uz site work
against the standard API host:
https://api.moysklad.ru/api/remap/1.2Two quirks worth knowing, both handled in server/src/moysklad/moysklad.service.ts:
The API returns a bare nginx 415 to any client that does not advertise gzip.
Account limits are 45 requests / 3 seconds and 5 concurrent; breaching either returns 429 with a
X-RateLimit-Retry-Afterheader (milliseconds).
Related MCP server: Company Data MCP Server
Setup
pnpm setup # install server + web deps
cp .env.example .env # then put your token in it
echo 'MOYSKLAD_TOKEN=xxxxxxxx' > .envGet a token in the MoySklad UI under Настройки → Обмен данными → Токены доступа к JSON API, or:
curl -X POST https://api.moysklad.ru/api/remap/1.2/security/token \
-H "Authorization: Basic $(printf 'login:password' | base64)" \
--compressedCheck the account first
pnpm probeThis validates the token and prints what the account actually holds: identity, row counts per entity, stores, currency, document volumes, the date range of real sales, and a reachability check across all 14 report endpoints with sample row shapes. Run it before trusting any number on the dashboard.
Permissions matter. MoySklad hides cost price, purchase price and profit from users who lack those rights. If the token belongs to a restricted user the profit and margin columns come back empty — the probe shows this immediately, and the dashboard labels such panels "нет прав" rather than "нет данных".
Run
pnpm dev # API on :8787, dashboard on :5173Open http://localhost:5173. The browser never sees the token — it talks only to the Vite proxy, which forwards to the NestJS API.
pnpm build # production build of both
pnpm typecheckLayout
server/ NestJS 11 + TypeScript
src/moysklad/
moysklad.service.ts auth, gzip, pagination, rate limiter, retry
rate-limiter.ts 40 req/3s, 5 concurrent
moysklad.types.ts report row shapes
src/stats/
stats.service.ts report -> dashboard metrics, 5-minute TTL cache
stats.controller.ts GET /api/stats/*
period.dto.ts query validation
src/common/
api-exception.filter.ts upstream errors -> messages the UI can show
src/probe.ts account discovery
web/ Vite 8 + React 19 + HeroUI v3 + Tailwind v4 + Recharts
src/pages/ Overview, Products, Stock, Customers, Trends
src/components/ Panel, KpiCard, DataTable, charts, PeriodPicker
src/lib/ api client, formatters, typesAPI
All endpoints take ?from=YYYY-MM-DD&to=YYYY-MM-DD (default: last 30 days).
Endpoint | Returns |
| stores, organizations, default currency |
| headline KPIs with period-over-period deltas |
| per-product revenue/profit/margin, loss makers, returns, Pareto |
| stock value, days of cover, restock list, dead & overstocked |
| top customers, repeat rate, concentration, channels, employees |
| sales, orders and cash series |
| drop the TTL cache |
| whether the assistant has an API key |
| SSE stream — |
| MCP (Streamable HTTP) for external AI clients — see below |
Field semantics — verified against the live account
Four things the documentation does not make obvious, each confirmed against real rows and each a wrong number if handled naively:
Money arrives in minor units and is divided by 100. Confirmed: a bar priced at
sellPrice: 9642.86withsellSum: 67500for 7 units is $96.43 each, not $9,642.marginandsalesMarginare different ratios, both fractions.margin= profit / cost (наценка) andsalesMargin= profit / revenue (маржа). The dashboard showssalesMarginas "Маржа" andmarginas "Наценка", each scaled by 100. Rendering the rawmarginas a percentage reports 0.6% where the truth is 60.7%./report/stock/allreturns hrefs with a?expand=suppliersuffix while the profit report returns bare ones. Joining them raw matches nothing and silently classifies the entire catalogue as dead stock.The group profit reports count documents, not units —
salesCount, notsellQuantity, which does not exist on them.The money series uses
credit/debit, notincome/outcome.
Judgement calls that remain yours:
Average cheque divides shipment revenue by shipment count (
/entity/demandin the window), not by customer-order count — the two differ, and mixing them made Overview disagree with the Customers page.Days of cover uses velocity over the selected window only, so a short window exaggerates urgency on seasonal items.
Dead stock means zero sales in the selected window, not all-time.
AI assistant
The Ассистент tab answers natural-language questions over the same data —
"what should we reorder", "how are treadmills selling", "who are our biggest
customers". Set DEEPSEEK_API_KEY (get one here)
to enable it; without one the tab reports that it is off and nothing else changes.
It runs DeepSeek (deepseek-v4-flash, OpenAI-compatible API) with function
calling over seven read-only tools that wrap StatsService, so the assistant and
the dashboard panels read exactly the same numbers — there is no second
definition of "revenue" to drift. Answers stream to the browser over SSE.
The tools are the assistant's only access to the account: it cannot write to MoySklad, and it cannot reach anything the dashboard cannot.
Four things worth knowing if you change it:
Why this model. Benchmarked against the alternatives on the real account.
deepseek-v4-flash($0.14 in / $0.28 out per 1M, $0.0028 on cache hits) scored 4/4 on tool selection at ~2s per round — cheapest and fastest of everything tried.deepseek-v4-pro($0.435/$0.87) was ~2x slower with no accuracy gain. Local models were tested too and rejected: see the note below.The balance is prepaid. A depleted balance returns
402 Insufficient Balance, surfaced in the UI as a readable message rather than a stack trace.Let the tools default the dates. The prompt tells the model to omit
from/tounless the question names a period. Left to itself it computed "last 30 days" as a 31-day window — figures correct for its window, but silently disagreeing with the dashboard preset for the same phrase.The catalogue is in Russian, and the model is not.
search_productsmatches on any single token and, on a miss, returns the account's category names so the model can retry with a real term.The field semantics are in the prompt on purpose. Margin vs markup, the average-cheque basis, what "dead stock" counts — the same traps documented above. Without them the model reports plausible, wrong numbers.
Why not a local model
qwen3:8b and qwen3:4b were benchmarked on the production host via Ollama.
Tool selection was fine (4/4), but the box has no GPU: a single model round took
46–198s warm, so one answer (2–3 rounds) would take 3–5 minutes against ~2s for
DeepSeek. Worse, unconstrained inference took 7.7 of 8 cores and pushed
the production admin panel from 0.5s to 5.5s — the same host runs the API, admin,
bot, Postgres and MinIO. Capping it to 4 cores (CPUQuota=400%) protected
production but made inference slower still. Revisit only on a machine with a GPU.
The catalogue is in Russian, and the model is not. Asked about "treadmills", every model searched the English word and would have found nothing.
search_productstherefore matches on any single token and, on a miss, returns the account's category names so the model can retry with a real term — and the prompt tells it to search in Russian.The field semantics are in the prompt on purpose. Margin vs markup, the average-cheque basis, what "dead stock" counts — the same traps documented above. Without them the model reports plausible, wrong numbers.
MCP endpoint — the same data for external AI
POST /api/mcp speaks MCP over Streamable
HTTP (stateless, JSON responses), so any MCP client — Claude, Cursor, ChatGPT
connectors, another agent — can query the account directly.
The tool surface is the assistant's seven, plus seven business tools, plus raw:
the seven curated analytics tools (
get_account_info,get_overview,search_products,get_products,get_stock,get_customers,get_trends) — the samebuildTools(stats)objects the in-dashboard assistant uses, so an external model and the dashboard cannot disagree on what "revenue" means;seven business tools (
server/src/mcp/business-tools.ts), shaped by probing what the account actually holds:get_documents(any of 16 document types for a period — orders, shipments, cash, write-offs, transfers, ...),get_document(one document with line items),get_money(balances per org/account, cash vs bank flow, expenses by category — the cash desk is this account's busiest surface),get_purchases(supplies + purchase orders by supplier),get_debts(mutual settlements both directions),search_counterparties(lookup + per-counterparty history),get_turnover(opening/received/issued/closing per product). All money already in major units;moysklad_api— raw read-only GET against anyentity/...orreport/...path, for everything the curated tools don't cover (counterparties, purchase documents, cash flow, single objects by id). Structurally read-only: the underlying client only issues GET. Results are capped at 100 rows / ~60k characters andmetaobjects are trimmed tohref+type. Raw responses are in minor units (kopecks/tiyin) — the tool description tells the model to divide by 100; the curated tools already return major units.
Enable it by setting MCP_TOKEN (min 24 chars, openssl rand -hex 32).
Without MCP_TOKEN the route simply stays behind Basic auth like everything
else. With it, clients get in two ways:
1. Static bearer — for Claude Code and scripts:
claude mcp add moysklad --transport http https://your-deployment.example.com/api/mcp \
--header "Authorization: Bearer <MCP_TOKEN>"2. OAuth login — for the claude.ai and ChatGPT apps (including mobile),
whose connector UIs cannot send custom headers. Add a custom connector with
just the URL https://your-deployment.example.com/api/mcp; the app discovers the
OAuth endpoints, opens the login page, and the user signs in with the
dashboard credentials (DASHBOARD_USER / DASHBOARD_PASSWORD). In
claude.ai: Settings → Connectors → Add custom connector (connectors added on
the web are then available in the mobile app). In ChatGPT: Settings →
Connectors (Developer mode required for custom MCP).
The OAuth server is the full MCP-spec stack — RFC 9728 resource metadata, RFC 8414 AS metadata, RFC 7591 dynamic client registration, authorization code
PKCE (S256, enforced), refresh tokens — implemented in
server/src/oauth/. Design choices worth knowing:
Stateless everything. Codes, tokens and client registrations are HMAC-signed blobs; there is no database, and a redeploy logs nobody out. The signing key is
MCP_TOKENitself, so rotating that one value revokes the static credential and every OAuth grant at once.One human identity. The login form checks the dashboard's Basic-auth credentials — same person, same password, however they arrive.
Access tokens live 30 days, refresh 180. The only server-side state is the used-code set (single-use enforcement); losing it on restart is a 10-minute replay window, accepted.
Login is throttled globally (20 failures/minute), not per-IP — behind the proxy every client is one IP anyway.
PUBLIC_URL pins the origin used in OAuth metadata; unset, it is derived from
Host/X-Forwarded-Proto, which is correct behind Caddy.
Authentication
The server enforces HTTP Basic auth and refuses to start when
NODE_ENV=production without DASHBOARD_USER and DASHBOARD_PASSWORD. Only
/api/health is exempt, and it reports nothing but liveness. /api/mcp has
its own Bearer token (above) and is exempt from Basic auth only when that token
is configured.
This is not optional hardening. The dashboard shows revenue, cost prices, per-product margins and every customer with what they spend; on a public host an unguarded URL is not obscure, because the certificate transparency log publishes the hostname minutes after Caddy issues the certificate.
In development, omitting the variables runs without auth and logs a warning.
Deployment
docker build -t moysklad-stats:latest . # API + built SPA in one imageTool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
No tool schema history has been recorded yet.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Query your org's data in natural language — read-only MCP access to SQL, NoSQL, files & warehouses.
Let AI agents query data and act across all your business apps via MCP.
The HubSpot MCP Server acts as a bridge that enables AI assistants and Large Language Models to securely interact with HubSpot CRM data through natural conversation, without requiring users to understand complex API structures. It provides read-only access to standard CRM objects (contacts, companies, deals, tickets, products, invoices, and more) and their associations, secured via OAuth 2.0, allowing AI agents to perform tasks like summarizing deals, fetching company updates, and looking up record changes.
Read-only NuMetric.work accounting & ERP data: statements, KPIs, reports, invoices, documents.
Related MCP Servers
- AlicenseNot gradedqualityBmaintenanceA read-only MCP server for the NuMetric.work accounting/POS/ERP platform, exposing 38 tools to query live business data such as financial statements, invoices, taxes, projects, inventory, and documents. It enables AI assistants to answer from real accounting data without any create, edit, or delete capabilities.MIT
- FlicenseNot gradedqualityCmaintenanceEnables read-only access to company data across PostgreSQL, MongoDB Atlas, and flat files through MCP tools, allowing AI assistants to query and retrieve information via natural language.-
- FlicenseBqualityCmaintenanceA secure, read-only MCP server that enables AI assistants to inspect transactions, vendor performance, wallet balances, and analytics through validated REST API calls.19-
- FlicenseNot gradedqualityBmaintenanceEnables AI assistants to query internal business data for insights into customers, revenue, subscriptions, sales, and churn through controlled, read-only MCP tools.-
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/Abdulaziz1224/moysklad-analytics-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server