# MCP server

> Reference for the Haszno MCP server — install, configuration, and all 51 implemented tools with input schemas and examples.

## Overview

The Haszno MCP server (`haszno-mcp`) exposes the Haszno API as a set of `haszno_*` tools to any MCP-compatible AI agent client: Claude Code, Claude Desktop, Cursor, and others. It communicates over **stdio** using the Model Context Protocol.

The MCP server reads its credentials from environment variables or from the config file `~/.config/haszno/config.json`. That file is written by the [CLI](cli.md) command `haszno login` (or by the `haszno_account_configure` tool). The CLI reads the same file, so one login configures both; flags and environment variables take precedence over it.

> [!NOTE]
> This is a developer preview. The hosted demo at `https://haszno.com` returns synthetic sample data. Write operations and protected endpoints return `401` on the demo unless you have credentials issued by the Haszno team.

---

## Transport

The server uses `StdioServerTransport` from `@modelcontextprotocol/sdk`. Every MCP client that supports stdio-based servers is compatible.

---

## Installation

### Recommended: unified installer

The one-step installer in the skill repo installs the MCP server, the CLI, and the agent skill together:

```bash
curl -fsSL https://raw.githubusercontent.com/haszno-labs/haszno-skill/main/install.sh | bash
```

What the script does:

1. Clones `haszno-mcp` (and `haszno-cli`, `haszno-skill`) into `~/.haszno/`.
2. Runs `npm install && npm run build` for the MCP server.
3. Registers the server with Claude Code: `claude mcp add haszno --scope user -- node ~/.haszno/haszno-mcp/dist/index.js` (skipped if `claude` is not found).
4. Copies the skill files to `~/.claude/skills/haszno/`.

### Manual install

```bash
git clone https://github.com/haszno-labs/haszno-mcp ~/.haszno/haszno-mcp
cd ~/.haszno/haszno-mcp
npm install
npm run build
```

