From bd5961fc5449d98631ad1ba6aac64a2d0217a1de Mon Sep 17 00:00:00 2001 From: gouchan Date: Mon, 23 Mar 2026 22:09:25 -0700 Subject: [PATCH] feat: add local wishlist system with 6 new MCP tools Adds a local JSON-backed wishlist layer on top of the existing cart tools. New file: src/wishlist.ts - createWishlist, addToWishlist, getWishlist, listWishlists, removeFromWishlist, deleteWishlist - Auto-creates wishlist on add if it doesn't exist - ASIN deduplication per wishlist - Estimated total calculation per wishlist - Persists to wishlists.json (gitignored) Updated: src/index.ts - Registers 6 new MCP tools wrapping the wishlist module Updated: .gitignore - Adds wishlists.json --- .gitignore | 1 + README.md | 15 ++++ src/index.ts | 113 +++++++++++++++++++++++++++ src/wishlist.ts | 199 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 328 insertions(+) create mode 100644 src/wishlist.ts diff --git a/.gitignore b/.gitignore index ede657f..f6446bf 100644 --- a/.gitignore +++ b/.gitignore @@ -215,6 +215,7 @@ build/ dist/ amazonCookies.json +wishlists.json mocks/*_202*.html mocks/*_202*.txt !mcp-server-amazon.log \ No newline at end of file diff --git a/README.md b/README.md index 0479145..eaf2cc8 100644 --- a/README.md +++ b/README.md @@ -68,3 +68,18 @@ See `~/Library/Logs/Claude/mcp-server-amazon.log` ## License [The MIT license](./LICENSE) + +## Wishlist Feature (fork) + +This fork adds a local wishlist system via 6 new MCP tools: + +| Tool | Description | +|---|---| +| `create-wishlist` | Create a new named wishlist | +| `add-to-wishlist` | Add a product by ASIN (auto-creates wishlist if needed) | +| `get-wishlist` | View all items in a wishlist | +| `list-wishlists` | List all wishlists with item counts and estimated totals | +| `remove-from-wishlist` | Remove an item by ASIN | +| `delete-wishlist` | Delete an entire wishlist | + +Wishlists are stored locally in `wishlists.json` (gitignored). No Amazon API required — pure local JSON persistence. diff --git a/src/index.ts b/src/index.ts index 2f8bad7..b82b2a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import { z } from 'zod' import { getOrdersHistory } from './orders.js' import { getCartContent, addToCart, clearCart } from './cart.js' import { getProductDetails, searchProducts } from './products.js' +import { createWishlist, addToWishlist, getWishlist, listWishlists, removeFromWishlist, deleteWishlist } from './wishlist.js' // Create server instance const server = new McpServer({ @@ -268,6 +269,118 @@ server.tool( } ) +// ################################## +// Wishlist Tools +// ################################## + +server.tool( + 'create-wishlist', + 'Create a new local wishlist to save Amazon products for later. Wishlists are stored in wishlists.json.', + { + name: z.string().min(1).describe('Name for the wishlist, e.g. "Camping Gear", "Nintendo Switch", "Tesla Accessories"'), + description: z.string().optional().describe('Optional description for the wishlist'), + }, + async ({ name, description }) => { + try { + const result = createWishlist(name, description) + return { content: [{ type: 'text', text: result.success ? `✅ ${result.message}` : `❌ ${result.message}` }] } + } catch (error: any) { + return { content: [{ type: 'text', text: `❌ Error creating wishlist: ${error.message}` }] } + } + } +) + +server.tool( + 'add-to-wishlist', + 'Add a product to a local wishlist by ASIN. Auto-creates the wishlist if it does not exist. Use this to save cart items for later.', + { + wishlistName: z.string().min(1).describe('Name of the wishlist to add the item to'), + asin: z.string().length(10).describe('10-character Amazon ASIN of the product'), + title: z.string().min(1).describe('Product title'), + price: z.string().min(1).describe('Product price, e.g. "$19.99"'), + productUrl: z.string().describe('Amazon product URL or path'), + image: z.string().optional().describe('Product image URL'), + notes: z.string().optional().describe('Optional personal notes about this item'), + }, + async ({ wishlistName, asin, title, price, productUrl, image, notes }) => { + try { + const result = addToWishlist(wishlistName, { asin, title, price, productUrl, image, notes }) + return { content: [{ type: 'text', text: result.success ? `✅ ${result.message}` : `❌ ${result.message}` }] } + } catch (error: any) { + return { content: [{ type: 'text', text: `❌ Error adding to wishlist: ${error.message}` }] } + } + } +) + +server.tool( + 'get-wishlist', + 'Get all items in a specific wishlist, including titles, prices, ASINs, and product links.', + { + name: z.string().min(1).describe('Name of the wishlist to retrieve'), + }, + async ({ name }) => { + try { + const result = getWishlist(name) + if (!result) { + return { content: [{ type: 'text', text: `❌ Wishlist "${name}" not found. Use list-wishlists to see all wishlists.` }] } + } + return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } + } catch (error: any) { + return { content: [{ type: 'text', text: `❌ Error retrieving wishlist: ${error.message}` }] } + } + } +) + +server.tool( + 'list-wishlists', + 'List all wishlists with their names, item counts, estimated totals, and last updated timestamps.', + {}, + async () => { + try { + const result = listWishlists() + if (result.length === 0) { + return { content: [{ type: 'text', text: 'No wishlists found. Use create-wishlist or add-to-wishlist to get started.' }] } + } + return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] } + } catch (error: any) { + return { content: [{ type: 'text', text: `❌ Error listing wishlists: ${error.message}` }] } + } + } +) + +server.tool( + 'remove-from-wishlist', + 'Remove a specific product from a wishlist by ASIN.', + { + wishlistName: z.string().min(1).describe('Name of the wishlist'), + asin: z.string().length(10).describe('10-character ASIN of the product to remove'), + }, + async ({ wishlistName, asin }) => { + try { + const result = removeFromWishlist(wishlistName, asin) + return { content: [{ type: 'text', text: result.success ? `✅ ${result.message}` : `❌ ${result.message}` }] } + } catch (error: any) { + return { content: [{ type: 'text', text: `❌ Error removing from wishlist: ${error.message}` }] } + } + } +) + +server.tool( + 'delete-wishlist', + 'Permanently delete an entire wishlist and all its items.', + { + name: z.string().min(1).describe('Name of the wishlist to delete'), + }, + async ({ name }) => { + try { + const result = deleteWishlist(name) + return { content: [{ type: 'text', text: result.success ? `✅ ${result.message}` : `❌ ${result.message}` }] } + } catch (error: any) { + return { content: [{ type: 'text', text: `❌ Error deleting wishlist: ${error.message}` }] } + } + } +) + // Start the server async function main() { const transport = new StdioServerTransport() diff --git a/src/wishlist.ts b/src/wishlist.ts new file mode 100644 index 0000000..3e114c0 --- /dev/null +++ b/src/wishlist.ts @@ -0,0 +1,199 @@ +import fs from 'fs' +import path from 'path' + +const __dirname = new URL('.', import.meta.url).pathname +const WISHLISTS_FILE_PATH = path.join(__dirname, '..', 'wishlists.json') + +// ################################## +// Types +// ################################## + +export interface WishlistItem { + asin: string + title: string + price: string + productUrl: string + image?: string + notes?: string + addedAt: string +} + +export interface Wishlist { + name: string + description?: string + items: WishlistItem[] + createdAt: string + updatedAt: string +} + +interface WishlistStore { + wishlists: Record +} + +// ################################## +// Storage Helpers +// ################################## + +function wishlistKey(name: string): string { + return name.toLowerCase().replace(/\s+/g, '-') +} + +function loadWishlists(): WishlistStore { + if (fs.existsSync(WISHLISTS_FILE_PATH)) { + try { + return JSON.parse(fs.readFileSync(WISHLISTS_FILE_PATH, 'utf-8')) + } catch { + return { wishlists: {} } + } + } + return { wishlists: {} } +} + +function saveWishlists(store: WishlistStore): void { + fs.writeFileSync(WISHLISTS_FILE_PATH, JSON.stringify(store, null, 2)) +} + +// ################################## +// Create Wishlist +// ################################## + +export function createWishlist(name: string, description?: string): { success: boolean; message: string } { + const store = loadWishlists() + const key = wishlistKey(name) + + if (store.wishlists[key]) { + return { success: false, message: `Wishlist "${name}" already exists.` } + } + + store.wishlists[key] = { + name, + description, + items: [], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + } + + saveWishlists(store) + console.error(`[INFO][create-wishlist] Created wishlist "${name}"`) + return { success: true, message: `Wishlist "${name}" created successfully.` } +} + +// ################################## +// Add to Wishlist +// ################################## + +export function addToWishlist( + wishlistName: string, + item: Omit, +): { success: boolean; message: string } { + const store = loadWishlists() + const key = wishlistKey(wishlistName) + + // Auto-create wishlist if it doesn't exist + if (!store.wishlists[key]) { + store.wishlists[key] = { + name: wishlistName, + items: [], + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + } + console.error(`[INFO][add-to-wishlist] Auto-created wishlist "${wishlistName}"`) + } + + const wishlist = store.wishlists[key] + + // Prevent duplicates + if (wishlist.items.some(i => i.asin === item.asin)) { + return { success: false, message: `"${item.title}" is already in wishlist "${wishlistName}".` } + } + + wishlist.items.push({ ...item, addedAt: new Date().toISOString() }) + wishlist.updatedAt = new Date().toISOString() + + saveWishlists(store) + console.error(`[INFO][add-to-wishlist] Added ASIN ${item.asin} to wishlist "${wishlistName}"`) + return { success: true, message: `Added "${item.title}" to wishlist "${wishlistName}".` } +} + +// ################################## +// Get Wishlist +// ################################## + +export function getWishlist(name: string): Wishlist | null { + const store = loadWishlists() + const key = wishlistKey(name) + return store.wishlists[key] ?? null +} + +// ################################## +// List Wishlists +// ################################## + +export function listWishlists(): { + name: string + description?: string + itemCount: number + estimatedTotal: string + updatedAt: string +}[] { + const store = loadWishlists() + + return Object.values(store.wishlists).map(wishlist => { + const total = wishlist.items.reduce((sum, item) => { + const price = parseFloat(item.price.replace(/[$,]/g, '')) + return sum + (isNaN(price) ? 0 : price) + }, 0) + + return { + name: wishlist.name, + description: wishlist.description, + itemCount: wishlist.items.length, + estimatedTotal: `$${total.toFixed(2)}`, + updatedAt: wishlist.updatedAt, + } + }) +} + +// ################################## +// Remove from Wishlist +// ################################## + +export function removeFromWishlist(wishlistName: string, asin: string): { success: boolean; message: string } { + const store = loadWishlists() + const key = wishlistKey(wishlistName) + + if (!store.wishlists[key]) { + return { success: false, message: `Wishlist "${wishlistName}" not found.` } + } + + const wishlist = store.wishlists[key] + const before = wishlist.items.length + wishlist.items = wishlist.items.filter(i => i.asin !== asin) + + if (wishlist.items.length === before) { + return { success: false, message: `ASIN "${asin}" not found in wishlist "${wishlistName}".` } + } + + wishlist.updatedAt = new Date().toISOString() + saveWishlists(store) + console.error(`[INFO][remove-from-wishlist] Removed ASIN ${asin} from wishlist "${wishlistName}"`) + return { success: true, message: `Removed item from wishlist "${wishlistName}".` } +} + +// ################################## +// Delete Wishlist +// ################################## + +export function deleteWishlist(name: string): { success: boolean; message: string } { + const store = loadWishlists() + const key = wishlistKey(name) + + if (!store.wishlists[key]) { + return { success: false, message: `Wishlist "${name}" not found.` } + } + + delete store.wishlists[key] + saveWishlists(store) + console.error(`[INFO][delete-wishlist] Deleted wishlist "${name}"`) + return { success: true, message: `Wishlist "${name}" deleted.` } +}