hackshop-mcp 0.0.1 → 0.0.2

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 CHANGED
@@ -12,7 +12,27 @@ This is a hardware-knowledge layer on top of LLMs. Two tools, fifty hand-vetted
12
12
 
13
13
  ## Status
14
14
 
15
- V0.0.1 — local development only. Not yet on npm or the MCP registry.
15
+ V0.0.2 — published on npm. Install with `npx hackshop-mcp` or add to your MCP client config.
16
+
17
+ ## Install in 30 seconds
18
+
19
+ Add to your MCP client config (Claude Desktop / Claude Code / Cursor):
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "hackshop": {
25
+ "command": "npx",
26
+ "args": ["-y", "hackshop-mcp"],
27
+ "env": {
28
+ "ANTHROPIC_API_KEY": "sk-ant-..."
29
+ }
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ The `ANTHROPIC_API_KEY` env var is **optional but recommended**. The server first tries `sampling/createMessage` (host-delegated reasoning, no key needed). If your host doesn't support that — many don't yet — the server falls back to a direct Anthropic API call when this key is set. Without it, you'll get raw catalog matches in degraded mode.
16
36
 
17
37
  ## Tools
18
38
 
package/SAMPLING.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Sampling Contract
2
2
 
3
- This server uses MCP's `sampling/createMessage` to delegate LLM reasoning to the host. No Anthropic / OpenAI SDK is bundled. The host pays for tokens; users don't ship a second API key.
3
+ This server prefers MCP's `sampling/createMessage` to delegate LLM reasoning to the host. The host pays for tokens; users don't manage a second API key.
4
+
5
+ **Update (V0.0.2):** since not every MCP host implements sampling reliably (Claude Code does; Claude Desktop is spotty; many other clients don't yet), V0.0.2 adds an opt-in **direct Anthropic API fallback**. Set `ANTHROPIC_API_KEY` in the MCP server's env config and the server will use the Anthropic SDK directly when sampling fails. Without it, the server returns degraded responses (raw catalog matches with a clear note).
6
+
7
+ The fallback order is:
8
+ 1. `server.createMessage` (host sampling) — preferred
9
+ 2. retry `server.createMessage` once
10
+ 3. direct Anthropic API call IF `ANTHROPIC_API_KEY` is set
11
+ 4. degraded response (catalog matches by tag overlap, no reasoning)
4
12
 
5
13
  ## Boot probe
6
14
 
package/catalog.json CHANGED
@@ -30,6 +30,21 @@
30
30
  "community_size_bucket": "thriving",
31
31
  "notes": "Default tinkerer choice. Boring but always works. Use as baseline; recommend more interesting hardware first."
32
32
  },
33
+ {
34
+ "id": "raspberry-pi-zero-2w",
35
+ "name": "Raspberry Pi Zero 2 W",
36
+ "category": "sbc",
37
+ "idea_fit_tags": ["sbc", "low-power", "creative-coding", "kiosk", "iot"],
38
+ "hack_difficulty": 1,
39
+ "brick_risk": 1,
40
+ "brick_provenance": "founder-verified",
41
+ "last_verified": "2026-05-04",
42
+ "firmware_links": [
43
+ "https://www.raspberrypi.com/products/raspberry-pi-zero-2-w/"
44
+ ],
45
+ "community_size_bucket": "thriving",
46
+ "notes": "Smaller than Pi 4B, lower power. Good for projects that need to be embedded/hidden. Reasonable Wi-Fi. Slightly slow CPU but usually enough."
47
+ },
33
48
  {
34
49
  "id": "tidbyt-gen1",
35
50
  "name": "Tidbyt (Gen 1)",
@@ -60,7 +75,7 @@
60
75
  "https://forum.xda-developers.com/c/barnes-noble-nook.5045/"
61
76
  ],
62
77
  "community_size_bucket": "active",
63
- "notes": "2011 e-reader, runs Android 2.1 underneath. Well-documented root path via SD card image. Community projects use it as a wall-mounted dashboard / status display."
78
+ "notes": "2011 e-reader, Android 2.1 underneath. Well-documented root via SD card. Used widely as wall-mounted dashboard / status display."
64
79
  },
65
80
  {
66
81
  "id": "amazon-kindle-paperwhite-gen2",
@@ -76,7 +91,24 @@
76
91
  "https://github.com/koreader/koreader"
77
92
  ],
78
93
  "community_size_bucket": "thriving",
79
- "notes": "Older Kindles (PW2 era) jailbreak via known firmware paths; newer ones harder. Used widely as low-power dashboard / calendar / weather displays. KOReader runs custom apps on jailbroken units."
94
+ "notes": "Older Kindles (PW2 era) jailbreak via known firmware paths. Used widely as low-power dashboard / calendar / weather displays. KOReader runs custom apps."
95
+ },
96
+ {
97
+ "id": "kobo-clara-hd",
98
+ "name": "Kobo Clara HD",
99
+ "category": "display",
100
+ "idea_fit_tags": ["display", "e-ink", "low-power", "framed-art", "ambient", "kiosk"],
101
+ "hack_difficulty": 2,
102
+ "brick_risk": 2,
103
+ "brick_provenance": "community-reported",
104
+ "last_verified": "2026-05-04",
105
+ "firmware_links": [
106
+ "https://github.com/koreader/koreader",
107
+ "https://www.mobileread.com/forums/forumdisplay.php?f=247",
108
+ "https://github.com/Kobo-InkBox/inkbox"
109
+ ],
110
+ "community_size_bucket": "active",
111
+ "notes": "6-inch e-ink reader, Linux underneath. Telnet/SSH via 'developer mode' file. KOReader and InkBox provide custom OSes. More open than Kindle."
80
112
  },