Then register with your client (see [Client setup](#client-setup)).

---

## Client setup

### Claude Code

```bash
claude mcp add haszno --scope user -- node ~/.haszno/haszno-mcp/dist/index.js
```

### Generic mcpServers JSON

Use this block in `claude_desktop_config.json`, `.cursor/mcp.json`, or any MCP client config that accepts `mcpServers`:

```json
{
  "mcpServers": {
    "haszno": {
      "command": "node",
      "args": ["~/.haszno/haszno-mcp/dist/index.js"],
      "env": {
        "HASZNO_API_URL": "https://haszno.com",
        "HASZNO_API_KEY": "your-api-key"
      }
    }
  }
}
```

> [!TIP]
> You can omit `HASZNO_API_KEY` to use the public demo. Only the tools listed in [What works without credentials](#auth-public) work without a key.

### npx variant

The packages are not published to the npm registry (both have `"private": true` in `package.json`). An `npx` command referencing the GitHub source works if you have Node 20+ and network access:

```bash
claude mcp add haszno -- npx -y github:haszno-labs/haszno-mcp
```

---

## Configuration

### Environment variables

| Variable | Description |
|---|---|
| `HASZNO_API_URL` | API base URL. Default: `https://haszno.com`. |
| `HASZNO_API_KEY` | API key, sent as the `x-api-key` header. |
| `HASZNO_BASIC` | `user:pass` for HTTP Basic auth. Used only when no API key is set. |
| `ARFUTAR_API_URL` | Legacy fallback for `HASZNO_API_URL`. |
| `ARFUTAR_API_KEY` | Legacy fallback for `HASZNO_API_KEY`. |
| `ARFUTAR_BASIC` | Legacy fallback for `HASZNO_BASIC`. |

### Config file

The MCP server reads `~/.config/haszno/config.json` and, if that file does not exist, `~/.config/arfutar/config.json`. The file is written by the CLI command `haszno login` or by the `haszno_account_configure` tool. Unlike the CLI, the MCP server does not check that the saved key belongs to the URL in use: if you override `HASZNO_API_URL` for the MCP server, set `HASZNO_API_KEY` explicitly as well.

```json
{
  "apiUrl": "https://haszno.com",
  "apiKey": "haszno_live_..."
}
```

Instead of `apiKey`, the file can hold `basicAuth` (`user:pass`).

### Precedence (highest to lowest)

1. `HASZNO_API_URL` / `HASZNO_API_KEY` / `HASZNO_BASIC` environment variables.
2. `ARFUTAR_API_URL` / `ARFUTAR_API_KEY` / `ARFUTAR_BASIC` environment variables (legacy fallback, used only when the matching `HASZNO_*` variable is not set).
3. `~/.config/haszno/config.json`.
4. `~/.config/arfutar/config.json`.

The config file is read only when neither an API key nor Basic credentials come from the environment, and only once, when the server starts. Restart the MCP server after changing the file. The API key is always sent as the `x-api-key` header; Basic credentials are used only when there is no API key.

> [!NOTE]
> `haszno_feeds_outbound_info` and `haszno_orders_create_demo` look only at the `HASZNO_API_URL` / `ARFUTAR_API_URL` environment variables, never at `apiUrl` in the config file.

### What works without credentials

Against `https://haszno.com` the following tools work without an API key and return synthetic demo data: `haszno_ping`, `haszno_account_get_stats`, `haszno_account_get_impact`, `haszno_account_get_settings`, `haszno_account_list_suppliers`, `haszno_orders_list`, `haszno_orders_get`, `haszno_proposals_list`, `haszno_proposals_market`, `haszno_import_list_list`, `haszno_peers_invite_list`, and `haszno_blog_latest`. `haszno_feeds_outbound_info` without `fetch_summary` and `haszno_account_configure` make no API call at all.

All other tools — every write tool, and read tools such as `haszno_catalog_*`, `haszno_catalogue_search`, `haszno_connections_list`, `haszno_ideas_list` and the `haszno_rules_*` / `haszno_automation_get` readers — return `401` without credentials.

---

## Implemented tools

The server registers exactly **51 tools** in 11 domains. The tools for the twelfth domain, own-shop products, are not implemented yet — see [Planned tools](#planned).

| Domain | Tools |
|---|---|
| [Account & settings](#tools-account) | 7 |
| [Supplier catalogue](#tools-catalog) | 6 |
| [Partner catalogue search and import list](#tools-catalogue) | 4 |
| [Store connections](#tools-connections) | 1 |
| [Price proposals](#tools-proposals) | 5 |
| [Automation rules](#tools-rules) | 12 |
| [Orders](#tools-orders) | 5 |
| [Ideas](#tools-ideas) | 4 |
| [Feeds](#tools-feeds) | 3 |
| [P2P peer invites](#tools-peers) | 3 |
| [Blog / market insights](#tools-blog) | 1 |

### The confirm parameter

Most write tools take a `confirm` parameter, but it behaves in one of four ways. Each tool's parameter table says which one applies:

- **`true` (literal):** the schema accepts only `true`. Omitting it or passing `false` fails input validation, and nothing is sent.
- **boolean, preview on `false`:** `confirm` is required. With `false` the tool returns a preview and makes no API call.
- **boolean, error on `false`:** `confirm` is required. With `false` the tool returns a "Safety gate" error and makes no API call.
- **boolean, default `false`:** `confirm` is optional. When omitted or `false`, the tool returns a dry-run summary and makes no API call.

The badge shows whether the tool changes data: **Read-only** tools only read (some still need `confirm: true` because they can start a paid AI call), and **Write** tools change data.

---

## Account & settings

| Tool | Mode | Description |
|---|---|---|
| `haszno_ping` | **Read-only** | Check API connectivity. |
| `haszno_account_get_stats` | **Read-only** | Dashboard snapshot: product, losing, over-market, to-fix and order counts. |
| `haszno_account_get_impact` | **Read-only** | Profit impact for a time window. |
| `haszno_account_get_settings` | **Read-only** | Current pricing rules. |
| `haszno_account_list_suppliers` | **Read-only** | The shop's own supplier integrations. |
| `haszno_account_update_settings` | **Write** | Update the pricing rules. |
| `haszno_account_configure` | **Write** | Save API credentials to the local config file (no API call). |

### haszno_ping

Calls `GET /api/health`. **Read-only**

**When to call:** before any other tool, to verify connectivity.

**Input parameters:** none.

**Returns:** `{ ok: true }` on success.

**Example call:**

```json
{
  "name": "haszno_ping",
  "arguments": {}
}
```

---

### haszno_account_get_stats

Returns a dashboard snapshot of the shop (`GET /api/stats`). **Read-only**

**When to call:** at the start of a daily health check, or when the user asks for an overview.

**Input parameters:** none.

**Returns:** an object with:

| Field | Type | Description |
|---|---|---|
| `products` | integer | Number of products in the catalogue. |
| `losing` | integer | Products currently priced below cost. |
| `overMarket` | integer | Products priced above the cheapest market competitor. |
| `toFix` | integer | Products with a pending pricing problem. |
| `orders` | integer | Number of orders received. |

**Example call:**

```json
{
  "name": "haszno_account_get_stats",
  "arguments": {}
}
```

---

### haszno_account_get_impact

Returns the profit impact of Haszno's pricing over the last N days (`GET /api/impact`). **Read-only**

**When to call:** when the user asks about extra profit, margin gains, or automatic order forwarding.

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `days` | integer, 1–365 | No | Look-back window in days. When omitted, the API uses 30. |

**Returns:** `{ days, extraProfit, baseline, pctGain, orders, autoForwardedOrders }` — money values in gross HUF.

**Example call:**

```json
{
  "name": "haszno_account_get_impact",
  "arguments": { "days": 7 }
}
```

---

### haszno_account_get_settings

Returns the shop's pricing configuration (`GET /api/settings`). **Read-only**

**When to call:** before proposing any pricing change, and always before `haszno_account_update_settings`.

**Input parameters:** none.

**Returns:** `{ undercutPct, minMarginPct, vatPct, automationPaused }`.

**Example call:**

```json
{
  "name": "haszno_account_get_settings",
  "arguments": {}
}
```

---

### haszno_account_list_suppliers

Returns the shop's own supplier integrations — the suppliers whose catalogues have been imported (`GET /api/suppliers`). This is not the network list; for that, use `haszno_catalog_list_suppliers`. **Read-only**

**Input parameters:** none.

**Returns:** an array of `{ id, name, orderMode, email, network, category, country, shopUrl, createdAt }`.

**Example call:**

```json
{
  "name": "haszno_account_list_suppliers",
  "arguments": {}
}
```

---

### haszno_account_update_settings

Replaces the shop's pricing rules (`PUT /api/settings`). All three numeric fields are required, so read the current values with `haszno_account_get_settings` first and send back the ones you do not change. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `undercutPct` | number, 0–100 | **Yes** | How many percent to undercut the cheapest market price. |
| `minMarginPct` | number, 0–100 | **Yes** | Minimum net profit margin percentage. |
| `vatPct` | number, 0–100 | **Yes** | VAT rate for gross/net conversion (e.g. `27`). |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** the updated settings object.

**Example call:**

```json
{
  "name": "haszno_account_update_settings",
  "arguments": { "undercutPct": 1, "minMarginPct": 12, "vatPct": 27, "confirm": true }
}
```

---

### haszno_account_configure

Writes credentials to `~/.config/haszno/config.json` (file mode `600`), keeping other keys already in the file. It makes no API call. The running server keeps the credentials it loaded at startup, so restart it afterwards. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `apiUrl` | string (URL) | **Yes** | API base URL, e.g. `https://haszno.com`. |
| `apiKey` | string | No | API key, sent as the `x-api-key` header. |
| `basicAuth` | string, `user:pass` | No | HTTP Basic credentials. |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

Provide exactly one of `apiKey` and `basicAuth`; the tool returns an error if both or neither are given.

**Returns:** `{ saved: true, configFile, apiUrl, authMethod }`.

**Example call:**

```json
{
  "name": "haszno_account_configure",
  "arguments": { "apiUrl": "https://haszno.com", "apiKey": "haszno_live_...", "confirm": true }
}
```

---

## Supplier catalogue (`haszno_catalog_*`)

The network of partner suppliers the shop can connect to. `haszno_catalog_list_categories` and `haszno_catalog_list_suppliers` both fetch `GET /api/network/suppliers` and filter and paginate locally.

| Tool | Mode | Description |
|---|---|---|
| `haszno_catalog_list_categories` | **Read-only** | Distinct categories in the partner network. |
| `haszno_catalog_list_suppliers` | **Read-only** | List network suppliers. |
| `haszno_catalog_supplier_products` | **Read-only** | A supplier's product catalogue. |
| `haszno_catalog_supplier_view` | **Read-only** | Aggregated reseller market view for a supplier. |
| `haszno_catalog_connect_supplier` | **Write** | Connect a network supplier and import its products. |
| `haszno_catalog_sync_supplier` | **Write** | Refresh cost and stock from a connected supplier. |

### haszno_catalog_list_categories

**Read-only**

**When to call:** to find valid `category` values for `haszno_catalog_list_suppliers` and `haszno_catalog_supplier_products`.

**Input parameters:** none.

**Returns:** `{ categories: string[] }`, sorted alphabetically.

**Example call:**

```json
{
  "name": "haszno_catalog_list_categories",
  "arguments": {}
}
```

---

### haszno_catalog_list_suppliers

**Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `category` | string | No | Filter by category (case-insensitive exact match). |
| `search` | string | No | Case-insensitive substring search on the supplier name. |
| `connected` | boolean | No | `true` shows only suppliers the shop already imports from. `false` does not filter. |
| `page` | integer, min 1 | No | Page number. Default: `1`. |
| `limit` | integer, 1–100 | No | Items per page. Default: `25`. |

**Returns:** `{ suppliers: [{ id, name, category, country, connected, productCount, avgResellerMarginPct, shopUrl }], total, page, pages }`.

**Example call:**

```json
{
  "name": "haszno_catalog_list_suppliers",
  "arguments": { "category": "kert", "limit": 10 }
}
```

---

### haszno_catalog_supplier_products

Returns products from a partner supplier's catalogue (`GET /api/network/suppliers/:id/products`), filtered and paginated locally. **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `supplier_id` | integer | **Yes** | Supplier ID (from `haszno_catalog_list_suppliers`). |
| `search` | string | No | Case-insensitive substring search on product name or SKU. |
| `category` | string | No | Filter by product category (case-insensitive). |
| `page` | integer, min 1 | No | Page number. Default: `1`. |
| `limit` | integer, 1–100 | No | Items per page. Default: `25`. |

**Returns:** `{ products: [{ id, sku, name, ean, category, wholesaleNet, rrpGross, stock, potentialMargin }], total, page, pages }`. `wholesaleNet` is net HUF; `rrpGross` and `potentialMargin.priceGross` are gross HUF.

**Example call:**

```json
{
  "name": "haszno_catalog_supplier_products",
  "arguments": { "supplier_id": 3 }
}
```

---

### haszno_catalog_supplier_view

Returns supplier metadata and, per product, aggregated reseller-market statistics (`GET /api/network/supplier-view/:supplierId`): `resellerCount`, `minGross` / `avgGross` / `maxGross`, `belowRrp`, `lossCount`, and `saturation` (0–100). **Read-only**

**When to call:** to evaluate a supplier before connecting it.

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `supplier_id` | integer | **Yes** | Supplier ID. |

**Example call:**

```json
{
  "name": "haszno_catalog_supplier_view",
  "arguments": { "supplier_id": 3 }
}
```

---

### haszno_catalog_connect_supplier

Connects a partner supplier and imports all its products into the shop (`POST /api/network/suppliers/:id/connect`). **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `supplier_id` | integer | **Yes** | Supplier ID. |
| `confirm` | boolean, preview on `false` | **Yes** | `true` executes; `false` returns a preview without an API call. |

**Returns:** `{ status, imported, skipped }`.

**Example call:**

```json
{
  "name": "haszno_catalog_connect_supplier",
  "arguments": { "supplier_id": 3, "confirm": true }
}
```

---

### haszno_catalog_sync_supplier

Synchronises wholesale prices and stock from a connected partner supplier (`POST /api/network/suppliers/:id/sync`). **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `supplier_id` | integer | **Yes** | ID of a connected supplier. |
| `confirm` | boolean, preview on `false` | **Yes** | `true` executes; `false` returns a preview without an API call. |

**Returns:** `{ costChanges, stockOuts, total }`.

**Example call:**

```json
{
  "name": "haszno_catalog_sync_supplier",
  "arguments": { "supplier_id": 3, "confirm": true }
}
```

---

## Partner catalogue search and import list

| Tool | Mode | Description |
|---|---|---|
| `haszno_catalogue_search` | **Read-only** | Search the partner product catalogue. |
| `haszno_import_list_list` | **Read-only** | List items on the import list. |
| `haszno_import_list_add` | **Write** | Add catalogue products to the import list. |
| `haszno_import_list_push` | **Write** | Push import-list items to a connected webshop. |

### haszno_catalogue_search

Searches the partner product catalogue (`GET /api/catalogue/products`). **Read-only**

**When to call:** when the user wants to discover new products to list.

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `q` | string | No | Free-text search on product name or category. |
| `cats` | string | No | Comma-separated category keys. |
| `partner` | integer, positive | No | Filter by supplier/partner ID. |
| `minMarginPct` | number | No | Minimum margin percentage. |
| `maxPrice` | number | No | Maximum RRP, gross HUF. |
| `minPrice` | number | No | Minimum RRP, gross HUF. |
| `inStock` | boolean | No | `true` returns only products in stock. |
| `hasImage` | boolean | No | `true` returns only products with an image. |
| `country` | string | No | Supplier country code, e.g. `HU`, `DE`. |
| `sort` | `margin_desc` \| `price_asc` \| `price_desc` \| `name_asc` | No | Sort order. Default: `name_asc`. |
| `page` | integer, min 1 | No | Page number. Default: `1`. |
| `pageSize` | integer, 1–100 | No | Items per page. Default: `24`. |

**Returns:** `{ items, total, facets }`.

**Example call:**

```json
{
  "name": "haszno_catalogue_search",
  "arguments": { "cats": "kert", "minMarginPct": 20, "inStock": true, "sort": "margin_desc" }
}
```

---

### haszno_import_list_list

Lists the items on the import list, the staging area between the catalogue and a webshop (`GET /api/import-list`). **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `status` | `varakozik` \| `kuldve` \| `kesz` \| `hiba` | No | Filter by push status (waiting, sent, done, error). |
| `partner` | integer, positive | No | Filter by supplier/partner ID. |
| `q` | string | No | Free-text search on product name or SKU. |

**Returns:** `{ items, total }`.

**Example call:**

```json
{
  "name": "haszno_import_list_list",
  "arguments": { "status": "varakozik" }
}
```

---

### haszno_import_list_add

Adds catalogue products to the import list (`POST /api/import-list`). No product is created in a webshop until `haszno_import_list_push` runs. This tool has no `confirm` parameter. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `networkProductIds` | integer[] (positive), 1–100 items | **Yes** | Catalogue product IDs (from `haszno_catalogue_search`). |

**Returns:** `{ added, skipped }` — `skipped` counts products already on the list.

**Example call:**

```json
{
  "name": "haszno_import_list_add",
  "arguments": { "networkProductIds": [101, 102, 103] }
}
```

---

### haszno_import_list_push

Pushes import-list items to a connected webshop. The connection must have status `kapcsolva` (check with `haszno_connections_list`). **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `ids` | integer[] (positive), at least 1 | **Yes** | Import-list item IDs (from `haszno_import_list_list`). |
| `targetConnectionId` | integer, positive | No | Store connection ID. With it, the tool calls `POST /api/connect/stores/:id/push` and returns `{ pushed, failed, errors }`. Without it, it calls `POST /api/import-list/push` and returns `{ pushed }`. |
| `confirm` | boolean, preview on `false` | **Yes** | `true` executes; `false` returns a preview without an API call. |

**Example call:**

```json
{
  "name": "haszno_import_list_push",
  "arguments": { "ids": [201, 202], "targetConnectionId": 5, "confirm": true }
}
```

---

## Store connections

| Tool | Mode | Description |
|---|---|---|
| `haszno_connections_list` | **Read-only** | List connected webshops. |

### haszno_connections_list

Lists the shop's webshop connections (`GET /api/connect/stores`); the status filter is applied locally. **Read-only**

**When to call:** before `haszno_import_list_push`, to find the `targetConnectionId`.

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `status` | `uj` \| `domain_ok` \| `kapcsolva` \| `hiba` | No | Filter by connection status. |

**Returns:** `{ connections: [{ id, platform, name, status, domain, lastError, connectedAt, lastSyncAt }] }`.

**Example call:**

```json
{
  "name": "haszno_connections_list",
  "arguments": { "status": "kapcsolva" }
}
```

---

## Price proposals

| Tool | Mode | Description |
|---|---|---|
| `haszno_proposals_list` | **Read-only** | List price proposals. |
| `haszno_proposals_market` | **Read-only** | A product's pending proposals with market context. |
| `haszno_proposals_approve` | **Write** | Approve proposals and apply the prices. |
| `haszno_proposals_reject` | **Write** | Reject proposals. |
| `haszno_proposals_rollback` | **Write** | Roll back an approved batch. |

> [!WARNING]
> While the automation kill switch is on (`haszno_automation_get` returns `paused: true`), the API refuses proposal approval with HTTP `409`. Check the switch if `haszno_proposals_approve` fails with `409`.

### haszno_proposals_list

Lists price proposals (`GET /api/proposals`). The product filter and the pagination are applied locally. **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `status` | `pending` \| `approved` \| `rejected` \| `rolled_back` | No | Filter by status. |
| `productId` | integer, positive | No | Only proposals for this product. |
| `limit` | integer, 1–100 | No | Maximum proposals to return. Default: `25`. |
| `offset` | integer, min 0 | No | Number of proposals to skip. Default: `0`. |

**Returns:** `{ _meta: { total, offset, limit, hasMore }, proposals: [{ id, productId, productName, productSku, oldPriceGross, proposedPriceGross, diffGross, reason, source, status, batchId, createdAt, decidedAt }] }`.

**Example call:**

```json
{
  "name": "haszno_proposals_list",
  "arguments": { "status": "pending" }
}
```

---

### haszno_proposals_market

Returns a product with its current, recommended and market prices, plus its pending proposals, newest first. It reads `GET /api/products` and `GET /api/proposals?status=pending`. **Read-only**

**When to call:** before approving or rejecting a product's proposals.

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `productId` | integer, positive | **Yes** | Product ID (from `haszno_proposals_list`). |

**Returns:** `{ product: { id, sku, name, supplierName, costNet, stock, listed, current, recommended, market, rrpGross }, pendingProposals: [...] }`.

**Example call:**

```json
{
  "name": "haszno_proposals_market",
  "arguments": { "productId": 42 }
}
```

---

### haszno_proposals_approve

Approves proposals and applies the prices immediately (`POST /api/proposals/approve`). Save the returned `batchId` — `haszno_proposals_rollback` needs it. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `ids` | integer[] (positive) | No | Proposal IDs to approve. Omit to approve **all** pending proposals. |
| `confirm` | boolean, error on `false` | **Yes** | Must be `true`. |

**Returns:** `{ batchId, applied, note }`.

**Example call:**

```json
{
  "name": "haszno_proposals_approve",
  "arguments": { "ids": [42, 43], "confirm": true }
}
```

---

### haszno_proposals_reject

Rejects proposals (`POST /api/proposals/reject`). Prices do not change; the proposals stay in the history with status `rejected`. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `ids` | integer[] (positive), at least 1 | **Yes** | Proposal IDs to reject. |
| `confirm` | boolean, error on `false` | **Yes** | Must be `true`. |

**Returns:** `{ rejected, note }`.

**Example call:**

```json
{
  "name": "haszno_proposals_reject",
  "arguments": { "ids": [44], "confirm": true }
}
```

---

### haszno_proposals_rollback

Restores the prices from before an approved batch (`POST /api/proposals/rollback`); the proposals change to `rolled_back`. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `batchId` | string, non-empty | **Yes** | The `batchId` returned by `haszno_proposals_approve`. |
| `confirm` | boolean, error on `false` | **Yes** | Must be `true`. |

**Returns:** `{ rolledBack, note }`.

**Example call:**

```json
{
  "name": "haszno_proposals_rollback",
  "arguments": { "batchId": "b-1234abcd", "confirm": true }
}
```

---

## Automation rules

| Tool | Mode | Description |
|---|---|---|
| `haszno_rules_list_templates` | **Read-only** | Built-in rule templates. |
| `haszno_rules_list` | **Read-only** | All rules of the shop. |
| `haszno_rules_list_runs` | **Read-only** | Rule execution log. |
| `haszno_automation_get` | **Read-only** | Automation kill-switch state. |
| `haszno_rules_create` | **Write** | Create a rule. |
| `haszno_rules_update` | **Write** | Update a rule. |
| `haszno_rules_delete` | **Write** | Delete a rule and its run history. |
| `haszno_rules_run` | **Write** | Run one rule manually. |
| `haszno_rules_run_all` | **Write** | Run all enabled `manual` rules. |
| `haszno_rules_trigger` | **Write** | Fire the enabled rules of a trigger event. |
| `haszno_rules_undo_unlists` | **Write** | Re-list products a rule unlisted. |
| `haszno_automation_set` | **Write** | Pause or resume automation. |

### haszno_automation_get

Returns the global automation kill switch (`GET /api/automation`). While `paused` is `true`, proposal approval returns HTTP `409`. **Read-only**

**Input parameters:** none.

**Returns:** `{ paused: boolean }`.

**Example call:**

```json
{
  "name": "haszno_automation_get",
  "arguments": {}
}
```

---

### haszno_rules_list_templates

Lists the built-in rule templates (`GET /api/rules/templates`), for example `keszlet_or` (stock-out protection), `arjavaslat` (price proposal on market update), `felar_savok` (markup tiers) and `veszteseg_riasztas` (loss alert). **Read-only**

**Input parameters:** none.

**Returns:** each template's key, name, description, trigger, conditions and actions.

**Example call:**

```json
{
  "name": "haszno_rules_list_templates",
  "arguments": {}
}
```

---

### haszno_rules_list

Lists the shop's automation rules (`GET /api/rules`). **Read-only**

**Input parameters:** none.

**Returns:** each rule's id, name, enabled flag, trigger, conditions, actions, template key and creation date.

**Example call:**

```json
{
  "name": "haszno_rules_list",
  "arguments": {}
}
```

---

### haszno_rules_list_runs

Returns the rule execution log, newest first (`GET /api/rule-runs`). Entries whose summary starts with "Levéve" are unlists that `haszno_rules_undo_unlists` can reverse. **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `limit` | integer, 1–200 | No | Maximum entries. Default: `25`. |

**Returns:** entries with run id, rule id, rule name, trigger, product id (or `null`), summary and timestamp.

**Example call:**

```json
{
  "name": "haszno_rules_list_runs",
  "arguments": { "limit": 10 }
}
```

---

### haszno_rules_create

Creates a rule (`POST /api/rules`). **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `name` | string, 1–120 chars | **Yes** | Rule name. |
| `trigger` | string | **Yes** | `feed_import`, `market_price`, `manual`, or a custom string. |
| `actions` | array of `{ type, params? }` | **Yes** | Actions on matching products. Known `type` values: `unlist`, `propose_price`, `notify`. `params` is an object. |
| `conditions` | array of `{ field, op, value? }` | No | All must match. `op` is one of `lt`, `lte`, `gt`, `gte`, `eq`, `neq`, `changed`; `value` is a number, string or `null`. Known `field` values: `stock`, `marginPct`, `recommendedChange`, `priceGross`. |
| `enabled` | boolean | No | Defaults to `true` on the server. |
| `template` | string or `null` | No | Template key the rule is based on (from `haszno_rules_list_templates`). |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** the created rule with its `id`.

**Example call:**

```json
{
  "name": "haszno_rules_create",
  "arguments": {
    "name": "Unlist when out of stock",
    "trigger": "feed_import",
    "conditions": [{ "field": "stock", "op": "lte", "value": 0 }],
    "actions": [{ "type": "unlist" }],
    "confirm": true
  }
}
```

---

### haszno_rules_update

Updates a rule (`PATCH /api/rules/:id`). Only the fields you send change. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | integer | **Yes** | Rule ID (from `haszno_rules_list`). |
| `name` | string, 1–120 chars | No | New name. |
| `enabled` | boolean | No | `true` active, `false` paused. |
| `trigger` | string | No | New trigger event. |
| `conditions` | array of `{ field, op, value? }` | No | Replaces all conditions. |
| `actions` | array of `{ type, params? }` | No | Replaces all actions. |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Example call:**

```json
{
  "name": "haszno_rules_update",
  "arguments": { "id": 7, "enabled": false, "confirm": true }
}
```

---

### haszno_rules_delete

Permanently deletes a rule and its entire run history (`DELETE /api/rules/:id`). This cannot be undone; to stop a rule reversibly, use `haszno_rules_update` with `enabled: false`. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | integer | **Yes** | Rule ID to delete. |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Example call:**

```json
{
  "name": "haszno_rules_delete",
  "arguments": { "id": 7, "confirm": true }
}
```

---

### haszno_rules_run

Runs one rule against all eligible products, regardless of its trigger (`POST /api/rules/:id/run`). **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | integer | **Yes** | Rule ID. |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** `{ applied, skipped, errors }`.

**Example call:**

```json
{
  "name": "haszno_rules_run",
  "arguments": { "id": 7, "confirm": true }
}
```

---

### haszno_rules_run_all

Runs all enabled rules whose trigger is `manual` (`POST /api/rules/run-all`). **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** aggregate `{ applied, skipped, errors }`.

**Example call:**

```json
{
  "name": "haszno_rules_run_all",
  "arguments": { "confirm": true }
}
```

---

### haszno_rules_trigger

Fires all enabled rules that listen to a trigger event (`POST /api/rules/trigger`). **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `trigger` | string | **Yes** | `feed_import`, `market_price`, `manual`, or a custom string. |
| `product_ids` | integer[] | No | Restrict the run to these products. Omit to run on all eligible products. |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** aggregate `{ applied, skipped, errors }`.

**Example call:**

```json
{
  "name": "haszno_rules_trigger",
  "arguments": { "trigger": "market_price", "confirm": true }
}
```

---

### haszno_rules_undo_unlists

Re-lists products that a rule unlisted and marks those log entries as reversed (`POST /api/rule-runs/undo`). Only entries whose summary starts with "Levéve" and that are not reversed yet are affected. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `ids` | integer[] | No | Run-log entry IDs (from `haszno_rules_list_runs`). Use this **or** `rule_id` + `at`. |
| `rule_id` | integer | No | Undo all unlists of this rule at the time given in `at`. |
| `at` | string | No | ISO timestamp of the rule run, used with `rule_id`. |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** `{ relisted }`.

**Example call:**

```json
{
  "name": "haszno_rules_undo_unlists",
  "arguments": { "ids": [311, 312], "confirm": true }
}
```

---

### haszno_automation_set

Turns the global automation kill switch on or off (`PUT /api/automation`). Setting the current state again is harmless. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `paused` | boolean | **Yes** | `true` pauses automation and blocks approvals; `false` resumes. |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** `{ paused }`.

**Example call:**

```json
{
  "name": "haszno_automation_set",
  "arguments": { "paused": true, "confirm": true }
}
```

---

## Orders

Money values are whole forints, gross. Order statuses: `received`, `forwarded`, `shipped`, `error`.

| Tool | Mode | Description |
|---|---|---|
| `haszno_orders_list` | **Read-only** | List orders, newest first. |
| `haszno_orders_get` | **Read-only** | One order with items and event log. |
| `haszno_orders_ship` | **Write** | Mark an order as shipped. |
| `haszno_orders_receive` | **Write** | Inject an incoming order. |
| `haszno_orders_create_demo` | **Write** | Create a random demo order (demo/staging/localhost only). |

### haszno_orders_list

Fetches `GET /api/orders` and returns compact rows without the event log; the status filter and the limit are applied locally. **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `status` | `received` \| `forwarded` \| `shipped` \| `error` | No | Filter by status. |
| `limit` | integer, 1–50 | No | Maximum rows. Default: `25`. |

**Returns:** `{ orders: [{ id, externalId, customerName, address, status, totalGross, supplierOrderId, trackingNumber, itemCount, createdAt }], total, showing }`.

**Example call:**

```json
{
  "name": "haszno_orders_list",
  "arguments": { "status": "forwarded" }
}
```

---

### haszno_orders_get

Returns one order with its items, event log, supplier order ID and tracking number. It searches the list returned by `GET /api/orders`. **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | integer, positive | **Yes** | Order ID. |

**Example call:**

```json
{
  "name": "haszno_orders_get",
  "arguments": { "id": 123 }
}
```

---

### haszno_orders_ship

Marks an order as shipped (`POST /api/orders/:id/ship`). Use it for orders in `forwarded` state; calling it again on a shipped order is safe. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `id` | integer, positive | **Yes** | Order ID. |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** the updated order.

**Example call:**

```json
{
  "name": "haszno_orders_ship",
  "arguments": { "id": 123, "confirm": true }
}
```

---

### haszno_orders_receive

Injects a new order into the forwarding pipeline (`POST /api/webhooks/order`). Every SKU must already exist in the shop's products. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `externalId` | string, 1–64 chars | **Yes** | The webshop's own order reference. |
| `customerName` | string, 1–120 chars | **Yes** | Customer's full name. |
| `address` | string, 1–300 chars | **Yes** | Delivery address as one string. |
| `items` | array of `{ sku, qty }`, at least 1 | **Yes** | `sku`: non-empty string; `qty`: positive integer. |
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** `{ id }` of the new order.

**Example call:**

```json
{
  "name": "haszno_orders_receive",
  "arguments": {
    "externalId": "WC-9001",
    "customerName": "Kovács János",
    "address": "Budapest, Fő utca 1.",
    "items": [{ "sku": "DEMO-5", "qty": 2 }],
    "confirm": true
  }
}
```

---

### haszno_orders_create_demo

Creates a random demo order from in-stock products (`POST /api/demo/order`). **Write**

> [!WARNING]
> The tool refuses to run unless the `HASZNO_API_URL` (or `ARFUTAR_API_URL`) environment variable contains `demo`, `staging` or `localhost`. With no such variable the URL defaults to `https://haszno.com`, so the call is refused. `apiUrl` in the config file is not checked.

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** `{ id }` of the new order.

**Example call:**

```json
{
  "name": "haszno_orders_create_demo",
  "arguments": { "confirm": true }
}
```

---

## Ideas

| Tool | Mode | Description |
|---|---|---|
| `haszno_ideas_list` | **Read-only** | Product ideas ranked by trend score and margin. |
| `haszno_ideas_ai_shortlist` | **Read-only** | AI-curated shortlist of up to 5 products (may cost AI usage). |
| `haszno_ideas_ai_description` | **Read-only** | AI-written description for a shop product (not saved; may cost AI usage). |
| `haszno_ideas_fill_keywords` | **Write** | Fill missing trend keywords via AI. |

### haszno_ideas_list

Fetches `GET /api/recommendations`, then filters, sorts by trend score and margin, and limits the list locally. **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `min_score` | number, 0–100 | No | Keep only items with `trendScore` at least this value. |
| `supplier` | string | No | Case-insensitive substring match on the supplier name. |
| `limit` | integer, 1–100 | No | Maximum items. Default: `25`. |

**Returns:** `{ anchor, geo, total, filtered, truncated, items: [{ rank, id, sku, name, supplier, stock, costNet_HUF, suggestedPriceGross_HUF, marginNet_HUF, marginPct, trendScore }] }`.

**Example call:**

```json
{
  "name": "haszno_ideas_list",
  "arguments": { "min_score": 50, "limit": 10 }
}
```

---

### haszno_ideas_ai_shortlist

Asks the AI for up to 5 products that best fit the shop (`POST /api/recommendations/ai`). It changes no data, but it can start a paid AI call; the server returns a cached result (`cached: true`) for 24 hours when the inputs are unchanged. **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `confirm` | `true` (literal) | **Yes** | Must be `true` — accepts a possible AI cost. |

**Returns:** `{ summary, cached, picks: [{ productId, networkProductId, name, supplier, recommendedPriceGross_HUF, marginNet_HUF, marginPct, fit, aiReason }], usage }`.

**Example call:**

```json
{
  "name": "haszno_ideas_ai_shortlist",
  "arguments": { "confirm": true }
}
```

---

### haszno_ideas_ai_description

Generates a Hungarian webshop description for one of the shop's products (`POST /api/products/:id/ai-description`). The text is only returned, not saved. Triggers a paid AI call. **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `product_id` | integer, min 1 | **Yes** | ID of the shop product (from `haszno_ideas_list`). |
| `confirm` | `true` (literal) | **Yes** | Must be `true` — accepts the AI cost. |

**Returns:** `{ product_id, description }`.

**Example call:**

```json
{
  "name": "haszno_ideas_ai_description",
  "arguments": { "product_id": 55, "confirm": true }
}
```

---

### haszno_ideas_fill_keywords

Uses AI to fill the missing Google Trends keyword of every product that has none, and saves them (`POST /api/trends/keywords`). Running it again when nothing is missing returns `{ updated: 0 }`. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `confirm` | `true` (literal) | **Yes** | Must be `true`. |

**Returns:** `{ updated }`.

**Example call:**

```json
{
  "name": "haszno_ideas_fill_keywords",
  "arguments": { "confirm": true }
}
```

---

## Feeds

| Tool | Mode | Description |
|---|---|---|
| `haszno_feeds_import_supplier` | **Write** | Import a supplier product feed (CSV or XML). |
| `haszno_feeds_import_market` | **Write** | Import market prices from CSV. |
| `haszno_feeds_outbound_info` | **Read-only** | URLs of the published Árukereső and Google Shopping feeds. |

Feed content can be passed as text in `content` or read by the MCP server from `filePath`. A `filePath` must end in `.csv`, `.tsv`, `.txt` or `.xml` and must not be inside a hidden directory (such as `~/.ssh` or `~/.config`).

### haszno_feeds_import_supplier

Imports a supplier's catalogue feed (`POST /api/feeds/import`): creates new products and updates existing ones matched by SKU, then fires the `feed_import` rules. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `supplierId` | integer, positive | **Yes** | Supplier ID. |
| `format` | `csv` \| `xml` | **Yes** | Feed format. |
| `content` | string | No | Feed text. Provide this or `filePath`. |
| `filePath` | string | No | Path of a local feed file. Provide this or `content`. |
| `confirm` | boolean, default `false` | No | `true` executes; omitted or `false` returns a dry run without an API call. |

**Returns:** `{ created, updated, total }`.

**Example call:**

```json
{
  "name": "haszno_feeds_import_supplier",
  "arguments": { "supplierId": 3, "format": "csv", "content": "sku,name,price\nABC-1,Widget,2990", "confirm": true }
}
```

---

### haszno_feeds_import_market

Imports competitor market prices from CSV (`POST /api/market-prices/import`), matching rows to products by SKU, then fires the `market_price` rules. Re-importing the same CSV sets the same values. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `content` | string | No | CSV text. Provide this or `filePath`. |
| `filePath` | string | No | Path of a local CSV file. Provide this or `content`. |
| `confirm` | boolean, default `false` | No | `true` executes; omitted or `false` returns a dry run without an API call. |

**Returns:** `{ matched, total }`.

**Example call:**

```json
{
  "name": "haszno_feeds_import_market",
  "arguments": { "content": "sku,market_price\nABC-1,2890", "confirm": true }
}
```

---

### haszno_feeds_outbound_info

Returns the URLs of the shop's public feeds: `<base>/feed/arukereso.xml` and `<base>/feed/google.xml`, where `<base>` comes from `HASZNO_API_URL` / `ARFUTAR_API_URL` or defaults to `https://haszno.com`. **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `fetch_summary` | boolean, default `false` | No | `true` also downloads both feeds and returns the item count and a 3-item sample of each. `false` makes no HTTP request. |

**Returns:** `{ arukereso_url, google_url, note }`, or with `fetch_summary: true`, `{ arukereso: { url, item_count, sample }, google: { url, item_count, sample } }` (an `error` field replaces the counts if a feed cannot be fetched).

**Example call:**

```json
{
  "name": "haszno_feeds_outbound_info",
  "arguments": { "fetch_summary": true }
}
```

---

## P2P peer invites

| Tool | Mode | Description |
|---|---|---|
| `haszno_peers_invite_list` | **Read-only** | Invite codes you created. |
| `haszno_peers_invite_create` | **Write** | Create an invite to share a supplier. |
| `haszno_peers_invite_accept` | **Write** | Accept a peer invite. |

### haszno_peers_invite_list

Lists the invite codes you created (`GET /api/peers/invites`). **Read-only**

**Input parameters:** none.

**Returns:** `{ invites: [...] }`; each invite has `code`, `supplierId`, `expiresAt`, `acceptedBy` (`null` while pending) and `revokedAt` (`null` while active).

**Example call:**

```json
{
  "name": "haszno_peers_invite_list",
  "arguments": {}
}
```

---

### haszno_peers_invite_create

Creates a single-use invite code that gives another Haszno shop access to one of your suppliers' catalogue (`POST /api/peers/invites`). **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `supplierId` | integer, positive | **Yes** | Supplier to share. |
| `expiresAt` | string | **Yes** | Expiry as ISO 8601, in the future. |
| `confirm` | boolean, preview on `false` | **Yes** | `true` executes; `false` returns a preview without an API call. |

**Returns:** `{ code, expiresAt, supplierId }`.

**Example call:**

```json
{
  "name": "haszno_peers_invite_create",
  "arguments": { "supplierId": 3, "expiresAt": "2027-01-01T00:00:00Z", "confirm": true }
}
```

---

### haszno_peers_invite_accept

Accepts an invite code and creates a partner link (`POST /api/peers/invites/:code/accept`). The code becomes invalid afterwards. **Write**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `code` | string, non-empty | **Yes** | Invite code from the inviting shop. |
| `acceptorId` | string, 1–120 chars | **Yes** | Your shop identifier. |
| `confirm` | boolean, preview on `false` | **Yes** | `true` executes; `false` returns a preview without an API call. |

**Returns:** the new link `{ id, supplierId, partner, createdAt }`.

**Example call:**

```json
{
  "name": "haszno_peers_invite_accept",
  "arguments": { "code": "INV-XYZ", "acceptorId": "my-shop", "confirm": true }
}
```

---

## Blog / market insights

| Tool | Mode | Description |
|---|---|---|
| `haszno_blog_latest` | **Read-only** | Latest market insight posts. |

### haszno_blog_latest

Returns the latest published market insight posts (`GET /api/blog`). The data is aggregated from at least 5 shops; no single shop's data is included. **Read-only**

**Input parameters:**

| Name | Type | Required | Description |
|---|---|---|---|
| `period` | `het` \| `honap` | No | `het` weekly, `honap` monthly. Omit for both. |
| `limit` | integer, 1–10 | No | Number of posts. Default: `1`. |

**Returns:** `{ posts, total }`; each post has `title`, `summary`, `bodyMd`, `period`, `periodStart`, `periodEnd`, `publishedAt` and optional `data`.

**Example call:**

```json
{
  "name": "haszno_blog_latest",
  "arguments": { "period": "het", "limit": 3 }
}
```

---

## Planned tools

The own-shop product tools are listed in the skill's `references/tools.md`, but the MCP server registers none of them yet. Calling them returns a tool-not-found error.

| Tool | **Planned** |
|---|---|
| `haszno_products_list` | List own shop products with pricing and margin data. |
| `haszno_products_update` | Update a product's market data or manual price. |
| `haszno_products_set_listing` | List or unlist a product. |
| `haszno_products_apply_prices` | Apply recommended prices to selected products. |
| `haszno_products_ai_description` | Generate an AI product description. |

Until these tools are available, use the [CLI](cli.md) equivalents: `haszno products list`, `haszno products update`, `haszno products listing`, `haszno products apply-prices`, `haszno products describe`.

---

## See also

- [CLI reference](cli.md) — same operations accessible from the terminal.
- [Agent skill](skill.md) — workflow recipes and safety rules for agents.
- [Authentication](authentication.md) — API key and demo credential details.
- [Errors and rate limits](errors.md) — error schema and retry guidance.
