Skip to main content
Glama
README.md
# UnoPim MCP Server

Version 3.0.0 -- A Model Context Protocol server that connects Claude Desktop (and other MCP clients) to a UnoPim PIM instance (UnoPim v3 / 3.0.x, self-hosted or Cloud Hosting). Provides 15 consolidated, action-based tools for managing attributes, families, categories, products, media, association types, variant structures, measurements, and settings.

## 3.0.0 — UnoPim v3

This release targets **UnoPim 3.0.x** and is a breaking change from 2.x. It will not work against a 2.x UnoPim instance, and a 2.x version of this server will not work against a v3 instance.

### Requirements

- UnoPim **3.0.x** (self-hosted or UnoPim Cloud Hosting)
- Node.js 20+

### Breaking changes

- **Tool set consolidated**: 36 old per-verb tools → 15 action-based tools (one tool per resource, `action` parameter selects the operation). Full mapping:

<!-- KEEP IN SYNC: this table exists in CLAUDE.md and README.md — edit both. -->
| Old tool (2.x) | New call (3.0) |
|---|---|
| `unopim_get_guide` | unchanged |
| `unopim_get_schema` | unchanged |
| `unopim_get_attributes` | `unopim_attributes` action=`list` |
| `unopim_create_attribute` | `unopim_attributes` action=`create` |
| `unopim_get_attribute_options` | `unopim_attributes` action=`list_options` |
| `unopim_create_attribute_options` | `unopim_attributes` action=`create_options` |
| `unopim_delete_attribute` | `unopim_attributes` action=`delete` |
| `unopim_get_attribute_groups` | `unopim_attribute_groups` action=`list` |
| `unopim_create_attribute_group` | `unopim_attribute_groups` action=`create` |
| `unopim_delete_attribute_group` | `unopim_attribute_groups` action=`delete` |
| `unopim_get_families` | `unopim_families` action=`list` |
| `unopim_create_family` | `unopim_families` action=`create` |
| `unopim_update_family` | `unopim_families` action=`add_attributes` (or `update` = full PUT) |
| `unopim_delete_family` | `unopim_families` action=`delete` |
| `unopim_get_family_schema` | removed — `unopim_families` action=`get` shows the attribute groups |
| `unopim_get_categories` | `unopim_categories` action=`list` |
| `unopim_create_category` | `unopim_categories` action=`create` |
| `unopim_delete_category` | `unopim_categories` action=`delete` |
| `unopim_get_category_fields` | `unopim_category_fields` action=`list` |
| `unopim_create_category_field` | `unopim_category_fields` action=`create` |
| `unopim_get_products` | `unopim_products` action=`list` |
| `unopim_get_product` | `unopim_products` action=`get` |
| `unopim_create_product` | `unopim_products` action=`create` |
| `unopim_update_product` | `unopim_products` action=`update` |
| `unopim_upsert_product` | `unopim_products` action=`upsert` |
| `unopim_bulk_create_products` | `unopim_products` action=`bulk_create` (NB: `validate_only` removed — it never actually validated) |
| `unopim_delete_product` | `unopim_products` action=`delete` (configurable parent: `unopim_configurable_products` action=`delete` — deletes ALL variants in v3!) |
| `unopim_smart_create_product` | removed — `unopim_products` action=`create` + `unopim_get_guide` |
| `unopim_create_configurable_product` | `unopim_configurable_products` action=`create` |
| `unopim_add_variant` | `unopim_configurable_products` action=`add_variant` |
| `unopim_update_configurable_product` | `unopim_configurable_products` action=`update` |
| `unopim_upload_product_media` | `unopim_media` action=`upload` target=`product` |
| `unopim_upload_category_media` | `unopim_media` action=`upload` target=`category` |
| `unopim_get_channels` | `unopim_settings` action=`list` resource=`channel` |
| `unopim_get_locales` | `unopim_settings` action=`list` resource=`locale` |
| `unopim_get_currencies` | `unopim_settings` action=`list` resource=`currency` |

- **`unopim_smart_create_product` and `unopim_get_family_schema` are gone.** Use `unopim_products` action=`create` (family validation happens against the real family) and `unopim_families` action=`get` respectively.
- **Authentication changed**: OAuth2 password grant now sends client credentials via an `Authorization: Basic` header instead of the JSON body. No config change needed — the server builds the header internally from `UNOPIM_CLIENT_ID`/`UNOPIM_CLIENT_SECRET` — but **tokens and cached OAuth sessions issued against a 2.x instance are invalid after the instance is upgraded to v3.** Re-authenticate (HTTP mode) or regenerate API credentials (stdio mode) after a UnoPim upgrade.
- **Pagination defaults to 10 and clamps to 100** per request — tools now follow `links.next` internally where relevant; if you built anything against the raw API, don't assume `limit` above 100 is honored.
- **Product `associations` moved to the top level** of the product payload (a sibling of `values`, not nested inside it).