81
113
  {
82
114
  "id": "ipad-2",
@@ -88,10 +120,309 @@
88
120
  "brick_provenance": "community-reported",
89
121
  "last_verified": "2026-05-04",
90
122
  "firmware_links": [
91
- "https://support.apple.com/guided-access",
92
- "https://github.com/sponsorpay/iPad-Kiosk"
123
+ "https://support.apple.com/guided-access"
93
124
  ],
94
125
  "community_size_bucket": "thriving",
95
- "notes": "Dirt-cheap on eBay. Guided Access locks it to a single app for a kiosk display. No jailbreak required for calendar/dashboard use cases. Older models can run iOS 9.x with vintage Safari."
126
+ "notes": "Dirt-cheap on eBay. Guided Access locks it to a single app for kiosk use. No jailbreak required for calendar/dashboard. Older models can run iOS 9.x."
127
+ },
128
+ {
129
+ "id": "philips-hue-go",
130
+ "name": "Philips Hue Go (1st gen)",
131
+ "category": "bulb",
132
+ "idea_fit_tags": ["bulb", "color-cycle", "ambient", "low-power"],
133
+ "hack_difficulty": 2,
134
+ "brick_risk": 1,
135
+ "brick_provenance": "community-reported",
136
+ "last_verified": "2026-05-04",
137
+ "firmware_links": [
138
+ "https://github.com/home-assistant/core/tree/dev/homeassistant/components/hue",
139
+ "https://github.com/openhab/openhab-addons/tree/main/bundles/org.openhab.binding.hue"
140
+ ],
141
+ "community_size_bucket": "thriving",
142
+ "notes": "Portable Hue lamp with battery. Pairs with Zigbee2MQTT or Home Assistant. Programmable color animations are the killer use case for ambient/breathing apps."
143
+ },
144
+ {
145
+ "id": "esp32-devkit-c",
146
+ "name": "ESP32 DevKit-C",
147
+ "category": "sbc",
148
+ "idea_fit_tags": ["sbc", "low-power", "iot", "creative-coding", "sensor"],
149
+ "hack_difficulty": 1,
150
+ "brick_risk": 1,
151
+ "brick_provenance": "founder-verified",
152
+ "last_verified": "2026-05-04",
153
+ "firmware_links": [
154
+ "https://docs.espressif.com/projects/esp-idf/en/latest/esp32/",
155
+ "https://github.com/espressif/arduino-esp32",
156
+ "https://micropython.org/download/esp32/"
157
+ ],
158
+ "community_size_bucket": "thriving",
159
+ "notes": "Default ESP32 board for tinkering. Wi-Fi + BLE, runs MicroPython or Arduino. Common in DIY ambient displays, sensor relays, custom IoT projects."
160
+ },
161
+ {
162
+ "id": "olimex-esp32-poe",
163
+ "name": "Olimex ESP32-POE",
164
+ "category": "sbc",
165
+ "idea_fit_tags": ["sbc", "low-power", "iot", "network"],
166
+ "hack_difficulty": 2,
167
+ "brick_risk": 1,
168
+ "brick_provenance": "community-reported",
169
+ "last_verified": "2026-05-04",
170
+ "firmware_links": [
171
+ "https://www.olimex.com/Products/IoT/ESP32/ESP32-POE/",
172
+ "https://github.com/OLIMEX/ESP32-POE"
173
+ ],
174
+ "community_size_bucket": "small",
175
+ "notes": "ESP32 with PoE Ethernet. Single cable for power and network — good for wall-mounted displays/sensors with no wall-warts."
176
+ },
177
+ {
178
+ "id": "tp-link-tl-wr841n",
179
+ "name": "TP-Link TL-WR841N (router)",
180
+ "category": "router",
181
+ "idea_fit_tags": ["network", "iot", "low-power"],
182
+ "hack_difficulty": 3,
183
+ "brick_risk": 3,
184
+ "brick_provenance": "community-reported",
185
+ "last_verified": "2026-05-04",
186
+ "firmware_links": [
187
+ "https://openwrt.org/toh/tp-link/tl-wr841nd"
188
+ ],
189
+ "community_size_bucket": "active",
190
+ "notes": "Cheap consumer router. OpenWrt support varies by hardware revision (v8+ are constrained). Listed for projects needing a tiny Linux box with Wi-Fi where Pi is overkill."
191
+ },
192
+ {
193
+ "id": "wyze-cam-v2",
194
+ "name": "Wyze Cam v2",
195
+ "category": "sensor",
196
+ "idea_fit_tags": ["sensor", "iot", "low-power"],
197
+ "hack_difficulty": 3,
198
+ "brick_risk": 3,
199
+ "brick_provenance": "community-reported",
200
+ "last_verified": "2026-05-04",
201
+ "firmware_links": [
202
+ "https://github.com/openmiko/openmiko",
203
+ "https://github.com/HclX/WyzeHacks"
204
+ ],
205
+ "community_size_bucket": "active",
206
+ "notes": "$25 cloud camera. Custom firmware (openmiko) gives RTSP and frees from Wyze cloud. Good for home automation streams; the older v2 is the documented target."
207
+ },
208
+ {
209
+ "id": "pebble-time",
210
+ "name": "Pebble Time (smartwatch)",
211
+ "category": "wearable",
212
+ "idea_fit_tags": ["wearable", "display", "e-ink", "low-power"],
213
+ "hack_difficulty": 2,
214
+ "brick_risk": 2,
215
+ "brick_provenance": "community-reported",
216
+ "last_verified": "2026-05-04",
217
+ "firmware_links": [
218
+ "https://rebble.io/",
219
+ "https://help.rebble.io/"
220
+ ],
221
+ "community_size_bucket": "active",
222
+ "notes": "Discontinued e-paper smartwatch. Rebble.io keeps it alive with free app store and custom watchfaces. Cheap on eBay; week-long battery; SDK still works."
223
+ },
224
+ {
225
+ "id": "chromecast-1st-gen",
226
+ "name": "Google Chromecast (1st gen, H2G2-42)",
227
+ "category": "display",
228
+ "idea_fit_tags": ["display", "low-power", "creative-coding", "ambient"],
229
+ "hack_difficulty": 3,
230
+ "brick_risk": 3,
231
+ "brick_provenance": "community-reported",
232
+ "last_verified": "2026-05-04",
233
+ "firmware_links": [
234
+ "https://wiki.exotic.org.uk/wiki/Chromecast",
235
+ "https://github.com/chromecast-mods"
236
+ ],
237
+ "community_size_bucket": "small",
238
+ "notes": "$5-15 on eBay. Original Chromecast (not Ultra/Audio). Linux-based; community 'eureka-rom' projects exist for full control. HDMI output for an ambient TV display."
239
+ },
240
+ {
241
+ "id": "dell-wyse-5070",
242
+ "name": "Dell Wyse 5070 (thin client)",
243
+ "category": "mini-pc",
244
+ "idea_fit_tags": ["sbc", "mini-pc", "kiosk", "low-power"],
245
+ "hack_difficulty": 1,
246
+ "brick_risk": 1,
247
+ "brick_provenance": "community-reported",
248
+ "last_verified": "2026-05-04",
249
+ "firmware_links": [
250
+ "https://parkytowers.me.uk/thin/Dell5070/"
251
+ ],
252
+ "community_size_bucket": "active",
253
+ "notes": "x86 thin client (Pentium Silver, 4GB RAM, fanless). $40-80 on eBay. Boots any Linux distro from USB. Better than a Pi for desktop-grade workloads, low power."
254
+ },
255
+ {
256
+ "id": "hp-t620-plus",
257
+ "name": "HP t620 Plus (thin client)",
258
+ "category": "mini-pc",
259
+ "idea_fit_tags": ["sbc", "mini-pc", "kiosk", "low-power", "industrial"],
260
+ "hack_difficulty": 1,
261
+ "brick_risk": 1,
262
+ "brick_provenance": "community-reported",
263
+ "last_verified": "2026-05-04",
264
+ "firmware_links": [
265
+ "https://parkytowers.me.uk/thin/hp/T620P/"
266
+ ],
267
+ "community_size_bucket": "active",
268
+ "notes": "AMD GX-420CA, dual NIC, 4 USB. $30-60. Popular as a tiny pfSense/OPNsense firewall, Pi-hole box, or home server. Common in homelab community."
269
+ },
270
+ {
271
+ "id": "nintendo-3ds",
272
+ "name": "Nintendo 3DS",
273
+ "category": "handheld",
274
+ "idea_fit_tags": ["retrogaming", "handheld", "display", "input"],
275
+ "hack_difficulty": 2,
276
+ "brick_risk": 4,
277
+ "brick_provenance": "community-reported",
278
+ "last_verified": "2026-05-04",
279
+ "firmware_links": [
280
+ "https://3ds.hacks.guide/",
281
+ "https://github.com/LumaTeam/Luma3DS"
282
+ ],
283
+ "community_size_bucket": "thriving",
284
+ "notes": "Massive modding scene. CFW (Luma3DS) enables homebrew, emulators, region-free play. Brick risk REAL on bad installs — the 3ds.hacks.guide walkthrough is the canonical source."
285
+ },
286
+ {
287
+ "id": "atari-flashback",
288
+ "name": "Atari Flashback (HDMI plug-and-play)",
289
+ "category": "handheld",
290
+ "idea_fit_tags": ["retrogaming", "creative-coding", "input"],
291
+ "hack_difficulty": 4,
292
+ "brick_risk": 4,
293
+ "brick_provenance": "llm-inferred",
294
+ "last_verified": "2026-05-04",
295
+ "firmware_links": [
296
+ "https://atariage.com/forums/forum/116-modern-classic/"
297
+ ],
298
+ "community_size_bucket": "small",
299
+ "notes": "Retro-styled mini-console. Hacking is non-trivial. Llm-inferred + handheld = brick-risk score will be stripped from output by safety rule."
300
+ },
301
+ {
302
+ "id": "arduino-uno-r3",
303
+ "name": "Arduino Uno R3",
304
+ "category": "sbc",
305
+ "idea_fit_tags": ["sbc", "low-power", "iot", "creative-coding", "sensor", "input"],
306
+ "hack_difficulty": 1,
307
+ "brick_risk": 1,
308
+ "brick_provenance": "founder-verified",
309
+ "last_verified": "2026-05-04",
310
+ "firmware_links": [
311
+ "https://docs.arduino.cc/hardware/uno-rev3"
312
+ ],
313
+ "community_size_bucket": "thriving",
314
+ "notes": "The classic. Not Wi-Fi, not powerful, but trivially programmable for sensors, motor control, blinkenlights. First step before ESP32 for many tinkerers."
315
+ },
316
+ {
317
+ "id": "waveshare-7-5-epaper-hat",
318
+ "name": "Waveshare 7.5\" e-Paper HAT (V2)",
319
+ "category": "display",
320
+ "idea_fit_tags": ["display", "e-ink", "low-power", "framed-art", "ambient", "kiosk"],
321
+ "hack_difficulty": 2,
322
+ "brick_risk": 1,
323
+ "brick_provenance": "community-reported",
324
+ "last_verified": "2026-05-04",
325
+ "firmware_links": [
326
+ "https://www.waveshare.com/wiki/7.5inch_e-Paper_HAT",
327
+ "https://github.com/waveshare/e-Paper",
328
+ "https://github.com/speedyg0nz/MagInkCal"
329
+ ],
330
+ "community_size_bucket": "thriving",
331
+ "notes": "800x480 mono e-paper, drives off Pi Zero W via SPI. THE canonical kitchen-calendar build (MagInkCal, InkyPi). Refresh ~2-5s; perfect for static daily layouts. Pair with Pi Zero W for the classic combo."
332
+ },
333
+ {
334
+ "id": "waveshare-10-3-epaper",
335
+ "name": "Waveshare 10.3\" e-Paper (HD, parallel/SPI)",
336
+ "category": "display",
337
+ "idea_fit_tags": ["display", "e-ink", "low-power", "framed-art", "ambient"],
338
+ "hack_difficulty": 3,
339
+ "brick_risk": 1,
340
+ "brick_provenance": "community-reported",
341
+ "last_verified": "2026-05-04",
342
+ "firmware_links": [
343
+ "https://www.waveshare.com/wiki/10.3inch_e-Paper",
344
+ "https://github.com/waveshare/e-Paper"
345
+ ],
346
+ "community_size_bucket": "active",
347
+ "notes": "1872x1404 mono e-ink. Larger than 7.5\" so suits a kitchen wall calendar or magazine-style display. Needs IT8951 driver board. More expensive (~$200+) but gorgeous."
348
+ },
349
+ {
350
+ "id": "pimoroni-inky-impression-7-3",
351
+ "name": "Pimoroni Inky Impression 7.3\" (7-color)",
352
+ "category": "display",
353
+ "idea_fit_tags": ["display", "e-ink", "low-power", "framed-art", "ambient", "color-cycle", "creative-coding"],
354
+ "hack_difficulty": 2,
355
+ "brick_risk": 1,
356
+ "brick_provenance": "community-reported",
357
+ "last_verified": "2026-05-04",
358
+ "firmware_links": [
359
+ "https://shop.pimoroni.com/products/inky-impression-7-3",
360
+ "https://github.com/pimoroni/inky"
361
+ ],
362
+ "community_size_bucket": "active",
363
+ "notes": "800x480 SEVEN-COLOR e-paper HAT. 7-color e-ink is rare — great for ambient art rotation, family photos, weather displays. Slow refresh (~30s) so static-frame use only. Pi Zero W compatible."
364
+ },
365
+ {
366
+ "id": "remarkable-2",
367
+ "name": "reMarkable 2 (e-paper tablet)",
368
+ "category": "display",
369
+ "idea_fit_tags": ["display", "e-ink", "low-power", "framed-art", "kiosk", "creative-coding"],
370
+ "hack_difficulty": 3,
371
+ "brick_risk": 2,
372
+ "brick_provenance": "community-reported",
373
+ "last_verified": "2026-05-04",
374
+ "firmware_links": [
375
+ "https://remarkable.guide/",
376
+ "https://github.com/Eeems-Org/oxide",
377
+ "https://github.com/toltec-dev/toltec"
378
+ ],
379
+ "community_size_bucket": "active",
380
+ "notes": "10.3\" mono e-paper with stylus. Linux underneath. Toltec package manager + Oxide launcher = full custom OS. Premium ($300+) but a polished tinkerer device. Expensive failure mode if bricked."
381
+ },
382
+ {
383
+ "id": "pimoroni-hyperpixel-4",
384
+ "name": "Pimoroni HyperPixel 4.0 (Pi LCD)",
385
+ "category": "display",
386
+ "idea_fit_tags": ["display", "kiosk", "creative-coding", "input", "framed-art"],
387
+ "hack_difficulty": 1,
388
+ "brick_risk": 1,
389
+ "brick_provenance": "community-reported",
390
+ "last_verified": "2026-05-04",
391
+ "firmware_links": [
392
+ "https://github.com/pimoroni/hyperpixel4"
393
+ ],
394
+ "community_size_bucket": "active",
395
+ "notes": "4-inch high-PPI IPS LCD that mounts directly on a Pi (no HDMI cable). Great for self-contained dashboards, smart-home controllers, pi-in-a-box projects. Touch variant available."
396
+ },
397
+ {
398
+ "id": "lilygo-ttgo-t5-epaper",
399
+ "name": "LILYGO TTGO T5 V2.3 (ESP32 + 2.13\" e-Paper)",
400
+ "category": "display",
401
+ "idea_fit_tags": ["display", "e-ink", "low-power", "iot", "wearable"],
402
+ "hack_difficulty": 2,
403
+ "brick_risk": 2,
404
+ "brick_provenance": "community-reported",
405
+ "last_verified": "2026-05-04",
406
+ "firmware_links": [
407
+ "https://github.com/Xinyuan-LilyGO/LilyGo-EPD47",
408
+ "https://www.lilygo.cc/products/t5-2-13-inch-e-paper"
409
+ ],
410
+ "community_size_bucket": "active",
411
+ "notes": "All-in-one ESP32 + 2.13\" e-paper. Tiny, battery-friendly, $20-30. Perfect for badge displays, weather stations, status indicators. Larger 4.7\" T5 variant exists for bigger projects."
412
+ },
413
+ {
414
+ "id": "boox-poke-3",
415
+ "name": "Boox Poke 3 (Android e-reader)",
416
+ "category": "display",
417
+ "idea_fit_tags": ["display", "e-ink", "low-power", "framed-art", "kiosk", "creative-coding"],
418
+ "hack_difficulty": 2,
419
+ "brick_risk": 3,
420
+ "brick_provenance": "community-reported",
421
+ "last_verified": "2026-05-04",
422
+ "firmware_links": [
423
+ "https://www.mobileread.com/forums/forumdisplay.php?f=275"
424
+ ],
425
+ "community_size_bucket": "small",
426
+ "notes": "6\" e-ink with stock Android 10 + Google Play. Sideload any Android app — Google Calendar, custom dashboards, kiosk launchers. Less hacking required than a Kindle/Kobo because it's already open."
96
427
  }
