awwwards-mcp 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +349 -347
- package/dist/capture.js +7 -1
- package/dist/cli.js +7 -0
- package/dist/motion.js +11 -2
- package/dist/server.js +10 -3
- package/dist/structure.js +7 -1
- package/dist/viewport.js +7 -0
- package/package.json +19 -1
- package/skills/awwwards-inspiration/SKILL.md +7 -3
- package/skills/awwwards-motion-study/SKILL.md +3 -0
package/README.md
CHANGED
|
@@ -1,347 +1,349 @@
|
|
|
1
|
-
# awwwards-mcp
|
|
2
|
-
|
|
3
|
-
Free, open-source MCP server that gives AI agents design inspiration from
|
|
4
|
-
[Awwwards](https://www.awwwards.com/) — the Mobbin-style visual reference loop,
|
|
5
|
-
sourced from the web's best award-winning websites.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
|
16
|
-
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
the
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
|
133
|
-
|
|
134
|
-
| `awwwards-
|
|
135
|
-
|
|
136
|
-
|
|
|
137
|
-
|
|
138
|
-
|
|
|
139
|
-
|
|
140
|
-
|
|
|
141
|
-
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
skill.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
>
|
|
193
|
-
>
|
|
194
|
-
>
|
|
195
|
-
>
|
|
196
|
-
>
|
|
197
|
-
>
|
|
198
|
-
>
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
**
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
 — the Mobbin-style visual reference loop,
|
|
5
|
+
sourced from the web's best award-winning websites.
|
|
6
|
+
|
|
7
|
+
<a href="https://github.com/INSANE0777/Awwwards-mcp"><img src="assets/demo.gif" alt="awwwards-mcp in action: search results, design DNA, motion filmstrip, band map — real tool output" width="480"></a>
|
|
8
|
+
|
|
9
|
+
Your agent searches in natural language ("dark 3D portfolio sites", "soft pastel
|
|
10
|
+
e-commerce"), sees **real screenshots inline**, and can pull the **design DNA**
|
|
11
|
+
of any site: color palette, tech stack, design elements, award history.
|
|
12
|
+
|
|
13
|
+
## Tools
|
|
14
|
+
|
|
15
|
+
| Tool | What it does |
|
|
16
|
+
|------|--------------|
|
|
17
|
+
| `search_sites` | Search by color, tags, technology or award type. Returns site cards with inline screenshots. |
|
|
18
|
+
| `get_site_details` | Full design DNA for one site: palette, technologies, elements, awards, description. |
|
|
19
|
+
| `get_site_elements` | Component-level visuals for one site: each element's poster image inline (3D models, video content, mobile layouts, microcopy…) + video URLs. |
|
|
20
|
+
| `list_categories` | Every filter the agent can search by (200+ tags, 27 colors). |
|
|
21
|
+
| `capture_live_site` | Optional: fresh full-page screenshot of any live URL. Waits for `load` + a settle window with a bounded pre-scroll, so heavy sites work (`waitStrategy: "networkidle"` available). Pass `viewport: "mobile"` for the 390×844 iPhone-class render (`"desktop"` 1440×900 default). (needs [playwright](https://playwright.dev)). |
|
|
22
|
+
| `analyze_page_structure` | Section band map of any page (live URL or local file:// build): tag, background, offset, height per band. Compare a reference site's structure against your build. Same heavy-site-friendly wait (`waitStrategy: "networkidle"` available); `viewport: "mobile"` analyzes the phone-class layout (`"desktop"` default). (needs [playwright](https://playwright.dev)). |
|
|
23
|
+
| `record_site_motion` | Optional: short motion-through video of a live URL — preloader, scroll-triggered and hover/cursor animations. Returns an inline filmstrip JPEG plus the saved .webm path. `viewport: "mobile"` records at phone size — the filmstrip renders at the selected viewport, no pillarboxing (`"desktop"` default). (needs [playwright](https://playwright.dev) + ffmpeg-static). |
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
Any MCP-compatible coding agent can use awwwards-mcp — no API key, no account.
|
|
28
|
+
Requires Node ≥ 22.13 (`node -v` to check). Pick your agent:
|
|
29
|
+
|
|
30
|
+
**Updates**: the server checks the npm registry once a day and prints an
|
|
31
|
+
stderr notice when a newer `awwwards-mcp` exists (stdout stays clean for the
|
|
32
|
+
JSON-RPC channel — your agent sees the notice as a log line). Set
|
|
33
|
+
`AWWWARDS_AUTO_UPDATE=1` in the server's `env` to opt into background
|
|
34
|
+
self-update; restart your agent afterwards to load it. Nothing is fetched
|
|
35
|
+
more than once a day and serving never waits on the check.
|
|
36
|
+
|
|
37
|
+
**Claude Code**
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
claude mcp add awwwards -- npx -y awwwards-mcp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Codex CLI** (ChatGPT desktop app and the IDE extension share this config)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
codex mcp add awwwards -- npx -y awwwards-mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
or in `~/.codex/config.toml` (project-scoped: `.codex/config.toml`):
|
|
50
|
+
|
|
51
|
+
```toml
|
|
52
|
+
[mcp_servers.awwwards]
|
|
53
|
+
command = "npx"
|
|
54
|
+
args = ["-y", "awwwards-mcp"]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**OpenCode** (`opencode.json` — note the command is an array)
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"$schema": "https://opencode.ai/config.json",
|
|
62
|
+
"mcp": {
|
|
63
|
+
"awwwards": {
|
|
64
|
+
"type": "local",
|
|
65
|
+
"command": ["npx", "-y", "awwwards-mcp"]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**ZCode** (`~/.zcode/cli/config.json` — note servers nest under `"mcp": { "servers": ... }`)
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"mcp": {
|
|
76
|
+
"servers": {
|
|
77
|
+
"awwwards": { "command": "npx", "args": ["-y", "awwwards-mcp"], "env": {} }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Claude Desktop / Cursor / Windsurf / Gemini CLI / Cline / Continue** — anything
|
|
84
|
+
reading the common `mcpServers` JSON shape (e.g. `~/.claude/claude_desktop_config.json`
|
|
85
|
+
or `~/.gemini/settings.json`):
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"mcpServers": {
|
|
90
|
+
"awwwards": { "command": "npx", "args": ["-y", "awwwards-mcp"] }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Anything else** — awwwards-mcp is a plain stdio MCP server: point your client
|
|
96
|
+
at `npx -y awwwards-mcp` and it works. To pin a version, use
|
|
97
|
+
`npx -y awwwards-mcp@1.0.0`.
|
|
98
|
+
|
|
99
|
+
**pi coding agent** has no built-in MCP by design — it uses skills and
|
|
100
|
+
extensions instead. Two options:
|
|
101
|
+
|
|
102
|
+
1. Install the awwwards-inspiration skill (below). pi reads skills from
|
|
103
|
+
`~/.pi/agent/skills/` or `~/.agents/skills/` (the latter is shared across
|
|
104
|
+
agents following the Agent Skills standard). The skill teaches the workflow;
|
|
105
|
+
for it to reach the live data, add an MCP-supporting pi extension, or run
|
|
106
|
+
the queries in another agent and paste results.
|
|
107
|
+
2. Skip MCP entirely: ask pi to build you a small CLI wrapper around
|
|
108
|
+
awwwards.com, or use a shared skills directory (`~/.agents/skills/`) so the
|
|
109
|
+
same skill file serves pi and every other agent.
|
|
110
|
+
|
|
111
|
+
Optional full-page captures (needed by `capture_live_site`,
|
|
112
|
+
`analyze_page_structure`, `record_site_motion`):
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
npm install -g playwright && npx playwright install chromium
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`record_site_motion` additionally uses ffmpeg; it resolves the `ffmpeg-static`
|
|
119
|
+
package automatically if present.
|
|
120
|
+
|
|
121
|
+
## Skills
|
|
122
|
+
|
|
123
|
+
This package ships three agent skills. Any agent that follows the
|
|
124
|
+
[Agent Skills standard](https://agentskills.io) can load them; copy them into
|
|
125
|
+
your agent's skills directory:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npm install awwwards-mcp
|
|
129
|
+
mkdir -p ~/.agents/skills && cp -r node_modules/awwwards-mcp/skills/awwwards-inspiration node_modules/awwwards-mcp/skills/awwwards-doctor node_modules/awwwards-mcp/skills/awwwards-motion-study ~/.agents/skills/
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Skill | What it teaches |
|
|
133
|
+
|-------|-----------------|
|
|
134
|
+
| `awwwards-inspiration` | The inspiration loop: search, judge from screenshots, pull design DNA, state a design direction, capture/motion-first builds. |
|
|
135
|
+
| `awwwards-motion-study` | The full video chain: what to record from a live site (and what to skip), frame-by-frame review (video input or tile-per-element), the motion inventory, and build verification by re-recording. |
|
|
136
|
+
| `awwwards-doctor` | Repair: run `npm run doctor`, apply its fixes, re-anchor parsers after real awwwards.com drift, recover the in-flight task that surfaced the failure. |
|
|
137
|
+
|
|
138
|
+
| Agent | Skills directory |
|
|
139
|
+
|-------|------------------|
|
|
140
|
+
| Claude Code | `~/.claude/skills/` |
|
|
141
|
+
| pi | `~/.pi/agent/skills/` (also reads `~/.agents/skills/`) |
|
|
142
|
+
| ZCode | `~/.zcode/skills/` |
|
|
143
|
+
| Agent Skills-standard agents | `~/.agents/skills/` |
|
|
144
|
+
|
|
145
|
+
Windows: run this from Git Bash, or copy
|
|
146
|
+
`node_modules\awwwards-mcp\skills\awwwards-inspiration` manually.
|
|
147
|
+
|
|
148
|
+
## Indexing (recommended)
|
|
149
|
+
|
|
150
|
+
`search_sites` works out of the box, but its depth is limited by polite live
|
|
151
|
+
scraping (~31 sites per filter page). Build a local index once and searches
|
|
152
|
+
draw from thousands of award-winning sites instantly:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
npx -y -p awwwards-mcp awwwards-index # once published
|
|
156
|
+
# or, from a local checkout of this repo:
|
|
157
|
+
npm run index
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- Crawls all ~200 tag pages at 1 request/second (~4 minutes) into the local
|
|
161
|
+
SQLite cache at `~/.awwwards-mcp/`.
|
|
162
|
+
- Resumable: interrupt it and re-run — completed pages are skipped.
|
|
163
|
+
- The MCP server re-indexes automatically in the background whenever the
|
|
164
|
+
index is older than 7 days (never blocking your session).
|
|
165
|
+
|
|
166
|
+
Site details (palettes, tech stacks) are still fetched on demand and cached
|
|
167
|
+
for 7 days.
|
|
168
|
+
|
|
169
|
+
## How it works
|
|
170
|
+
|
|
171
|
+
- Live, polite scraping of awwwards.com public pages (max 1 request/second,
|
|
172
|
+
robots.txt-compliant paths only, cached 7 days in SQLite at `~/.awwwards-mcp/`).
|
|
173
|
+
- Screenshots are served from Awwwards' own CDN (880×660), cached on disk.
|
|
174
|
+
- No API key, no account, no cost.
|
|
175
|
+
|
|
176
|
+
## Ethics & terms
|
|
177
|
+
|
|
178
|
+
This tool fetches publicly available pages for **personal design-inspiration
|
|
179
|
+
use**, at human-ish request rates, honoring robots.txt. Awwwards' screenshots
|
|
180
|
+
and content remain the property of Awwwards and the credited creators — don't
|
|
181
|
+
bulk-scrape, redistribute, or republish them. If you use this commercially,
|
|
182
|
+
review awwwards.com's terms yourself.
|
|
183
|
+
|
|
184
|
+
## Built with awwwards-mcp: three real sites
|
|
185
|
+
|
|
186
|
+
Three complete sites were built through the full inspiration loop this MCP
|
|
187
|
+
enables, using nothing but the server's tools plus the shipped
|
|
188
|
+
`awwwards-inspiration` skill. Each one exercised a different corner of the
|
|
189
|
+
loop — and every correction the loop caught on the way became doctrine in the
|
|
190
|
+
skill.
|
|
191
|
+
|
|
192
|
+
> **Built in one shot, by a model that can't watch video.** All three sites
|
|
193
|
+
> were built in a single prompt run on **GLM 5.3-flash** — which does not
|
|
194
|
+
> support video input. The loop's motion study worked entirely from
|
|
195
|
+
> frame-tiled filmstrips (ffmpeg, 1–2 fps per element) instead of watching
|
|
196
|
+
> the recordings. With a video-native model, those same `get_site_elements`
|
|
197
|
+
> videos and `record_site_motion` .webm files could be watched directly —
|
|
198
|
+
> timing, easing and overlap read at full fidelity — and the motion-true
|
|
199
|
+
> results would be better still. The skill's frame-tile doctrine is what
|
|
200
|
+
> closes that gap today.
|
|
201
|
+
|
|
202
|
+
**1. [Fallow Press](fallow-press/index.html)**
|
|
203
|
+
([source](fallow-press/)) — a flat-2D editorial journal, direction
|
|
204
|
+
**Emergence Magazine** (SOTD): pink `#FF9398` on cream and black, torn-paper
|
|
205
|
+
masthead (pure CSS `clip-path`, zero WebGL), giant grotesque display over
|
|
206
|
+
grayscale photography, serif-italic brand, three pages with **separate
|
|
207
|
+
horizontal** projects/about pages (GSAP ScrollTrigger pin +
|
|
208
|
+
`containerAnimation`).
|
|
209
|
+
|
|
210
|
+
| Torn-paper masthead (home) | Horizontal gallery (Fields) | Horizontal chapters (Practices) |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
|  |  |  |
|
|
213
|
+
|
|
214
|
+
The loop as it ran:
|
|
215
|
+
|
|
216
|
+
1. `search_sites` (magazine filters) → shortlist judged from inline
|
|
217
|
+
screenshots → `get_site_details` on Emergence Magazine.
|
|
218
|
+
2. **Capture before building**: `capture_live_site` + `record_site_motion`
|
|
219
|
+
on the live site *first*; full-page PNG and motion .webm kept in
|
|
220
|
+
[fallow-press/ref-motion/](fallow-press/ref-motion/) as the evidence trail.
|
|
221
|
+
3. Build, then verify: full-page capture plus **panel-center pin shots** of
|
|
222
|
+
both horizontal pages (13 stops each, in
|
|
223
|
+
[fallow-press/_qa/](fallow-press/_qa/) — `capture-qa.mjs` is reusable).
|
|
224
|
+
4. The pin shots caught a real bug: horizontal-panel entrances used
|
|
225
|
+
`toggleActions: "play none none reverse"`, and 100vw panels hide content
|
|
226
|
+
at midpoints on the way back — copy disappeared mid-view. Fix
|
|
227
|
+
(one-shot play entrances) is now doctrine: **full-viewport panels get
|
|
228
|
+
one-shot entrances**; QA pin shots land at panel **centers**, not uniform
|
|
229
|
+
fractions, or you photograph empty transition zones.
|
|
230
|
+
|
|
231
|
+
**2. Cerebrium recreation** (`C:/Users/Afjal/cerebrium-recreation/`) — a
|
|
232
|
+
fidelity-first recreation of cerebrium.ai, pixel-checked against the live
|
|
233
|
+
reference: full-page captures of both sides, `analyze_page_structure` band
|
|
234
|
+
compare, and SVG icon/legend fixes until the build matched the reference to
|
|
235
|
+
within 1px of total page height (10,871px vs 10,870px). This is the
|
|
236
|
+
**structure-before-pixels** doctrine at its strictest — band maps compared,
|
|
237
|
+
never just totals.
|
|
238
|
+
|
|
239
|
+

|
|
240
|
+
|
|
241
|
+
**3. The Meridian** (`C:/Users/Afjal/editorial-site/`) — an editorial journal
|
|
242
|
+
built from ORDR/Hearst references: the first build to run the whole loop
|
|
243
|
+
end-to-end. `analyze_page_structure` caught a masthead band bug by comparing
|
|
244
|
+
the build's band map against the reference's; the reference captures,
|
|
245
|
+
motion film, and the reusable pre-scroll capture script live in
|
|
246
|
+
`editorial-site/_qa/`.
|
|
247
|
+
|
|
248
|
+

|
|
249
|
+
|
|
250
|
+

|
|
251
|
+
|
|
252
|
+
## Skills used to build these
|
|
253
|
+
|
|
254
|
+
| Skill | Role in the builds |
|
|
255
|
+
|---|---|
|
|
256
|
+
| `awwwards-inspiration` | The 8-step loop itself (ships with this package): search → judge from screenshots → design DNA → capture/motion study → state direction → build → band-map verify. |
|
|
257
|
+
| `gsap-scrolltrigger` | The horizontal pin + `containerAnimation` pattern (ease `"none"`, one-shot entrances) driving both Fallow Press horizontal pages. |
|
|
258
|
+
| `gsap-core` / `gsap-timeline` | Tween composition and sequenced hero entrances (torn-paper drop, panel copy rises). |
|
|
259
|
+
| `frontend-design` | Typography, palette and layout judgment applied when translating reference DNA into original pages. |
|
|
260
|
+
| `lenis` (library, via skill guidance) | smooth scrolling synced to ScrollTrigger on the Fallow Press home page. |
|
|
261
|
+
| `tailwindcss` / plain CSS | All builds are plain hand-rolled CSS — flat 2D, no frameworks needed. |
|
|
262
|
+
|
|
263
|
+
Reduced-motion, JS-less visits, and capture tools all get graceful fallbacks
|
|
264
|
+
(vertical stacks; progressive-enhancement reveals).
|
|
265
|
+
|
|
266
|
+
**What the verification loop caught** — proof the structure-before-pixels
|
|
267
|
+
doctrine is load-bearing:
|
|
268
|
+
|
|
269
|
+
- Element **posters lie**: the first showcase build was designed from poster
|
|
270
|
+
frames alone and rendered a spinning 3D ring as *floating static cards*.
|
|
271
|
+
Downloading the element videos (`get_site_elements`) and frame-tiling them
|
|
272
|
+
revealed the motion truth — now the skill mandates studying motion before
|
|
273
|
+
animating.
|
|
274
|
+
- Full-page captures of reveal-on-scroll builds showed blank sections: `.reveal`
|
|
275
|
+
animation state vs capture's no-scroll reality. Builds ship
|
|
276
|
+
content-visible-without-JS progressive enhancement.
|
|
277
|
+
- Horizontal-panel copy vanished **mid-view** on the Fallow Press pages:
|
|
278
|
+
`toggleActions` reverse reverts entrances while a 100vw panel is still
|
|
279
|
+
holding the viewport (see above).
|
|
280
|
+
- Band-map compare kept the references' rhythm instead of drifting on
|
|
281
|
+
section heights (Cerebrium, The Meridian).
|
|
282
|
+
|
|
283
|
+
Prompt counts: **3** for the original showcase build (the build ask, the
|
|
284
|
+
motion correction that exposed the poster-lie, the structure pass) and
|
|
285
|
+
**1** for Fallow Press ("create a new website using our MCP and skills… no
|
|
286
|
+
3D websites") — its two follow-ups were caught by the QA loop, not by the
|
|
287
|
+
user. Each correction became doctrine in the shipped `awwwards-inspiration`
|
|
288
|
+
skill: frame-study element videos before animating; judge page architecture
|
|
289
|
+
from the studied passages; tile per element, not one giant filmstrip;
|
|
290
|
+
capture live sites and animation **before** building.
|
|
291
|
+
|
|
292
|
+
## Can awwwards-mcp crawl the sitemap? (robots.txt notes)
|
|
293
|
+
|
|
294
|
+
The awwwards.com `robots.txt` advertises
|
|
295
|
+
`Sitemap: https://www.awwwards.com/sitemap.xml` and — verified live
|
|
296
|
+
2026-09-18 — **that sitemap URL returns a soft-404 HTML page** (as do common
|
|
297
|
+
child names like `/sitemap-websites.xml`). So sitemap discovery isn't
|
|
298
|
+
currently a path to more data; the polite crawl surface is exactly what the
|
|
299
|
+
indexer uses:
|
|
300
|
+
|
|
301
|
+
- **Allowed and used**: `/websites/`, `/websites/<filter>/`, `/sites/<slug>`
|
|
302
|
+
(one filter per URL; deep pagination stays un-crawled).
|
|
303
|
+
- **Disallowed and never fetched**: `/tag/`, `/search-websites`,
|
|
304
|
+
`/websites/?` (query-string pagination), `/elements/*`, `/vote/`,
|
|
305
|
+
favourites/likes/follows, and the rest of the 33 rules.
|
|
306
|
+
- Our client (`src/awwwards.ts` `buildFilterUrl`) constructs **only**
|
|
307
|
+
`/websites/…` paths at 1 request/second — the loop stays inside the
|
|
308
|
+
published rules by construction, not by convention.
|
|
309
|
+
|
|
310
|
+
## Contributing
|
|
311
|
+
|
|
312
|
+
PRs welcome! The project especially needs **parser-drift fixes** — when live
|
|
313
|
+
awwwards.com markup changes, a fresh HTML snapshot attached to an issue often
|
|
314
|
+
becomes the new test fixture and the fastest merged PR. See
|
|
315
|
+
[CONTRIBUTING.md](CONTRIBUTING.md) for the full guide:
|
|
316
|
+
|
|
317
|
+
**Parser-drift is monitored automatically.** A probe script
|
|
318
|
+
([scripts/parser-drift-probe.mjs](scripts/parser-drift-probe.mjs),
|
|
319
|
+
`npm run drift`) checks every markup anchor the parsers depend on — the
|
|
320
|
+
`split`/`indexOf`/regex literals in `src/parsers.ts` — against the live
|
|
321
|
+
listing and detail pages (2 fetches, 1 request/second, same politeness as
|
|
322
|
+
the client). A daily GitHub Action ([.github/workflows/parser-drift.yml](.github/workflows/parser-drift.yml))
|
|
323
|
+
runs it and, on drift, opens/updates a single tracking issue with the exact
|
|
324
|
+
anchors that changed (and auto-closes it when a later run is green). To run
|
|
325
|
+
it yourself: `npm run drift` (live, exit code 0/1/2) or `npm run drift -- --fixture`
|
|
326
|
+
(offline, checks the committed fixtures still feed every anchor). Raw HTML
|
|
327
|
+
is never diffed or stored — anchors only fire when the parsers actually
|
|
328
|
+
break, so there are no false alarms from cosmetic tweaks.
|
|
329
|
+
|
|
330
|
+
- Development setup & project layout (offline fixture-tested, no network in tests)
|
|
331
|
+
- How to create a PR: fork → `fix/`/`feat/`/`docs/` branch → typecheck + tests → PR template
|
|
332
|
+
- The politeness constraints new code must keep (1 req/s, robots.txt paths, light runtime deps)
|
|
333
|
+
|
|
334
|
+
Bugs and feature ideas start as
|
|
335
|
+
[issues](https://github.com/INSANE0777/Awwwards-mcp/issues/new/choose) with
|
|
336
|
+
templates. Security problems go privately — see
|
|
337
|
+
[SECURITY.md](SECURITY.md). By participating you agree to the
|
|
338
|
+
[Code of Conduct](CODE_OF_CONDUCT.md).
|
|
339
|
+
|
|
340
|
+
## Development
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
npm install
|
|
344
|
+
npm test # offline unit tests against committed HTML fixtures
|
|
345
|
+
npm run smoke # manual live smoke test against awwwards.com
|
|
346
|
+
npm run build # compile to dist/
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
MIT — see [LICENSE](LICENSE).
|
package/dist/capture.js
CHANGED
|
@@ -6,6 +6,7 @@ import { join } from "node:path";
|
|
|
6
6
|
// CAPTURE_INSTALL_HINT from here) — both sides only use the other's bindings
|
|
7
7
|
// at call time, which ESM resolves fine.
|
|
8
8
|
import { preScroll } from "./structure.js";
|
|
9
|
+
import { resolveViewport } from "./viewport.js";
|
|
9
10
|
export const CAPTURE_INSTALL_HINT = "Full-page capture needs Playwright, which is an optional dependency.\n" +
|
|
10
11
|
"Install it with: npm install -D playwright && npx playwright install chromium\n" +
|
|
11
12
|
"Then retry the capture or structure tool.";
|
|
@@ -29,7 +30,12 @@ loader = () => import("playwright"), opts) {
|
|
|
29
30
|
}
|
|
30
31
|
try {
|
|
31
32
|
const waitStrategy = opts?.waitStrategy ?? "load";
|
|
32
|
-
|
|
33
|
+
// Same viewport-profile split as analyzePageStructure (structure.ts):
|
|
34
|
+
// width/height fill the `viewport` key, mobile-profile flags spread in as
|
|
35
|
+
// sibling context options. Desktop resolves to no extra fields, so the
|
|
36
|
+
// default call shape is unchanged.
|
|
37
|
+
const { width, height, ...contextOpts } = resolveViewport(opts?.viewport);
|
|
38
|
+
const page = await browser.newPage({ viewport: { width, height }, ...contextOpts });
|
|
33
39
|
await page.goto(url, { waitUntil: waitStrategy, timeout: 45_000 });
|
|
34
40
|
// "load" can fire before late XHRs settle, so give the page a fixed
|
|
35
41
|
// settle window; networkidle already means the network went quiet.
|
package/dist/cli.js
CHANGED
|
@@ -23,6 +23,10 @@ const waitStrategySchema = z
|
|
|
23
23
|
.enum(["load", "networkidle"])
|
|
24
24
|
.default("load")
|
|
25
25
|
.describe("'load' + settle works on heavy sites; 'networkidle' waits for total quiet");
|
|
26
|
+
const viewportSchema = z
|
|
27
|
+
.enum(["desktop", "mobile"])
|
|
28
|
+
.default("desktop")
|
|
29
|
+
.describe("desktop = 1440x900 (default); mobile = 390x844 iPhone-class with deviceScaleFactor 3, isMobile + hasTouch");
|
|
26
30
|
const cacheRoot = process.env.AWWWARDS_CACHE_DIR ?? join(homedir(), ".awwwards-mcp");
|
|
27
31
|
let cache;
|
|
28
32
|
try {
|
|
@@ -77,11 +81,13 @@ server.tool("list_categories", "List the filter taxonomy available on Awwwards:
|
|
|
77
81
|
server.tool("capture_live_site", "Take a fresh full-page screenshot of a live website URL using a headless browser. Requires the optional playwright dependency.", {
|
|
78
82
|
url: z.string().url().describe("Absolute URL of the site to capture"),
|
|
79
83
|
waitStrategy: waitStrategySchema,
|
|
84
|
+
viewport: viewportSchema,
|
|
80
85
|
}, (args) => asMcpResult(handlers.capture_live_site(args)));
|
|
81
86
|
server.tool("analyze_page_structure", "Extract a page's section band map (tag, label, background color, offset, height per band) via a headless browser. Works on live URLs and file:// paths — use it to compare a reference site's structure against your local build.", {
|
|
82
87
|
url: z.string().url().describe("Absolute URL (https:// or file://) of the page to analyze"),
|
|
83
88
|
maxBands: z.number().int().min(5).max(60).default(40).describe("Cap on returned bands"),
|
|
84
89
|
waitStrategy: waitStrategySchema,
|
|
90
|
+
viewport: viewportSchema,
|
|
85
91
|
}, (args) => asMcpResult(handlers.analyze_page_structure(args)));
|
|
86
92
|
server.tool("record_site_motion", "Record a short motion-through video of a live website — preloader, scroll-triggered and hover/cursor animations — and return an inline filmstrip JPEG plus the .webm path. Requires the optional playwright and ffmpeg-static dependencies.", {
|
|
87
93
|
url: z.string().url().describe("Absolute URL of the site to record"),
|
|
@@ -93,6 +99,7 @@ server.tool("record_site_motion", "Record a short motion-through video of a live
|
|
|
93
99
|
.default(16)
|
|
94
100
|
.describe("Filmstrip tile count (default 16 → a 4x4 grid)"),
|
|
95
101
|
waitStrategy: waitStrategySchema,
|
|
102
|
+
viewport: viewportSchema,
|
|
96
103
|
}, (args) => asMcpResult(handlers.record_site_motion(args)));
|
|
97
104
|
// Auto-refresh: if the index is stale (or absent) and no crawl is running,
|
|
98
105
|
// re-index in the background. Serving is never blocked; errors are stderr-only.
|
package/dist/motion.js
CHANGED
|
@@ -4,6 +4,7 @@ import { existsSync, mkdirSync, mkdtempSync, readdirSync, renameSync, rmSync, st
|
|
|
4
4
|
import { readFile } from "node:fs/promises";
|
|
5
5
|
import { join } from "node:path";
|
|
6
6
|
import { CAPTURE_INSTALL_HINT } from "./capture.js";
|
|
7
|
+
import { resolveViewport } from "./viewport.js";
|
|
7
8
|
export const MOTION_FFMPEG_HINT = "Motion recording needs ffmpeg-static, which is an optional dependency.\n" +
|
|
8
9
|
"Install it with: npm install -D ffmpeg-static\n" +
|
|
9
10
|
"Then retry record_site_motion.";
|
|
@@ -167,9 +168,17 @@ export async function recordSiteMotion(url, opts) {
|
|
|
167
168
|
const videoTmp = mkdtempSync(join(opts.cacheImagesDir, ".video-tmp-"));
|
|
168
169
|
const waitStrategy = opts.waitStrategy ?? "load";
|
|
169
170
|
try {
|
|
171
|
+
// Viewport profile split (same as structure/capture): width/height fill
|
|
172
|
+
// the `viewport` key; the mobile-profile flags (deviceScaleFactor/
|
|
173
|
+
// isMobile/hasTouch) spread in as sibling context options AFTER the
|
|
174
|
+
// existing fields. recordVideo.size derives from the same profile so the
|
|
175
|
+
// video canvas matches the viewport (desktop keeps the 1440x900 canvas; a
|
|
176
|
+
// mobile recording gets a 390x844 canvas instead of a pillarboxed one).
|
|
177
|
+
const { width, height, ...contextOpts } = resolveViewport(opts.viewport);
|
|
170
178
|
const context = await browser.newContext({
|
|
171
|
-
viewport: { width
|
|
172
|
-
recordVideo: { dir: videoTmp, size: { width
|
|
179
|
+
viewport: { width, height },
|
|
180
|
+
recordVideo: { dir: videoTmp, size: { width, height } },
|
|
181
|
+
...contextOpts,
|
|
173
182
|
});
|
|
174
183
|
try {
|
|
175
184
|
const page = await context.newPage();
|
package/dist/server.js
CHANGED
|
@@ -419,7 +419,12 @@ export function createHandlers(deps) {
|
|
|
419
419
|
// the injectable playwright loader — opts must land in fourth place.
|
|
420
420
|
const capture = deps.captureFn ??
|
|
421
421
|
((url, imagesDir, opts) => import("./capture.js").then((m) => m.captureLiveSite(url, imagesDir, undefined, opts)));
|
|
422
|
-
|
|
422
|
+
// The tool schema defaults viewport to "desktop" (zod); the ?? keeps
|
|
423
|
+
// direct handler calls on the same explicit path.
|
|
424
|
+
const result = await capture(args.url, cache.imagesDir, {
|
|
425
|
+
waitStrategy: args.waitStrategy,
|
|
426
|
+
viewport: args.viewport ?? "desktop",
|
|
427
|
+
});
|
|
423
428
|
if ("error" in result)
|
|
424
429
|
return { content: [text(result.error)], isError: true };
|
|
425
430
|
return {
|
|
@@ -442,6 +447,7 @@ export function createHandlers(deps) {
|
|
|
442
447
|
((url, maxBands, opts) => import("./structure.js").then((m) => m.analyzePageStructure(url, undefined, maxBands, opts)));
|
|
443
448
|
const structure = await analyze(args.url, args.maxBands, {
|
|
444
449
|
waitStrategy: args.waitStrategy,
|
|
450
|
+
viewport: args.viewport ?? "desktop",
|
|
445
451
|
});
|
|
446
452
|
if ("error" in structure)
|
|
447
453
|
return { content: [text(structure.error)], isError: true };
|
|
@@ -454,14 +460,15 @@ export function createHandlers(deps) {
|
|
|
454
460
|
async function record_site_motion(args) {
|
|
455
461
|
try {
|
|
456
462
|
// Lazy default: playwright/ffmpeg are only touched when the tool runs.
|
|
457
|
-
// The default forwards motionOpts wholesale, so waitStrategy
|
|
458
|
-
// recordSiteMotion's MotionOpts (which already accepts
|
|
463
|
+
// The default forwards motionOpts wholesale, so waitStrategy and viewport
|
|
464
|
+
// flow into recordSiteMotion's MotionOpts (which already accepts both).
|
|
459
465
|
const motion = deps.motionFn ??
|
|
460
466
|
((url, motionOpts) => import("./motion.js").then((m) => m.recordSiteMotion(url, motionOpts)));
|
|
461
467
|
const result = await motion(args.url, {
|
|
462
468
|
cacheImagesDir: cache.imagesDir,
|
|
463
469
|
frames: args.frames,
|
|
464
470
|
waitStrategy: args.waitStrategy,
|
|
471
|
+
viewport: args.viewport ?? "desktop",
|
|
465
472
|
});
|
|
466
473
|
if ("error" in result)
|
|
467
474
|
return { content: [text(result.error)], isError: true };
|
package/dist/structure.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CAPTURE_INSTALL_HINT } from "./capture.js";
|
|
2
|
+
import { resolveViewport } from "./viewport.js";
|
|
2
3
|
// Runs IN THE PAGE via page.evaluate. Collects full-width, tall, opaque
|
|
3
4
|
// elements as band candidates; body is always the base candidate. Gradient
|
|
4
5
|
// shorthand backgrounds leave backgroundColor transparent — such sections
|
|
@@ -182,7 +183,12 @@ export async function analyzePageStructure(url, loader = () => import("playwrigh
|
|
|
182
183
|
}
|
|
183
184
|
try {
|
|
184
185
|
const waitStrategy = opts?.waitStrategy ?? "load";
|
|
185
|
-
|
|
186
|
+
// Viewport profile split: width/height fill playwright's `viewport` key;
|
|
187
|
+
// the mobile-profile flags (deviceScaleFactor/isMobile/hasTouch) are
|
|
188
|
+
// sibling context options. The desktop profile resolves to no extra
|
|
189
|
+
// fields, so the default call shape is unchanged.
|
|
190
|
+
const { width, height, ...contextOpts } = resolveViewport(opts?.viewport);
|
|
191
|
+
const page = await browser.newPage({ viewport: { width, height }, ...contextOpts });
|
|
186
192
|
await page.goto(url, { waitUntil: waitStrategy, timeout: 45_000 });
|
|
187
193
|
// "load" can fire before late XHRs settle, so give the page a fixed
|
|
188
194
|
// settle window; networkidle already means the network went quiet.
|
package/dist/viewport.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "awwwards-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Free MCP server giving AI agents design inspiration from Awwwards: search award-winning sites with inline screenshots and extract design DNA.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -13,6 +13,24 @@
|
|
|
13
13
|
"README.md",
|
|
14
14
|
"skills"
|
|
15
15
|
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"awwwards",
|
|
20
|
+
"design",
|
|
21
|
+
"inspiration",
|
|
22
|
+
"web-design",
|
|
23
|
+
"screenshots",
|
|
24
|
+
"agent-tools"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/INSANE0777/Awwwards-mcp.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/INSANE0777/Awwwards-mcp/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/INSANE0777/Awwwards-mcp#readme",
|
|
16
34
|
"engines": {
|
|
17
35
|
"node": ">=22.13.0"
|
|
18
36
|
},
|
|
@@ -38,7 +38,9 @@ Run this loop before building anything visual:
|
|
|
38
38
|
full-page PNG — every section, top to bottom. Cached Awwwards screenshots
|
|
39
39
|
are hero-only crops (~880×660) and hide everything below the fold: the
|
|
40
40
|
sections that make a site's structure distinctive (pricing, feature
|
|
41
|
-
layouts, contrast breaks, footer) were never visible in them.
|
|
41
|
+
layouts, contrast breaks, footer) were never visible in them. For
|
|
42
|
+
mobile-excellence references pass `viewport: "mobile"` — and capture BOTH
|
|
43
|
+
viewports when the desktop and mobile designs diverge.
|
|
42
44
|
5. **Get the design DNA.** Call `get_site_details` on the top pick for its
|
|
43
45
|
palette, technologies, design elements, awards, and description. If it
|
|
44
46
|
reports a layout-drift error, fall back to judging the shortlisted
|
|
@@ -68,8 +70,10 @@ Run this loop before building anything visual:
|
|
|
68
70
|
8. **Verify structure, then polish.** After building, capture your own build
|
|
69
71
|
full-page (`capture_live_site` on its `file://` or served URL) and run
|
|
70
72
|
`analyze_page_structure` on BOTH the reference and the build. Compare band
|
|
71
|
-
maps section by section (count, order, backgrounds, heights).
|
|
72
|
-
|
|
73
|
+
maps section by section (count, order, backgrounds, heights). Match the
|
|
74
|
+
reference's viewport when comparing: for mobile-excellence references pass
|
|
75
|
+
`viewport: "mobile"`, and capture BOTH viewports when the design diverges.
|
|
76
|
+
Fix distribution mismatches first — a section that is 3× the reference's height
|
|
73
77
|
is a structural bug no amount of pixel polish fixes. Match the reference's
|
|
74
78
|
band structure, never just its total height.
|
|
75
79
|
|
|
@@ -68,6 +68,9 @@ Capture rules that prevent re-shoots:
|
|
|
68
68
|
- **Capture BEFORE building** — the doctrine step. Review first, code second.
|
|
69
69
|
- Use a **consistent viewport** (1440×900 matches the QA scripts) so your
|
|
70
70
|
reference tiles and build tiles are comparable.
|
|
71
|
+
- For phone-class references pass `record_site_motion` a `viewport: "mobile"`
|
|
72
|
+
(390×844 @3x with isMobile + hasTouch) — and capture BOTH viewports when
|
|
73
|
+
the desktop and mobile designs diverge.
|
|
71
74
|
- **Pre-scroll** to fire lazy content, scroll back to top, then record —
|
|
72
75
|
otherwise reveal-on-scroll sections record as blank boxes.
|
|
73
76
|
- For multi-page sites, record **each page** you'll rebuild (home, projects,
|