### New capabilities

- `unopim_measurements` — measurement families, units (with conversion factors), and attribute↔unit bindings.
- `unopim_association_types` — custom product-relationship types (e.g. spare parts, accessories) with per-link fields.
- `unopim_variant_structures` — 1-/2-level variant axes per family with per-level attribute placement.
- `unopim_settings` — full CRUD (not just read) on channels, locales and currencies, enabling instance bootstrap from scratch.
- `unopim_media` — `get`/`delete` in addition to `upload`, plus a `swatch` target for attribute-option swatches. Product uploads are auto-linked server-side by UnoPim v3.
- Delta sync on `unopim_products`: `updated_since` filter shortcut plus `pagination_type=search_after` cursor pagination (simple products only).
- `unopim_get_capabilities`: deterministic, local self-description of every resource, filterable field, operator, pagination rule and rate limit — no trial-and-error required.

## UnoPim Cloud Hosting ("SaaS")

UnoPim also ships as a managed offering — a dedicated, standard UnoPim instance per customer, same API, no code differences. Point `UNOPIM_BASE_URL` (or the HTTP-mode login form's base-URL field) at the tenant's assigned URL. Hourly API-call quotas apply per tier: Starter 5,000/hour, Pro 10,000/hour, Enterprise 25,000/hour (independent of the general 120 req/min rate limit).

## Transports

**Stdio** -- Runs as a local subprocess of Claude Desktop. Requires UnoPim credentials in env vars.

**HTTP** -- Exposes SSE and Streamable HTTP endpoints. Intended for remote access via ngrok or cloud deployment.

## Authentication

### HTTP transport: OAuth browser-based (per-user)

Each user authenticates through a browser login form that Claude Desktop opens automatically (MCP OAuth spec). Uses PKCE with S256 challenge. Tokens expire after 24 hours by default (configurable via `MCP_TOKEN_EXPIRY_SECONDS`). Credentials are stored in localStorage for seamless re-auth.

When `UNOPIM_*` env vars are absent, the server runs in OAuth-only mode.

### HTTP transport: API key fallback

Uses shared credentials from env vars. Set `MCP_API_KEY` or it defaults to `UNOPIM_CLIENT_ID`.

### Stdio transport

Uses `UNOPIM_*` env vars directly. No browser-based auth.

UnoPim v3 itself authenticates via OAuth2 password grant with client credentials sent as an `Authorization: Basic` header (handled internally — no config change needed).

## Claude Desktop Configuration

### HTTP mode with OAuth (recommended)

```json
{
  "mcpServers": {
    "unopim": {
      "url": "https://your-ngrok-url.ngrok-free.app/mcp"
    }
  }
}
```

### Stdio mode (local)

```json
{
  "mcpServers": {
    "unopim": {
      "command": "node",
      "args": ["/path/to/unopim-mcp/dist/index.js"],
      "env": {
        "UNOPIM_BASE_URL": "http://your-unopim:8000",
        "UNOPIM_CLIENT_ID": "your-client-id",
        "UNOPIM_CLIENT_SECRET": "your-client-secret",
        "UNOPIM_USERNAME": "user@example.com",
        "UNOPIM_PASSWORD": "password"
      }
    }
  }
}
```

## Tools (15)

### Meta / Discovery (start here)

- `unopim_get_schema` -- Fetch the complete data model (fetches ALL pages)
- `unopim_get_capabilities` -- Deterministic self-description: resources, filters, operators, pagination, rate limits
- `unopim_get_guide` -- Step-by-step guides: product-import, configurable-products, troubleshooting

### Data Model (action-based CRUD)

- `unopim_attributes` -- actions: list, get, create, update, delete, list_options, create_options, update_options, delete_option
- `unopim_attribute_groups` -- actions: list, get, create, update, delete
- `unopim_families` -- actions: list, get, create, update, delete, add_attributes
- `unopim_variant_structures` -- actions: list, get, create, update, delete (NEW in v3)
- `unopim_categories` -- actions: list, get, create, update, delete
- `unopim_category_fields` -- actions: list, get, create, update, delete, list_options, create_options, update_options, delete_option

### Products

- `unopim_products` -- actions: list, get, create, update, patch, delete, upsert, bulk_create
- `unopim_configurable_products` -- actions: list, get, create, update, add_variant, delete (deleting a parent deletes ALL its variants)
- `unopim_media` -- actions: upload, get, delete × target: product, category, swatch (product uploads auto-link server-side)

### v3 Features

- `unopim_settings` -- actions: list, get, create, update, delete × resource: channel, locale, currency
- `unopim_measurements` -- measurement families, units, attribute↔unit bindings (NEW in v3)
- `unopim_association_types` -- custom product-relationship types + per-link fields (NEW in v3)

Every tool's `delete` action is **IRREVERSIBLE** -- always confirm with the user first.

## Environment Variables

### Required for stdio mode / Optional for HTTP mode (enables API key auth)

| Variable | Description |
|----------|-------------|
| `UNOPIM_BASE_URL` | UnoPim API URL (e.g., `http://localhost:8000`) |
| `UNOPIM_CLIENT_ID` | OAuth2 Client ID |
| `UNOPIM_CLIENT_SECRET` | OAuth2 Client Secret |
| `UNOPIM_USERNAME` | API username |
| `UNOPIM_PASSWORD` | API password |

### Optional

| Variable | Default | Description |
|----------|---------|-------------|
| `UNOPIM_DEFAULT_LOCALE` | `en_US` | Default locale |
| `UNOPIM_DEFAULT_CHANNEL` | `default` | Default channel |
| `UNOPIM_DEFAULT_CURRENCY` | `USD` | Default currency |
| `PORT` | `3000` | HTTP server port |
| `MCP_API_KEY` | Value of `UNOPIM_CLIENT_ID` | API key for shared-credential auth |
| `MCP_TOKEN_EXPIRY_SECONDS` | `86400` (24h) | OAuth token expiry |
| `MCP_LOCK_BASE_URL` | unset | Set to `true` to lock URL field in login form |

## Docker

The server is packaged as a Docker image (node:20-alpine). See the `unopim-deploy` repo for the Docker Compose setup.

```bash
docker compose build mcp
docker compose up -d
```

## Development

```bash
npm install
npm run build        # Build TypeScript (plain tsc)
npx tsc --noEmit     # Type check only
npm test             # Run the vitest suite -- run before considering any change done
```

## Project Structure

```
src/
  index.ts              # Stdio transport entry point
  index-http.ts         # HTTP transport with OAuth + per-session servers
  config.ts             # Environment configuration
  version.ts            # Package version constant
  auth/
    oauth.ts            # UnoPim OAuth2 token management (v3: Basic-header client creds)
    mcp-oauth.ts        # MCP OAuth server (PKCE, stores, token exchange)
    login-form.ts       # HTML login form with localStorage
    client-registry.ts  # Per-user UnoPimClient cache
  client/
    endpoints.ts         # Typed endpoint catalog (paths/filters/pagination)
    unopim-client.ts      # HTTP client: envelope handling, ETag cache, retry/backoff, pagination
    resources.ts          # Generic ResourceClient (list/get/create/update/patch/delete + sub-resources)
  tools/
    registry.ts          # Shared tool registry
    register-all.ts      # Registers all 15 consolidated tools
    crud-factory.ts       # Factory for action-based CRUD tools
    product-helpers.ts     # Shared product payload/error/orphan helpers
    schema.ts             # unopim_get_schema
    capabilities.ts        # unopim_get_capabilities
    guides.ts              # unopim_get_guide
    attributes.ts          # unopim_attributes
    groups.ts              # unopim_attribute_groups
    families.ts            # unopim_families
    variant-structures.ts  # unopim_variant_structures
    categories.ts          # unopim_categories
    category-fields.ts     # unopim_category_fields
    products.ts            # unopim_products
    configurable-products.ts # unopim_configurable_products
    media.ts                # unopim_media
    settings.ts             # unopim_settings
    measurements.ts          # unopim_measurements
    association-types.ts     # unopim_association_types
  prompts/
    index.ts            # MCP prompt handlers
  types/
    errors.ts           # Error types (v3 error envelope)
    oauth.ts             # OAuth + MCP auth types
    unopim.ts            # UnoPim API types
skills/                  # Content served by unopim_get_guide
```

## Tech Stack

- Node.js 20, TypeScript
- @modelcontextprotocol/sdk, zod
- vitest for tests
- No other runtime dependencies -- OAuth uses Node.js built-in crypto

## License

ISC