97
428
  ]
@@ -0,0 +1,9 @@
1
+ import type { DeviceEntry } from "./catalog/schema.js";
2
+ export interface DeviceLinks {
3
+ ebay_search_url: string;
4
+ hackaday_search_url: string;
5
+ reddit_search_url: string;
6
+ google_search_url: string;
7
+ }
8
+ export declare function buildEbayQuery(device: DeviceEntry): string;
9
+ export declare function buildLinks(device: DeviceEntry): DeviceLinks;
package/dist/links.js ADDED
@@ -0,0 +1,22 @@
1
+ // Generates real, clickable URLs for each device proposal. No API calls,
2
+ // no auth — pure URL templating. Lets users (or downstream agents like
3
+ // ebay-mcp) jump straight to live data.
4
+ function ebayCondition(brick_risk) {
5
+ if (brick_risk === null)
6
+ return "used";
7
+ return brick_risk >= 4 ? "working" : "used";
8
+ }
9
+ export function buildEbayQuery(device) {
10
+ const cond = ebayCondition(device.brick_risk);
11
+ return `${device.name} ${cond}`;
12
+ }
13
+ export function buildLinks(device) {
14
+ const ebayQuery = buildEbayQuery(device);
15
+ return {
16
+ ebay_search_url: `https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(ebayQuery)}&_sop=15`,
17
+ hackaday_search_url: `https://hackaday.com/?s=${encodeURIComponent(device.name)}`,
18
+ reddit_search_url: `https://www.reddit.com/search/?q=${encodeURIComponent(device.name + " hack")}&sort=new`,
19
+ google_search_url: `https://www.google.com/search?q=${encodeURIComponent(device.name + " custom firmware OR hack OR mod")}`,
20
+ };
21
+ }
22
+ //# sourceMappingURL=links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.js","sourceRoot":"","sources":["../src/links.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,uEAAuE;AACvE,wCAAwC;AAWxC,SAAS,aAAa,CAAC,UAAyB;IAC9C,IAAI,UAAU,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IACvC,OAAO,UAAU,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAmB;IAChD,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC9C,OAAO,GAAG,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAmB;IAC5C,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;IACzC,OAAO;QACL,eAAe,EAAE,wCAAwC,kBAAkB,CAAC,SAAS,CAAC,UAAU;QAChG,mBAAmB,EAAE,2BAA2B,kBAAkB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;QACjF,iBAAiB,EAAE,oCAAoC,kBAAkB,CAAC,MAAM,CAAC,IAAI,GAAG,OAAO,CAAC,WAAW;QAC3G,iBAAiB,EAAE,mCAAmC,kBAAkB,CAAC,MAAM,CAAC,IAAI,GAAG,iCAAiC,CAAC,EAAE;KAC5H,CAAC;AACJ,CAAC"}
package/dist/sampling.js CHANGED
@@ -1,12 +1,22 @@
1
- // Wraps sampling/createMessage with retry-once + degraded-response semantics.
2
- // Returns either parsed JSON of shape T, or null if the host failed twice.
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ // Wraps sampling/createMessage with retry-once + Anthropic-API fallback +
3
+ // degraded-response semantics. Returns either parsed JSON of shape T, or null
4
+ // if every path failed.
3
5
  //
