# tasteHQ MCP Server

Model Context Protocol server that exposes the tasteHQ brand-style dataset as agent tools. Install once; any MCP client (Claude Code, Cursor, Windsurf, Continue, generic JSON-RPC clients) can then call **12 tools** to pull live brand taste-DNA, structured grammar, the design graph, on-the-fly hybrid compositions, and bootstrap `AGENTS.md` files into agentic projects.

**Data source:** `https://taste-hq.vercel.app/api/` — 101 brand entries, CORS-open, no auth.

---

## Tools

| Tool | Purpose |
|------|---------|
| `match_taste` | Describe a brief in plain English → ranked references with reasoning |
| `list_brands` | Browse catalog with mood / era / density / tier filters |
| `get_brand` | Fetch full DESIGN.md for one brand (paste-ready for LLM prompts) |
| `search_brands` | Weighted lexical search across name, signature_move, era, mood, voice persona |
| `compare_brands` | Side-by-side taste-DNA diff for 2–3 brands |
| `grammar_for` | 30-axis structured taste grammar for a single brand |
| `find_by_grammar` | Filter brands by axis values (e.g. `palette.strategy = mono+1`) |
| `find_neighbors` | Graph adjacency lookup (`shares_grammar` / `informed_by` / `rejects`) |
| `compose_brands` | Synthesize a hybrid brand by merging grammar axes from N source brands |
| `judge_design` | Score a URL against a brand's grammar — closes the verify loop |
| `agents_md_for` | Render the per-brand `AGENTS.md` taste contract |
| `init_agents_md` | Write `AGENTS.md` (and optionally the Claude Skill) to a repo |

### `list_brands`

Browse the catalog with optional filters.

**Params**
- `mood`: one of `editorial`, `scholarly`, `technical`, `playful`, `restrained`, `exuberant`, `industrial`, `organic`, `futurist`, `retro`, `brutalist`, `polished`, `raw`, `corporate`, `intimate`, `theatrical`, `monochrome`, `vibrant`
- `era`: case-insensitive substring (e.g. `swiss`, `bauhaus`, `1970s`)
- `density`: `low` | `medium` | `high`
- `tier`: `reference` | `verified` | `community` | `unrated`
- `limit`: 1..100 (default 20)

**Example call**
```
list_brands(mood="editorial", tier="reference", limit=5)
```

**Example output (truncated)**
```markdown
## tasteHQ brands (5 results)

### ★★★ Anthropic (`anthropic`)
- **Mood:** editorial, restrained, scholarly
- **Era:** swiss-modernism / muji
- **Density:** medium
- **Voice score:** 89/100
- **Signature move:** Serif-led calm; persistent restraint…
```

### `get_brand`

Fetch the full DESIGN.md for one brand.

**Params**
- `slug`: brand slug, e.g. `stripe`, `anthropic`, `linear`

**Example call**
```
get_brand("stripe")
```

**Example output**
```markdown
---
name: Stripe
tier: reference
mood: [technical, polished, intimate]
…
---

# Stripe
> Cobalt-blue ground with confidently technical voice…
```

### `search_brands`

Weighted lexical search. Tokens scored: whole-word in name (3), whole-word in tags/text (2), substring in name (1), substring in tags/text (0.5).

**Params**
- `query`: free text (e.g. `"editorial serif"`, `"swiss modernism"`)
- `limit`: 1..50 (default 10)

**Example call**
```
search_brands(query="brutalist monospace", limit=3)
```

**Example output**
```markdown
## Search results for 'brutalist monospace' (3 matches)

1. ★★ **Bloomberg** (`bloomberg`) — score 5
   Mood: technical, industrial | Signature: dense terminal grid…
```

### `compare_brands`

Side-by-side comparison table for 2–3 brands.

**Params**
- `slugs`: list of 2–3 brand slugs

**Emitted fields:** `signature_move`, `mood`, `era`, `density`, `voice_persona`, `anti_patterns`, `theme` (plus `slug` and `tier`).

**Example call**
```
compare_brands(["anthropic", "linear", "stripe"])
```

**Example output (truncated)**
```markdown
## Brand comparison: Anthropic vs Linear vs Stripe

| Attribute | Anthropic | Linear | Stripe |
|---|---|---|---|
| **Signature move** | Serif-led calm… | Keyboard-first dense grid… | Cobalt aurora hero… |
| **Mood** | editorial, scholarly | technical, polished | technical, intimate |
…
```

### `grammar_for`

Return the 30-axis structured grammar for a brand.

**Params**
- `slug`: brand slug

**Example call**
```
grammar_for("stripe")
```

**Example output**
```json
{
  "slug": "stripe",
  "name": "Stripe",
  "graded": true,
  "axis_count": 27,
  "grammar": {
    "surface": {"ground": "white", "...": "..."},
    "palette": {"strategy": "mono+1", "...": "..."},
    "type":    {"pairing": "sans/sans", "...": "..."},
    "voice":   {"archetype": "engineer", "...": "..."}
  },
  "axis_catalog": "https://taste-hq.vercel.app/docs/GRAMMAR.md"
}
```

### `find_by_grammar`

Find brands matching a logical-AND filter over grammar axes.

**Params**
- `filters`: dict keyed by `"category.axis"` (e.g. `"palette.strategy": "mono+1"`)
- `limit`: 1..50 (default 20)

**Example call**
```
find_by_grammar({"palette.strategy": "mono+1", "voice.archetype": "librarian"})
```

