Skip to main content
Glama
montebelle

Looks MCP Server

by montebelle
README.md
# @thelooks/sdk

**Official TypeScript SDK for the Look API — outfit generation for a real venue, plus an analytics surface built around what it refuses to answer.** Ships the client, the Model Context Protocol tool definitions, and the OpenAPI document, all generated from the server so none of them can drift from it.

[![npm](https://img.shields.io/npm/v/@thelooks/sdk?color=blue)](https://www.npmjs.com/package/@thelooks/sdk)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![Node](https://img.shields.io/badge/node-%E2%89%A518-brightgreen)](https://nodejs.org/)
[![Dependencies](https://img.shields.io/badge/dependencies-0-success)](package.json)
[![MCP](https://img.shields.io/badge/MCP-tools%20included-orange)](#mcp)

```bash
npm install @thelooks/sdk
```

```ts
import { LookClient } from '@thelooks/sdk';

const look = new LookClient({ apiKey: process.env.LOOK_API_KEY! });

// Generation takes 50–80s, so it is async by design: enqueue, then wait.
const { jobId } = await look.looks.create({
  message: 'dinner then drinks',
  department: 'mens',
  place: { name: 'Balthazar', place_id: 'ChIJ...' },
});

const result = await look.looks.wait(jobId);
```

Look **starts with where**. A `place` carrying a `name`, or a `generalLocation`, is required — the client throws before making a request if neither is present. Every call needs `Authorization: Bearer sk_live_…`; keys are minted per organisation and carry scopes, so a key reaches only the endpoints its scopes allow.

## Generation

- **Async by default** — `looks.create()` returns a job handle immediately and `looks.wait()` polls to completion. Generation is 50–80s, so a synchronous contract would have been a lie
- **`looks.stream()`** — server-sent events for progress, instead of polling
- **`looks.refine()`** — adjust a finished look without regenerating it from scratch
- **`looks.describe()` / `looks.image()`** — natural-language description, and a rendered image of the look
- **Idempotency** — pass `idempotencyKey` so a retried enqueue cannot double-charge
- **End-user attribution** — `X-End-User-Id` scopes a call to one of *your* users without minting them a key

## Analytics

Behind its own scope, `analytics:read`, deliberately not granted by `looks:read`. Reporting is treated as more sensitive than transactional access, not less.

```ts
const { data, meta } = await look.analytics.items({
  from: '2026-06-01',
  to: '2026-07-01',      // EXCLUSIVE — the range is half-open [from, to)
  granularity: 'day',
  groupBy: ['brand'],    // at most two dimensions
});
```

**Read `meta` before you read `data`.** Three independent flags; conflating them produces confidently wrong numbers:

- **`truncated`** — more rows exist, and paging with `offset` **will** reach them
- **`suppressed`** — cells were withheld under a k-anonymity floor, and paging will **never** reach them
- **`sampled`** — the underlying read was capped, so every figure is a **floor**, not a total

A withheld cell is **absent from the results — never a row with zero.** Do not report a missing row as "no activity"; report it as withheld.

Two more things worth knowing before building a dashboard:

- **`surfaced` and `shown` differ by roughly 10×** — a generation stores ~40 SERP candidates and shows four. Use `shown` as the exposure denominator, or every rate lands an order of magnitude too low
- **Place figures are declared intent** — someone told us where they were going. Not foot traffic, not visits, not inferred presence. Please don't describe them as visitation

Check a query shape before spending budget on it. `compatibility` runs nothing and names the limit that would refuse it:

```ts
const check = await look.analytics.compatibility({
  endpoint: 'places', from: '2026-06-01', to: '2026-07-01', granularity: 'hour',
});
// { compatible: false, reason: 'A `hour` query may span at most 7 days.', … }
```

## Bulk export

```ts
const { exportId } = await look.analytics.createExport({
  endpoint: 'items', from: '2026-06-01', to: '2026-07-01', format: 'csv',
});
const done = await look.analytics.getExport(exportId);   // → { url, metaUrl }
```

Requires `analytics:export`. Exports run the **same** queries as the sync endpoints, so suppression and venue exclusion are not bypassed. JSON carries `meta` inside the payload; CSV has nowhere to put an envelope, so the flags arrive as a sidecar at `metaUrl` — without it, a CSV of suppressed demand is indistinguishable from a CSV of zero demand.

## MCP

The analytics surface is also exposed as [Model Context Protocol](https://modelcontextprotocol.io) tools at `POST /api/v1/mcp`, on the same credential and scopes. The definitions ship here so an agent host can register them without a round trip:

```ts
import { ANALYTICS_TOOLS, findTool } from '@thelooks/sdk/mcp';
```

They are generated from the same source the server answers `tools/list` from, so they cannot drift from what the endpoint accepts.

**Pass the descriptions through intact.** They carry semantics no JSON schema can express — the `surfaced`/`shown` ratio, that a withheld cell is absent rather than zero, that place demand is declared intent. An agent that sees only the schema will state those wrongly, and state them fluently.

## OpenAPI

```ts
import spec from '@thelooks/sdk/openapi.json' with { type: 'json' };
```

37 paths, generated from the same zod schemas the routes validate with, so the document cannot describe an API that does not exist.

## Errors

```ts
import { LookApiError } from '@thelooks/sdk';

try {
  await look.looks.create({ /* … */ });
} catch (err) {
  if (err instanceof LookApiError) {
    // isClient is true for 4xx — bad request, auth, scope. Retrying will not help.
    console.error(err.status, err.type, err.message, err.isClient);
  }
}
```

## API surface

| Namespace | What it covers |
|---|---|
| `looks` | `create`, `wait`, `get`, `refine`, `stream`, `describe`, `image` |
| `analytics` | `looks`, `items`, `places`, `users`, `compatibility`, `createExport`, `getExport` |
| `shop` | search and browse |
| `closet`, `savedItems`, `savedLooks`, `photos`, `cart` | everything a user owns, saved, or is considering |
| `items` | the garments a generation produced |
| `places`, `locations`, `sessions` | venue and session context |
| `preferences` | body, sizes, fit, style |
| `keys` | mint, list and revoke API keys |

## Licence

MIT. This licence covers the SDK; use of the Look API itself is governed by its terms of service.