4
- // The MCP server NEVER bundles an Anthropic/OpenAI SDK. All reasoning is
5
- // delegated to the host's LLM via this protocol. Cost: must verify host
6
- // support via boot probe (see scripts/smoke.ts and src/server.ts boot path).
6
+ // Order of attempts:
7
+ // 1. server.createMessage (host-delegated sampling) — preferred
8
+ // 2. retry server.createMessage once on transient failure
9
+ // 3. direct Anthropic API call IF process.env.ANTHROPIC_API_KEY is set — fallback
10
+ // 4. return null → caller produces a degraded response
11
+ //
12
+ // The fallback exists because not all MCP hosts implement sampling/createMessage
13
+ // today. Claude Code supports it; Claude Desktop's support has been spotty;
14
+ // Cursor / Cowork / other clients may not. Setting ANTHROPIC_API_KEY in your
15
+ // MCP client config (under "env") gives the user reasoning even on hosts
16
+ // without sampling.
7
17
  export async function sampleJson(opts) {
8
- const { server, systemPrompt, userPrompt, maxTokens = 1024 } = opts;
9
- const tryOnce = async () => {
18
+ const { server, systemPrompt, userPrompt, maxTokens = 1500 } = opts;
19
+ const tryHostSampling = async () => {
10
20
  try {
11
21
  const response = await server.createMessage({
12
22
  systemPrompt,
@@ -18,7 +28,6 @@ export async function sampleJson(opts) {
18
28
  ],
19
29
  maxTokens,
20
30
  modelPreferences: {
21
- // hint to the host: prefer a fast model; reasoning is light here.
22
31
  intelligencePriority: 0.6,
23
32
  speedPriority: 0.4,
24
33
  },
@@ -26,27 +35,73 @@ export async function sampleJson(opts) {
26
35
  const content = response.content;
27
36
  if (content.type !== "text")
28
37
  return null;
29
- const text = content.text.trim();
30
- // Strip a possible markdown code fence around the JSON.
31
- const fenced = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
32
- const jsonText = fenced ? fenced[1] : text;
33
- if (!jsonText)
34
- return null;
35
- return JSON.parse(jsonText);
38
+ return parseJsonLoose(content.text);
36
39
  }
37
40
  catch {
38
41
  return null;
39
42
  }
40
43
  };
41
- const first = await tryOnce();
44
+ // Attempt 1: host sampling
45
+ const first = await tryHostSampling();
42
46
  if (first !== null)
43
47
  return first;
44
- // One retry on transient failure.
45
- const second = await tryOnce();
46
- return second;
48
+ // Attempt 2: one retry
49
+ const second = await tryHostSampling();
50
+ if (second !== null)
51
+ return second;
52
+ // Attempt 3: direct Anthropic API fallback (env-gated)
53
+ const apiKey = process.env.ANTHROPIC_API_KEY;
54
+ if (apiKey) {
55
+ try {
56
+ const client = new Anthropic({ apiKey });
57
+ const response = await client.messages.create({
58
+ model: process.env.HACKSHOP_MODEL ?? "claude-sonnet-4-6",
59
+ max_tokens: maxTokens,
60
+ system: systemPrompt,
61
+ messages: [{ role: "user", content: userPrompt }],
62
+ });
63
+ const block = response.content[0];
64
+ if (block && block.type === "text") {
65
+ const parsed = parseJsonLoose(block.text);
66
+ if (parsed !== null) {
67
+ process.stderr.write("[hackshop-mcp] Sampling failed; used Anthropic API fallback (ANTHROPIC_API_KEY).\n");
68
+ return parsed;
69
+ }
70
+ }
71
+ }
72
+ catch (err) {
73
+ process.stderr.write(`[hackshop-mcp] Anthropic fallback failed: ${err.message}\n`);
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+ // Strip a possible markdown code fence around JSON. Tolerant of whitespace
79
+ // and explanatory prose around the fence.
80
+ function parseJsonLoose(text) {
81
+ const trimmed = text.trim();
82
+ // Look for fenced JSON anywhere in the string.
83
+ const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
84
+ let candidate = fenced ? fenced[1] : trimmed;
85
+ if (!candidate)
86
+ return null;
87
+ // If model returned prose + JSON, try to extract the first {...} block.
88
+ if (!candidate.trim().startsWith("{") && !candidate.trim().startsWith("[")) {
89
+ const start = candidate.indexOf("{");
90
+ const end = candidate.lastIndexOf("}");
91
+ if (start !== -1 && end > start) {
92
+ candidate = candidate.slice(start, end + 1);
93
+ }
94
+ }
95
+ try {
96
+ return JSON.parse(candidate);
97
+ }
98
+ catch {
99
+ return null;
100
+ }
47
101
  }
48
- // Boot-time probe to verify the host actually supports sampling/createMessage.
49
- // Returns true if the host responded; false if it errored or returned nothing.
102
+ // Boot-time probe to verify the host supports sampling/createMessage. Logs
103
+ // the result to stderr; never fatal. The Anthropic API fallback (if env-set)
104
+ // makes this informational rather than load-bearing.
50
105
  export async function probeSamplingSupport(server) {
51
106
  try {
52
107
  const response = await server.createMessage({
@@ -1 +1 @@
1
- {"version":3,"file":"sampling.js","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,2EAA2E;AAC3E,EAAE;AACF,yEAAyE;AACzE,wEAAwE;AACxE,6EAA6E;AAC7E,MAAM,CAAC,KAAK,UAAU,UAAU,CAAI,IAKnC;IACC,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS,GAAG,IAAI,EAAE,GAAG,IAAI,CAAC;IAEpE,MAAM,OAAO,GAAG,KAAK,IAAuB,EAAE;QAC5C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC;gBAC1C,YAAY;gBACZ,QAAQ,EAAE;oBACR;wBACE,IAAI,EAAE,MAAM;wBACZ,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE;qBAC5C;iBACF;gBACD,SAAS;gBACT,gBAAgB,EAAE;oBAChB,kEAAkE;oBAClE,oBAAoB,EAAE,GAAG;oBACzB,aAAa,EAAE,GAAG;iBACnB;aACF,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC;YACjC,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM;gBAAE,OAAO,IAAI,CAAC;YACzC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YAEjC,wDAAwD;YACxD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;YAC7D,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAC3C,IAAI,CAAC,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAE3B,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAM,CAAC;QACnC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IAEF,MAAM,KAAK,GAAG,MAAM,OAAO,EAAE,CAAC;IAC9B,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAEjC,kCAAkC;IAClC,MAAM,MAAM,GAAG,MAAM,OAAO,EAAE,CAAC;IAC/B,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+EAA+E;AAC/E,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAAc;IACvD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC;YAC1C,YAAY,EACV,mEAAmE;YACrE,QAAQ,EAAE;gBACR,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE;aAC3D;YACD,SAAS,EAAE,CAAC;SACb,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,MAAM,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"sampling.js","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import type { DeviceEntry } from "../catalog/schema.js";
3
+ import { type DeviceLinks } from "../links.js";
3
4
  export declare const assessHackabilityInput: z.ZodObject<{
4
5
  device_name: z.ZodString;
5
6
  }, "strip", z.ZodTypeAny, {
@@ -23,6 +24,7 @@ export interface AssessOutput {
23
24
  community_size: string;
24
25
  last_verified: string;
25
26
  notes: string;
27
+ links: DeviceLinks;
26
28
  };
27
29
  message?: string;
28
30
  }
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { applyBrickRiskSafety } from "../safety.js";
3
+ import { buildLinks } from "../links.js";
3
4
  export const assessHackabilityInput = z.object({
4
5
  device_name: z.string().min(1).max(200),
5
6
  });
@@ -22,6 +23,7 @@ export function assessHackability(input, catalog) {
22
23
  };
23
24
  }
24
25
  const safety = applyBrickRiskSafety(match);
26
+ const links = buildLinks(match);
25
27
  return {
26
28
  found: true,
27
29
  device: {
@@ -37,6 +39,7 @@ export function assessHackability(input, catalog) {
37
39
  community_size: match.community_size_bucket,
38
40
  last_verified: match.last_verified,
39
41
  notes: match.notes,
42
+ links,
40
43
  },
41
44
  };
42
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"assess_hackability.js","sourceRoot":"","sources":["../../src/tools/assess_hackability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;CACxC,CAAC,CAAC;AAuBH,qEAAqE;AACrE,SAAS,IAAI,CAAC,CAAS;IACrB,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAkB,EAClB,OAAsB;IAEtB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAEvC,8DAA8D;IAC9D,IAAI,KAAK,GAA4B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,WAAW,CAAC,CAAC;IACrF,IAAI,CAAC,KAAK;QAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC;IACjE,IAAI,CAAC,KAAK;QAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAExG,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,IAAI,KAAK,CAAC,WAAW,4JAA4J;SAC3L,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAE3C,OAAO;QACL,KAAK,EAAE,IAAI;QACX,MAAM,EAAE;YACN,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,IAAI,EAAE,uCAAuC;YACvD,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;YACzC,qBAAqB,EAAE,MAAM,CAAC,qBAAqB;YACnD,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,cAAc,EAAE,KAAK,CAAC,qBAAqB;YAC3C,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,KAAK,EAAE,KAAK,CAAC,KAAK;SACnB;KACF,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"assess_hackability.js","sourceRoot":"","sources":["../../src/tools/assess_hackability.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,UAAU,EAAoB,MAAM,aAAa,CAAC;AAE3D,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;CACxC,CAAC,CAAC;AAwBH,qEAAqE;AACrE,SAAS,IAAI,CAAC,CAAS;IACrB,OAAO,CAAC,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAkB,EAClB,OAAsB;IAEtB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAEvC,8DAA8D;IAC9D,IAAI,KAAK,GAA4B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,WAAW,CAAC,CAAC;IACrF,IAAI,CAAC,KAAK;QAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC;IACjE,IAAI,CAAC,KAAK;QAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAExG,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,IAAI,KAAK,CAAC,WAAW,4JAA4J;SAC3L,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAEhC,OAAO;QACL,KAAK,EAAE,IAAI;QACX,MAAM,EAAE;YACN,EAAE,EAAE,KAAK,CAAC,EAAE;YACZ,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,QAAQ,EAAE,IAAI,EAAE,uCAAuC;YACvD,eAAe,EAAE,KAAK,CAAC,eAAe;YACtC,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;YACzC,qBAAqB,EAAE,MAAM,CAAC,qBAAqB;YACnD,cAAc,EAAE,KAAK,CAAC,cAAc;YACpC,cAAc,EAAE,KAAK,CAAC,qBAAqB;YAC3C,aAAa,EAAE,KAAK,CAAC,aAAa;YAClC,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,KAAK;SACN;KACF,CAAC;AACJ,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import type { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import type { DeviceEntry } from "../catalog/schema.js";
4
+ import { type DeviceLinks } from "../links.js";
4
5
  export declare const proposeHardwareInput: z.ZodObject<{
5
6
  idea: z.ZodString;
6
7
  budget_usd: z.ZodOptional<z.ZodNumber>;
@@ -26,8 +27,9 @@ export interface Proposal {
26
27
  brick_risk_disclaimer: string | null;
27
28
  firmware_links: string[];
28
29
  community_size: string;
29
- ebay_query_suggestion: string;
30
30
  notes: string;
31
+ links: DeviceLinks;
32
+ ebay_query_suggestion: string;
31
33
  }
32
34
  export interface ProposeOutput {
33
35
  proposals: Proposal[];
@@ -35,4 +37,5 @@ export interface ProposeOutput {
35
37
  degraded: boolean;
36
38
  message?: string;
37
39
  }
40
+ export declare function shortlistCandidates(catalog: DeviceEntry[], idea: string): DeviceEntry[];
38
41
  export declare function proposeHardware(input: ProposeInput, catalog: DeviceEntry[], server: Server): Promise<ProposeOutput>;
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { applyBrickRiskSafety } from "../safety.js";
3
3
  import { sampleJson } from "../sampling.js";
4
+ import { buildLinks } from "../links.js";
4
5
  export const proposeHardwareInput = z.object({
5
6
  idea: z.string().min(3, "idea is too short").max(2000, "idea is too long"),
6
7
  budget_usd: z.number().positive().max(100000).optional(),
@@ -20,26 +21,57 @@ Rules:
20
21
  - Pick 3-5 devices. Never more than 5.
21
22
  - Only pick from the candidate list provided. Do not invent devices.
22
23
  - "why_this_fits" must mention the user's idea explicitly. No generic praise.
23
- - If candidates are weak for the idea, say so in the rationale rather than padding with bad picks.`;
24
- function buildEbayQuery(device, idea) {
25
- // Suggest an eBay search query the user can pass to ebay-mcp at the host.
26
- // Avoid LLM model names; use the device's marketing name.
27
- const cond = device.brick_risk >= 4 ? "working" : "used";
28
- return `${device.name} ${cond}`;
24
+ - Respect explicit constraints in the idea: if the user says "e-paper preferred", prioritize devices with the "e-ink" tag. If the user says "low power", prioritize "low-power" tagged devices.
25
+ - If candidates are weak for the idea, say so in the rationale rather than padding with bad picks.
26
+ - Display HATs (Waveshare, Pimoroni Inky) require a host SBC; if you propose one, also propose a Pi Zero W (raspberry-pi-zero-2w) as the driver.`;
27
+ // Token-aliasing: user-text terms → catalog tag terms.
28
+ // Lets "e-paper" match "e-ink", "tablet" match "display", etc.
29
+ const TAG_ALIASES = {
30
+ "e-paper": ["e-ink"],
31
+ "epaper": ["e-ink"],
32
+ "e ink": ["e-ink"],
33
+ "eink": ["e-ink"],
34
+ "tablet": ["display", "kiosk"],
35
+ "smart bulb": ["bulb", "color-cycle"],
36
+ "lamp": ["bulb"],
37
+ "dashboard": ["kiosk", "display"],
38
+ "calendar": ["display", "kiosk", "framed-art"],
39
+ "art frame": ["framed-art", "ambient", "display"],
40
+ "always-on": ["low-power"],
41
+ "battery": ["low-power"],
42
+ "speaker": ["audio-out"],
43
+ "music": ["audio-out"],
44
+ "mic": ["voice"],
45
+ "microphone": ["voice"],
46
+ };
47
+ function expandIdeaTerms(idea) {
48
+ const lower = idea.toLowerCase();
49
+ const tags = new Set();
50
+ for (const [phrase, mapped] of Object.entries(TAG_ALIASES)) {
51
+ if (lower.includes(phrase)) {
52
+ for (const t of mapped)
53
+ tags.add(t);
54
+ }
55
+ }
56
+ return tags;
29
57
  }
30
58
  function candidateContext(catalog) {
31
59
  return catalog
32
60
  .map((d) => `- id: ${d.id} | name: ${d.name} | category: ${d.category} | tags: ${d.idea_fit_tags.join(", ")} | notes: ${d.notes}`)
33
61
  .join("\n");
34
62
  }
35
- function shortlistCandidates(catalog, idea) {
36
- // Naive prefilter: if the idea text mentions any tag substring, prefer those.
37
- // Otherwise return the full catalog (LLM does the real picking).
63
+ // Always pass the full catalog when small (<100 devices). The LLM is the
64
+ // real selector; pre-filtering hurts more than it helps once the catalog
65
+ // fits in context. Shortlist is only used for the degraded fallback.
66
+ export function shortlistCandidates(catalog, idea) {
38
67
  const lower = idea.toLowerCase();
39
- const tagged = catalog.filter((d) => d.idea_fit_tags.some((t) => lower.includes(t.replace("-", " "))));
40
- if (tagged.length >= 5)
41
- return tagged;
42
- return catalog;
68
+ const aliasedTags = expandIdeaTerms(idea);
69
+ const tagged = catalog.filter((d) => {
70
+ return d.idea_fit_tags.some((t) => lower.includes(t.replace("-", " ")) || aliasedTags.has(t));
71
+ });
72
+ if (tagged.length === 0)
73
+ return catalog;
74
+ return tagged;
43
75
  }
44
76
  export async function proposeHardware(input, catalog, server) {
45
77
  if (catalog.length === 0) {
@@ -49,14 +81,16 @@ export async function proposeHardware(input, catalog, server) {
49
81
  degraded: true,
50
82
  };
51
83
  }
52
- const candidates = shortlistCandidates(catalog, input.idea);
84
+ // Pass the FULL catalog to sampling when small; lets the LLM weigh all
85
+ // options. Shortlist only as the degraded-mode fallback.
86
+ const fullCatalog = catalog.length <= 100 ? catalog : shortlistCandidates(catalog, input.idea);
53
87
  const userPrompt = [
54
88
  `Project idea: ${input.idea}`,
55
89
  input.budget_usd ? `Budget (USD): ${input.budget_usd}` : null,
56
90
  input.constraints ? `Constraints: ${input.constraints}` : null,
57
91
  "",
58
92
  "Candidate hardware:",
59
- candidateContext(candidates),
93
+ candidateContext(fullCatalog),
60
94
  ]
61
95
  .filter(Boolean)
62
96
  .join("\n");
@@ -66,13 +100,15 @@ export async function proposeHardware(input, catalog, server) {
66
100
  userPrompt,
67
101
  maxTokens: 1500,
68
102
  });
69
- // Degraded path: sampling failed twice. Return raw shortlist matches with a
70
- // clear "reasoning unavailable" note. No hallucination, no crash.
103
+ // Degraded path: every reasoning attempt failed (host sampling + retry +
104
+ // optional Anthropic API fallback). Use the alias-aware shortlist as the
105
+ // best available approximation. Honest about the degradation.
71
106
  if (!sampled || !Array.isArray(sampled.picks)) {
72
- const fallback = candidates.slice(0, 3).map((d) => buildProposal(d, input.idea, "Reasoning unavailable; raw catalog match."));
107
+ const shortlisted = shortlistCandidates(catalog, input.idea);
108
+ const fallback = shortlisted.slice(0, 5).map((d) => buildProposal(d, "Reasoning unavailable; matched against catalog tags only."));
73
109
  return {
74
110
  proposals: fallback,
75
- reasoning: "Reasoning unavailable. The host LLM failed twice. Returning raw catalog matches; verify hackability against the linked communities.",
111
+ reasoning: "Reasoning unavailable. The host LLM failed and no Anthropic API fallback was configured. Returning catalog matches by tag overlap. Set ANTHROPIC_API_KEY in the MCP server's env config to enable reasoning even on hosts without sampling/createMessage support.",
76
112
  degraded: true,
77
113
  };
78
114
  }
@@ -81,7 +117,7 @@ export async function proposeHardware(input, catalog, server) {
81
117
  const device = catalog.find((d) => d.id === pick.id);
82
118
  if (!device)
83
119
  continue; // sampler hallucinated an id; drop it
84
- proposals.push(buildProposal(device, input.idea, pick.why_this_fits));
120
+ proposals.push(buildProposal(device, pick.why_this_fits));
85
121
  }
86
122
  if (proposals.length === 0) {
87
123
  return {
@@ -97,8 +133,9 @@ export async function proposeHardware(input, catalog, server) {
97
133
  degraded: false,
98
134
  };
99
135
  }
100
- function buildProposal(device, idea, whyThisFits) {
136
+ function buildProposal(device, whyThisFits) {
101
137
  const safety = applyBrickRiskSafety(device);
138
+ const links = buildLinks(device);
102
139
  return {
103
140
  id: device.id,
104
141
  name: device.name,
@@ -110,8 +147,11 @@ function buildProposal(device, idea, whyThisFits) {
110
147
  brick_risk_disclaimer: safety.brick_risk_disclaimer,
111
148
  firmware_links: device.firmware_links,
112
149
  community_size: device.community_size_bucket,
113
- ebay_query_suggestion: buildEbayQuery(device, idea),
114
150
  notes: device.notes,
151
+ links,
152
+ ebay_query_suggestion: links.ebay_search_url.includes("_nkw=")
153
+ ? decodeURIComponent(links.ebay_search_url.split("_nkw=")[1]?.split("&")[0] ?? "")
154
+ : device.name,
115
155
  };
116
156
  }
117
157
  //# sourceMappingURL=propose_hardware.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"propose_hardware.js","sourceRoot":"","sources":["../../src/tools/propose_hardware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"propose_hardware.js","sourceRoot":"","sources":["../../src/tools/propose_hardware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ {
2
+ "idea": "always-on family calendar in the kitchen, light colors, no animation, e-paper preferred, ideally 7-13 inch with low refresh cadence so it can sit in a frame",
3
+ "expected_devices": [
4
+ "waveshare-7-5-epaper-hat",
5
+ "pimoroni-inky-impression-7-3",
6
+ "remarkable-2",
7
+ "barnes-noble-nook-touch",
8
+ "kobo-clara-hd"
9
+ ],
10
+ "notes": "Regression for the gap Cowork called out (2026-05-04 dogfood session): an e-paper kitchen-calendar idea must surface real e-paper hardware, not just LCD frames. Includes the canonical maker-community devices (Waveshare HAT for MagInkCal-style builds, Pimoroni Inky Impression for color e-ink, reMarkable 2 for the polished tablet route, plus jailbroken e-readers). At least 3 of these 5 should appear; if only 0-2 do, prompt or catalog needs work."
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hackshop-mcp",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Hardware-literate AI scout for tinkerers. Idea-to-hardware mapping via MCP.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -46,6 +46,7 @@
46
46
  "url": "https://github.com/msanchezgrice/hackshop-mcp/issues"
47
47
  },
48
48
  "dependencies": {
49
+ "@anthropic-ai/sdk": "^0.93.0",
49
50
  "@modelcontextprotocol/sdk": "^1.0.0",
50
51
  "zod": "^3.23.0"
51
52
  },