PriceLens
Real-Time Multi-Source Price Intelligence for AI Assistants, the Web, and Developers
Track: AWS Databases × Vercel v0 Hackathon Database: Amazon DynamoDB Deployment Target: Vercel Interface Protocol: Model Context Protocol (MCP) Repository: github.com/LSUDOKO/PriceLens
Executive Summary
PriceLens is a multi-product price intelligence platform that aggregates pricing data from 14 sources across five categories — electronics, flights, hotels, events, and financial instruments — and exposes that data through three synchronized interfaces: a Model Context Protocol (MCP) server for AI assistants, a Next.js web dashboard for human users, and a REST API for programmatic integration.
The system is built around a single architectural decision that we believe is its strongest engineering contribution: a database adapter pattern that transparently routes every read and write to either Amazon DynamoDB (production) or an in-memory store (development), without any conditional logic in the application code that calls it. This means the project runs instantly with zero AWS setup for evaluation, and upgrades to a fully persistent, serverless, pay-per-request DynamoDB backend the moment AWS credentials are supplied — with no code changes.
PriceLens was built specifically to address a problem we believe is underserved by the current generation of AI tools: AI assistants are increasingly the first place people go to ask about prices, but they have no reliable, real-time source of pricing truth, and will either hallucinate a number or report stale training data. PriceLens gives any MCP-compatible client — Claude Desktop, Cursor, Cline, or future tools — a grounded, queryable source of current pricing data through nine purpose-built tools.
1. Inspiration
The idea for PriceLens came from a problem that is both universal and, in Southeast Asia specifically, unusually costly: identical products are priced inconsistently across competing platforms, and finding the genuinely best price requires manually checking five to ten different sites.
We catalogued this pattern across every category we eventually built support for:
| Product | Source A | Source B | Spread |
|---|---|---|---|
| MacBook Pro M4 | Amazon SG — SGD 2,799 | Lazada SG — SGD 2,949 | ~5.4% |
| Flight, Singapore → London | Google Flights — SGD 980 | Singapore Airlines (direct) — SGD 1,250 | ~27.6% |
| Marina Bay Sands (per night) | Agoda — SGD 520 | Booking.com — SGD 550 | ~5.8% |
| Coldplay SG Concert | Ticketmaster — SGD 168 | Resale platform — SGD 198 | ~17.9% |
In a region where cross-border e-commerce is the norm and multiple platforms compete aggressively for the same customer, we found that choosing the right source over the wrong one can realistically save 20–40% on a single purchase — savings that are invisible to a buyer who only checks one or two sites.
At the same time, we observed a second, complementary trend: conversational AI is becoming a primary research interface for purchases. People already ask Claude, ChatGPT, and similar tools "where should I buy X" or "what's a fair price for Y" — but these models have no live connection to pricing data. Without grounding, they either decline to answer, hallucinate a number, or repeat outdated figures from their training data.
PriceLens was conceived to close that gap directly, by combining:
- A structured, queryable price-comparison engine backed by Amazon DynamoDB.
- A Model Context Protocol (MCP) server that gives any compatible AI assistant real-time, tool-based access to current pricing data.
- A Next.js web dashboard, deployable on Vercel, for visual, human-driven exploration of the same data.
- A database adapter layer that lets the entire system run with zero infrastructure for development and demos, then upgrade seamlessly to production-grade persistence.
2. What PriceLens Does
PriceLens is a universal multi-product price comparison platform. It aggregates pricing data from 14 sources across 5 source types (merchant, airline, booking, exchange, and financial) and exposes that data through three interfaces built on the same backend.
2.1 MCP Server — The Primary Interface
Any MCP-compatible client — Claude Desktop, Cursor, Cline, or any future MCP client — can connect to PriceLens and query pricing data conversationally through nine specialized tools:
| Tool | Function | Example Query |
|---|---|---|
search_prices |
Search products by keyword or category | "Find me MacBook Pro prices" |
compare_sources |
Full side-by-side price comparison across all merchants for a product | "Compare Sony headphones across all sources" |
find_best_price |
Identify the single cheapest source for a product | "Where should I buy an iPhone 16 Pro?" |
list_categories |
Enumerate all tracked product categories | "What categories do you track?" |
list_products_by_category |
List products with full specs for a category | "Show me all electronics" |
set_price_alert |
Create a price-drop alert with a target threshold | "Alert me when flights to Tokyo drop below SGD 500" |
list_alerts |
View all active alerts for the current user | "Show my active alerts" |
delete_alert |
Remove an existing alert | "Remove my alert for Marina Bay Sands" |
get_source_overview |
Aggregate pricing summary for a single source | "How does Shopee pricing compare?" |
Each tool is registered with a full JSON Schema for input validation, and the server additionally exposes three MCP resources (sources, categories, products) for clients that prefer direct resource reads over tool invocation.
2.2 Web Dashboard — The Visual Interface
A fully responsive Next.js application with three pages, all consuming the same underlying data as the MCP server:
- Price Explorer (
/) — Search, filter, and sort the full product catalogue; view price ranges, computed savings percentages, and the current cheapest source per product, with expandable per-source detail. - Source Comparison (
/heatmap) — A color-coded visual overview of all 14 sources by average price, tagged and grouped by source type (merchant, airline, booking, exchange). - Price Alerts (
/alerts) — Full create/read/update/delete management of alerts, with product autocomplete, target-price input, and per-source targeting (or "any source").
2.3 REST API — The Programmatic Interface
Four endpoints, built on the identical database adapter used by the MCP server, so that a request through any of the three interfaces stays consistent with the others:
| Endpoint | Method | Purpose |
|---|---|---|
/api/prices |
GET |
Query prices by SKU, source, or category |
/api/alerts |
GET / POST / DELETE |
List, create, and remove price alerts |
/api/seed |
POST |
Seed DynamoDB or the in-memory store with catalogue and pricing data |
/api/health |
GET |
Health check reporting active store type and current record counts |
2.4 Key Capabilities
- Cross-source price comparison — instantly surface which source has the best price for any tracked product.
- Smart deal-finding — return the single cheapest source along with a computed savings percentage versus the average.
- Automated price alerts — be notified, via the interface of your choice, when a price reaches a target threshold.
- Zero-setup development — the full application runs immediately against a pre-seeded in-memory store; no AWS account or configuration is required to evaluate it.
- Database adapter pattern — identical application code serves both the in-memory development store and the production DynamoDB backend.
3. How We Built It
3.1 Architecture Overview
┌───────────────────────────────────────────────────────────┐
│ Claude Desktop │
│ (or any MCP-compatible client) │
└──────────────────────────────┬──────────────────────────────┘
│ stdio · JSON-RPC 2.0
▼
┌───────────────────────────────────────────────────────────┐
│ PriceLens MCP Server │
│ 9 Tools + 3 Resources │
│ (@modelcontextprotocol/sdk v1.29) │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────┐
│ db-adapter.ts │
│ Auto-detects AWS credentials → routes to the correct │
│ backend with zero call-site branching │
└───────────┬───────────────────────────────────┬─────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────────┐
│ In-Memory Store │ │ Amazon DynamoDB │
│ (development / │ │ (production, PAY_PER_ │
│ instant evaluation) │ │ REQUEST, 3 tables, GSIs) │
└─────────────────────┘ └─────────────────────────┘
▲ ▲
└───────────────────┬───────────────┘
▼
┌─────────────────────────┐
│ Next.js API Routes │
│ (Vercel) — same adapter │
│ serves the web dashboard │
└─────────────────────────┘
The defining property of this architecture is that the MCP server, the REST API, and the web dashboard never talk to DynamoDB or the in-memory store directly — every one of them calls into db-adapter.ts, which is the only component in the system aware of which backend is actually active.
3.2 Why Amazon DynamoDB
DynamoDB was selected deliberately, not by default, against the specific requirements of this problem:
| Requirement | Why DynamoDB Fits |
|---|---|
| Heterogeneous product schemas | A laptop has cpu/ram/storage; a flight has departure/arrival/duration; a hotel has stars/amenities. DynamoDB's schemaless item model absorbs this variation natively, with no per-category migration. |
| Fast single-key lookups | A GetItem by sku returns a product record in low single-digit milliseconds, which matters for an interactive MCP tool call. |
| Category browsing via GSI | The category-index Global Secondary Index supports "all products in category X" without a table scan. |
| Source filtering via GSI | The source-index Global Secondary Index supports "all prices from source Y" — the access pattern behind get_source_overview. |
| Serverless, usage-based billing | PAY_PER_REQUEST billing means the table costs nothing while idle, which matters for a project under hackathon judging load and beyond. |
| TTL support | Built-in time-to-live lets stale price observations expire automatically without a cleanup job. |
| Global Tables readiness | The same table design extends cleanly to a future multi-region deployment without restructuring. |
3.3 Data Model
PriceLens uses three purpose-built DynamoDB tables, designed around the platform's actual access patterns rather than forced into a single generic table:
Products (Partition Key: sku)
├── name, category, tags[], baseSpecs{}
├── GSI: category-index (Partition Key: category)
└── 14 products across 5 categories
Prices (Partition Key: sku · Sort Key: source)
├── pricePerUnit, unit, currency, timestamp, sourceType
├── GSI: source-index (Partition Key: source)
└── 60+ price records across 14 sources
Alerts (Partition Key: userId · Sort Key: alertId)
├── sku, targetSource, targetPrice, createdAt
└── Per-user price-drop notification records
Products1—NPrices: every product carries many price observations, retrievable in a single query via theskupartition key.Products1—NAlerts: every product can be monitored by many alerts, joined at the application layer by matchingsku— DynamoDB itself enforces no foreign key, by design.
3.4 Technology Stack
Next.js 16.2 (Frontend)
- React Server Components for fast initial paint
- App Router with file-based routing (3 pages, 4 API routes)
- Tailwind CSS 4 with full dark-mode support
- Lucide React for a consistent icon set
- Mobile-first responsive layout with collapsible navigation
Model Context Protocol
@modelcontextprotocol/sdkv1.29 for full MCP compliance- stdio transport for Claude Desktop and IDE-based MCP clients
- 9 tools, each with strict JSON Schema input validation
- 3 URI-addressable resources (sources, categories, products)
- Typed
McpErrorcodes with graceful fallback behaviour on invalid input
Database Adapter Pattern (the architectural centerpiece)
db-adapter.ts performs the following sequence once, lazily, on first use:
- Check for
AWS_ACCESS_KEY_IDandAWS_SECRET_ACCESS_KEYin the environment. - If present, attempt a lightweight DynamoDB connectivity check (a
ListTablescall). - If DynamoDB responds, route all subsequent reads and writes there.
- If credentials are absent, or DynamoDB is unreachable, fall back to the in-memory store automatically, with a clear console warning — no thrown error, no broken request.
- Every consumer — MCP tool handler, API route, or resource reader — calls the same
getProduct(),getPrices(), andputAlert()functions regardless of which backend answered.
The practical effect: a judge or reviewer can clone the repository and run it immediately with no AWS account, while a production deployment becomes persistent simply by adding two environment variables — with not a single if (useDynamoDB) branch anywhere in the calling code.
3.5 Project Structure
pricelens/
├── src/
│ ├── lib/
│ │ ├── types.ts Shared types — 14 sources, 5 categories
│ │ ├── dynamodb.ts DynamoDB client, table/GSI definitions, seed data
│ │ ├── memory-store.ts In-memory fallback store (zero setup)
│ │ ├── db-adapter.ts Auto-detecting adapter (DynamoDB ⇄ in-memory)
│ │ └── mcp-server.ts 9 MCP tools + 3 resources
│ ├── mcp-run.ts Standalone MCP entry point for Claude Desktop
│ └── app/
│ ├── page.tsx Price Explorer dashboard
│ ├── heatmap/page.tsx Source Comparison visualization
│ ├── alerts/page.tsx Alert management with autocomplete
│ └── api/ REST routes: prices, alerts, seed, health
├── scripts/
│ └── provision-dynamodb.sh AWS table provisioning script
├── infra/
│ └── dynamodb-template.yaml CloudFormation template for the data layer
├── public/
│ └── architecture.html Interactive architecture diagram
├── vercel.json Vercel deployment configuration
└── README.md Setup, architecture, and demo documentation
3.6 Seed Dataset
14 products across 4 consumer-facing categories (plus a financial source type used for exchange-rate data):
| Category | Products |
|---|---|
| Electronics | MacBook Pro M4, ThinkPad X1 Gen 12, iPhone 16 Pro, Galaxy S25 Ultra, Sony WH-1000XM6 |
| Flights | Singapore → London (SIN–LHR), Singapore → Tokyo (SIN–NRT), Singapore → Bangkok (SIN–BKK), all round-trip |
| Hotels | Marina Bay Sands, Capella Singapore, Hôtel Ritz Paris |
| Events | Coldplay Singapore Concert, Singapore Grand Prix, Universal Studios Singapore |
60+ price records across 14 sources:
| Source | Type | Products Tracked |
|---|---|---|
| Shopee SG | merchant | 5 electronics |
| Lazada SG | merchant | 5 electronics |
| Amazon SG | merchant | 5 electronics |
| Amazon US | merchant | 5 electronics |
| Best Buy US | merchant | 5 electronics |
| Google Flights | airline | 3 flights |
| Singapore Airlines | airline | 3 flights |
| Emirates | airline | 1 flight |
| Booking.com | booking | 3 hotels |
| Agoda | booking | 3 hotels |
| Expedia | booking | 3 hotels |
| Ticketmaster | exchange | 3 events |
| Viator | exchange | 3 events |
| Google Finance | financial | exchange-rate reference data |
4. Challenges We Ran Into
4.1 Data Model Design: Generic vs. Category-Specific Attributes
The hardest early architectural decision concerned how to model fundamentally different product types in a single table. A laptop's meaningful attributes (cpu, ram, storage, screen) share almost nothing with a flight's (departure, arrival, duration, cabin) or a hotel's (stars, pool, rooms, restaurant).
Resolution: We used DynamoDB's native Map type for a baseSpecs attribute — a flexible key-value structure typed as Record<string, string> in the application layer. This lets each category define its own attribute set with no schema migration and no proliferation of nullable columns.
4.2 The N+1 Query Problem in get_source_overview
Our source-overview tool (originally get_region_overview) initially looked up the parent product for every price record individually, producing N sequential DynamoDB reads for N price records belonging to a single source.
Resolution: We introduced a per-request product cache inside the handler that stores each product after its first fetch, collapsing the worst case from N lookups down to at most 14 (one per unique SKU in the catalogue). We also switched to shallow-cloning price records ({ ...p, category: cat }) when enriching them, rather than mutating the stored objects directly — a small but important data-integrity fix that prevents the in-memory store from silently corrupting on repeated reads.
4.3 A Seamless DynamoDB-or-Memory Fallback Is Harder Than It Sounds
The adapter pattern was simple to describe but required care to implement correctly:
- Cold-start detection had to happen on first request, not at module import time, to avoid forcing a network call during build or static analysis.
- Lazy module loading — the DynamoDB client should only be constructed if it will actually be used, so the in-memory path stays dependency-free.
- Graceful degradation — if DynamoDB credentials are present but the service is unreachable, the system must fall back to memory with a visible warning, never a hard failure for the end user.
Resolution: A lazy singleton pattern where the resolved backend (useDynamoDB) starts as null, is resolved once on the first call, and is cached for the lifetime of the process. The exported getDB() function hides this entirely from every caller.
4.4 A Mid-Build Pivot and Rename
The project began as "PricePulse," scoped narrowly around AWS cloud-service pricing. Partway through the build, we recognised that the more compelling, demonstrable use case was general multi-product retail and travel price comparison — and pivoted accordingly. That meant renaming the project across over 100 references in 34 files. TypeScript's strict type checking caught the majority of missed references automatically during the rename, which we consider a strong argument for full strict-mode TypeScript on any project expected to evolve mid-build.
4.5 MCP stdio Protocol Discipline
The MCP stdio transport reserves stdout exclusively for JSON-RPC protocol messages; anything else written to stdout — including a stray console.log() — corrupts the protocol stream and breaks the connected client. We audited every module in the MCP server to ensure all diagnostic and debug output goes to console.error() (stderr) instead, and added this constraint explicitly to our internal contribution guidelines for the rest of the build.
5. Accomplishments We're Proud Of
Zero-setup evaluation. The application works immediately on npm install && npm run dev — no AWS account, no API keys, no manual table provisioning. A judge gets a fully functional platform with 14 products and 60+ live price points within seconds, powered by the in-memory store pre-loaded with realistic seed data.
Three interfaces, one backend, zero drift. The MCP server, the web dashboard, and the REST API all read and write through the identical db-adapter layer. A price alert created by typing into Claude Desktop appears instantly in the web dashboard's alerts page — there is no synchronization step, because there was never a second source of truth to synchronize.
Engineering rigor beyond hackathon scope. Despite the time constraints, PriceLens includes: full TypeScript in strict mode; typed error responses with consistent error handling across all entry points; input validation on every API endpoint (including rejection of negative or malformed prices); the database adapter pattern described above; a CloudFormation template for reproducible infrastructure; a Vercel deployment configuration with correct CORS headers; and a comprehensive README covering quick start, architecture, and a demo script.
Realistic, defensible demo data. Rather than placeholder figures, our 14 seed products carry pricing grounded in real public listings — a 2025 MacBook Pro M4 from Apple's own lineup, current Singapore Airlines route pricing, and published Marina Bay Sands room rates. The resulting savings percentages (18–35% across our test cases) are realistic rather than engineered to look impressive.
MCP-first by design, not as an add-on. By building the Model Context Protocol server as PriceLens's primary interface rather than a secondary integration, the project is natively compatible with the fast-growing ecosystem of MCP clients — Claude Desktop, Cursor, Cline today, and any future compliant client without modification.
A genuinely polished UI. The web dashboard ships with dark mode, smooth transitions, a fully responsive mobile layout with collapsible navigation, expandable product cards, autocomplete search, animated status indicators, and category-specific iconography with color-coded source types.
6. What We Learned
Technical
- Multi-table beat single-table design for our access patterns. We chose three separate tables over a single unified table because our three core access patterns — products by category, prices by SKU/source, alerts by user — are structurally distinct enough that forcing them into one table would have added overlay-key complexity without a corresponding benefit at our data volume. At significantly higher scale, a single-table redesign would likely reduce cost and improve some cross-entity queries.
- GSIs were the right call over LSIs for our query shape. Both
category-indexandsource-indexneed to support arbitrary, unpredictable query values (any category, any source) — exactly what a GSI is for. A Local Secondary Index onpricePerUnitwithin a product's own price records is a clear next step, and would let us support "cheapest to most expensive for this product" as a sorted query instead of a client-side sort. - The MCP ecosystem is still young and moving. We hit breaking changes between minor versions of
@modelcontextprotocol/sdkduring development and ultimately pinned our dependency version for stability. The stdio transport is mature and well-documented; the HTTP/SSE transport is still evolving, which shaped our decision to launch on stdio first. - Next.js 16 with React 19 is genuinely fast in practice. The App Router's file-based API routes (
route.ts) are elegant for a project of this size, though we noted they currently lack first-class middleware support for cross-cutting concerns like rate limiting — something we'd add at the infrastructure layer for a production deployment. - TypeScript generics made the adapter pattern type-safe, not just convenient. Typing the in-memory store and the DynamoDB client against a single shared interface meant a mismatch between the two implementations was a compile-time error, not a runtime surprise discovered during a demo.
Process
- Finalize the data model before the business logic. Building the UI and MCP server ahead of a settled schema is what made our PricePulse-to-PriceLens pivot more painful than it needed to be. We'd sequence this differently next time.
- Hackathon scope needs an explicit ceiling. Our original ambition — "compare the price of anything" — was unbounded and untestable. Narrowing deliberately to four product categories and fourteen specific products let us demonstrate the platform's full range credibly rather than its breadth shallowly.
- Writing the documentation surfaced design gaps before judges could. Drafting the README, the architecture diagram, and this submission forced us to articulate decisions precisely enough to notice a handful of inconsistencies we fixed before submission, rather than during Q&A.
7. What's Next for PriceLens
Short-Term (Next 30 Days)
- Production deployment to Vercel with persistent DynamoDB storage, enabled by setting:
bash AWS_ACCESS_KEY_ID=<your-key> AWS_SECRET_ACCESS_KEY=<your-secret> AWS_REGION=us-east-1 - Live data integration, replacing seed data with real feeds:
- A retail pricing API for live Shopee, Lazada, and Amazon prices on electronics.
- A travel-data API (e.g. SerpAPI) for live Google Flights, hotel, and event pricing.
- A flight-specific API (e.g. Duffel) for richer flight search and eventual booking.
- A DynamoDB Streams–based ingestion pipeline to keep prices current without manual re-seeding.
- User authentication via NextAuth.js or Clerk, replacing the current single
default-usermodel with per-user alerts, personalized watchlists, and basic multi-user/team support.
Medium-Term (Next 3 Months)
- Price history and trend analysis, using DynamoDB TTL to manage retention while powering historical price charts (Recharts is already integrated), "best time to buy" heuristics, and webhook/email price-drop notifications.
- Mobile clients (React Native, or native Swift/Kotlin) connecting to the same MCP server once an HTTP/SSE transport is production-ready in the SDK.
- A merchant-facing dashboard, allowing sellers to submit their own pricing data, view competitive intelligence against tracked sources, and receive alerts when a competitor changes price.
Long-Term (6+ Months)
- AI-assisted price prediction, using the accumulated pricing history to model demand-driven patterns ("this product typically drops during this sale window") and surface cross-category bundling opportunities.
- Global, multi-region expansion using DynamoDB Global Tables, with local currency support and region-specific merchant integrations across the US, EU, APAC, and LATAM.
- A B2B pricing-intelligence API, monetized as a data product for e-commerce analytics platforms, travel-aggregator partners, and price-intelligence SaaS — the natural extension of this hackathon track into a sustainable product.
8. Submission Checklist
| Requirement | Status | Notes |
|---|---|---|
| Text description | Complete | This document and the project README |
| AWS database used | Complete | Amazon DynamoDB — 3 tables, PAY_PER_REQUEST, 2 GSIs |
| Architecture diagram | Complete | /public/architecture.html |
| Vercel project link | Pending | vercel deploy from project root |
| DynamoDB screenshot | Pending | Pending table provisioning in the AWS Console |
| Demo video (under 3 minutes) | Pending | Script outlined in README.md |
| Blog post (bonus) | Pending | Planned for dev.to / Medium / LinkedIn |
| GitHub repository | Complete | github.com/LSUDOKO/PriceLens — clean history, no unrelated files |
Acknowledgments
Built for the H0 Hackathon (AWS Databases × Vercel v0 track).
- Amazon DynamoDB — persistent storage and querying for the production data layer
- Next.js 16.2 and Vercel — frontend framework and deployment platform
- @modelcontextprotocol/sdk — Model Context Protocol compliance for the MCP server
All product and pricing data in this submission is used for demonstration purposes. Actual market prices may vary and should be independently verified before purchase.


Log in or sign up for Devpost to join the conversation.