**Example output**
```json
[
  {"slug": "anthropic", "name": "Anthropic", "axis_count": 28, "signature_move": "Serif-led calm…"},
  {"slug": "muji",      "name": "Muji",      "axis_count": 21, "signature_move": "Anonymous design…"}
]
```

### `find_neighbors`

Graph adjacency lookup. Edge types: `shares_grammar`, `informed_by`, `rejects`.

**Params**
- `slug`: brand slug
- `edge_type`: optional filter (`shares_grammar` | `informed_by` | `rejects` | omit for all)
- `limit`: 1..50 (default 10)

**Example call**
```
find_neighbors(slug="linear", edge_type="shares_grammar", limit=5)
```

**Example output**
```json
[
  {"slug": "stripe",   "name": "Stripe",   "edge_type": "shares_grammar", "weight": 0.78, "signature_move": "…"},
  {"slug": "vercel",   "name": "Vercel",   "edge_type": "shares_grammar", "weight": 0.71, "signature_move": "…"}
]
```

### `compose_brands`

Synthesize a hybrid brand by merging grammar axes from N source brands. Deterministic merge — no LLM call — with templated `signature_move`.

**Params**
- `sources`: dict mapping grammar category → source brand slug. Categories: `surface`, `palette`, `type`, `emphasis`, `whitespace`, `voice`, `motion`, `imagery`.
- `name`: optional hybrid name
- `output_format`: `"json"` (default) or `"design_md"`

**Example call**
```
compose_brands(
  sources={
    "surface":    "stripe",
    "palette":    "anthropic",
    "voice":      "anthropic",
    "whitespace": "acne-studios"
  },
  name="Stripe × Anthropic × Acne hybrid"
)
```

**Example output (JSON, truncated)**
```json
{
  "name": "Stripe × Anthropic × Acne hybrid",
  "slug": "stripe-anthropic-acne-hybrid",
  "tier": "unrated",
  "provenance": "composed",
  "signature_move": "A taste hybrid of a stark white ground, museum-grade negative space, and a research-librarian's restraint, with monochrome with a single accent accent strategy.",
  "grammar": {"surface": {…}, "palette": {…}, "voice": {…}, "whitespace": {…}},
  "_compose": {"sources": {…}, "source_quotes": [...], "axis_count": 23, "version": "v1-templated"}
}
```

---

## Install

### Option A — run from source (recommended)

```bash
git clone https://github.com/MustBeSimo/tasteHQ.git
cd tasteHQ
python -m venv .venv && source .venv/bin/activate
pip install mcp
python tools/mcp_server.py          # stdio (default)
python tools/mcp_server.py --transport sse   # SSE / HTTP
```

### Option B — PyPI (when available)

Coming soon: `pip install tastehq-mcp` will be available once the package is published.

For now, use Option A (run from source).

---

## Claude Code

Add to `~/.claude/claude_code_config.json` (or the project-local `.claude/mcp.json`):

```json
{
  "mcpServers": {
    "tastehq": {
      "command": "python",
      "args": ["/absolute/path/to/tasteHQ/tools/mcp_server.py"]
    }
  }
}
```

Restart Claude Code. Type `/mcp` to confirm `tastehq` is listed.

---

## Cursor

Open **Cursor → Settings → MCP** (or edit `~/.cursor/mcp.json`):

```json
{
  "mcpServers": {
    "tastehq": {
      "command": "python",
      "args": ["/absolute/path/to/tasteHQ/tools/mcp_server.py"]
    }
  }
}
```

---

## Windsurf / Continue / generic stdio client

Any MCP client that accepts a JSON config block:

```json
{
  "name": "tastehq",
  "transport": "stdio",
  "command": "python",
  "args": ["/absolute/path/to/tasteHQ/tools/mcp_server.py"]
}
```

---

## SSE / HTTP transport

For web-based MCP clients or self-hosted deployments:

```bash
python tools/mcp_server.py --transport sse
```

The server starts on `http://localhost:8000` (FastMCP default). Point your client at that URL.

---

## Usage examples

Once the server is registered, ask your agent:

```
# Discover editorial brands
list_brands(mood="editorial", tier="reference")

# Pull a full style guide into context
get_brand("stripe")

# Find brands by design DNA
search_brands("brutalist monospace industrial")

# Side-by-side comparison
compare_brands(["anthropic", "linear", "stripe"])

# Structured axis query
grammar_for("stripe")
find_by_grammar({"voice.archetype": "librarian"})

# Graph adjacency
find_neighbors("linear", edge_type="shares_grammar")

# Hybrid synthesis
compose_brands({"surface": "stripe", "voice": "anthropic"}, output_format="design_md")
```

### Agent prompt recipe

```
Get the full tasteHQ DESIGN.md for Stripe, then build a pricing page that
matches its taste-DNA exactly — gradient aurora hero, Inter/Ideal Sans type
pairing, Midnight Blue (#0a2540) surface, intimately technical copy voice.
```

---

## Caching

All HTTP responses are cached in-memory with a 300-second TTL. MCP servers are typically short-lived (one session), but the TTL guards against stale catalog data if the server stays resident. The live API at `taste-hq.vercel.app` is CDN-cached with `max-age=300, s-maxage=3600`.

---

## Development

```bash
# Run the server in debug mode (verbose logs to stderr)
python tools/mcp_server.py

# Smoke test all tools
python tests/test_mcp_smoke.py
```

---

## Related

- `API.md` — raw REST API reference (no MCP client required)
- `docs/superpowers/plans/2026-05-09-beat-refero.md` — strategic context
- MCP specification: https://modelcontextprotocol.io
