Skip to content

Consumer Context API + MCP Service

Three systems (consumer-graph-worker, rover-agent, rover-mcp) independently call overlapping upstream services to enrich product, offer, user, and purchase data. Each maintains its own caches (or none), resulting in duplicated client code, inconsistent data, and wasted upstream calls. This plan defines a single Context API service (consumer-context-service) with centralized caching, plus an MCP layer on top that replaces rover-mcp entirely. The name is intentionally broad — this service will grow to include additional context domains (e.g., Fetch Play) beyond products.

Single binary, two transports: REST API + MCP. One process = one shared cache layer.

┌──────────────────────────────────────┐
│ consumer-context-service service │
│ │
upstream ◄──────│ internal/enricher/ (core logic) │
services │ internal/clients/ (adapters) │
(+ future: │ internal/cache/ (shared cache) │
Fetch Play, │ │
etc.) │ ┌──────────┐ ┌───────────┐ │
│ │ REST API │ │ MCP Server │ │
│ │ :8080 │ │ :8081 │ │
│ └────┬─────┘ └─────┬─────┘ │
└───────┼──────────────┼───────────────┘
│ │
┌─────────────┤ ┌────┴────────────────┐
│ │ │ │
┌──────▼───┐ ┌──────▼──┐ ┌────▼──────┐ ┌──────────▼──┐
│ scheduler │ │ rover- │ │ consumer- │ │ future │
│ (HTTP) │ │ agent │ │ agent │ │ agents │
└───────────┘ │ (HTTP) │ │ (MCP) │ │ (MCP) │
└─────────┘ └───────────┘ └─────────────┘
UX streaming all agents use MCP
gateway
  • REST API — programmatic/streaming consumers: scheduler, rover-agent (UX streaming gateway). Direct HTTP calls, typed responses.
  • MCP — all agents: consumer-agent, future scheduler agent, future agents. Tool-based interface for LLM reasoning.
  • Shared cache — one warm FidoIndex, one Button merchant cache, one FPS metadata cache. Every consumer benefits.
  • Cache TTLs tuned once — consumers don’t manage staleness.
  • Independent scaling — cache-heavy service scales separately.
  • Consistent data — all callers see the same enriched view.
  • Extensible — new context sources (Fetch Play, etc.) added once, available to all consumers immediately.

New module: github.com/fetch-rewards/consumer-context-service

Section titled “New module: github.com/fetch-rewards/consumer-context-service”
consumer-context-service/
├── go.mod
├── Makefile
├── cmd/
│ └── server/
│ └── main.go # Single binary: REST + MCP on two ports
├── internal/
│ ├── config/
│ │ └── config.go # YAML + env var config
│ │
│ ├── clients/ # Top-level: all upstream client adapters
│ │ ├── fps.go # FIDO Product Service
│ │ ├── fidora.go # FIDORA (retailer-specific product data)
│ │ ├── offer_guardian.go # Offer Guardian adapter
│ │ ├── fido_index.go # FIDO reverse index with periodic refresh
│ │ ├── neli.go # Neli (user offer eligibility)
│ │ ├── button.go # Button Merchant Service
│ │ ├── retailer.go # Retailer Service
│ │ ├── ereceipt.go # eReceipt dual-provider check
│ │ ├── neo4j.go # Neo4j read-only graph queries
│ │ ├── offer_search.go # Offer Search (semantic search)
│ │ ├── fido_search.go # FIDO Search (product search)
│ │ ├── lidar.go # LIDAR (location-based offers)
│ │ ├── purchase_history.go # Purchase History Service (PHS)
│ │ ├── user.go # User Service (profile, points)
│ │ └── web.go # Web fetch + Google search
│ │
│ ├── cache/
│ │ └── cache.go # Generic TTL cache
│ │
│ │ # ── Domain services (common access layer) ──
│ │ # Both REST handlers and MCP tools call these.
│ │ # Each domain owns its types, business logic, and scoring.
│ │
│ ├── offers/ # Offer domain
│ │ ├── service.go # GetOffersForProduct(), SearchOffers(), SearchNearby()
│ │ ├── service_test.go
│ │ └── types.go # OfferContext, OfferSearchResult
│ │
│ ├── users/ # User domain
│ │ ├── service.go # GetProfile(), GetHistory(), GetCandidates()
│ │ ├── service_test.go
│ │ └── types.go # UserProfile, PurchaseHistory, RepurchaseCandidate
│ │
│ ├── products/ # Product enrichment domain
│ │ ├── service.go # GetProductContext(), BatchGetProductContext(), SearchProducts()
│ │ ├── service_test.go
│ │ ├── scoring.go # EV, total points, stickers, CTA
│ │ ├── scoring_test.go
│ │ └── types.go # ProductContext, RetailerContext, ScoringContext, ProductSearchResult
│ │
│ ├── shopping/ # Shopping list domain
│ │ ├── service.go # CreateList(), OptimizeList()
│ │ └── types.go
│ │
│ ├── context/ # Cross-domain orchestration
│ │ ├── service.go # FullEnrich() — composes product + offer + user domains
│ │ ├── service_test.go
│ │ └── types.go # FullContext (union response)
│ │
│ │ # ── Transports (thin, no business logic) ──
│ │
│ ├── api/ # REST transport
│ │ ├── handler.go # Routes + handlers, calls domain services
│ │ └── handler_test.go
│ │
│ └── mcp/ # MCP transport
│ ├── tools.go # Tool definitions + handlers, calls domain services
│ └── tools_test.go
├── pkg/
│ └── client/
│ └── client.go # Go HTTP client (optional convenience)
├── config/
│ ├── local.yaml
│ ├── stage.yaml
│ └── prod.yaml
└── consumer-context-service.yml # FSD ECS deployment
  1. Single binary, two transports — REST on :8080 for programmatic consumers, MCP on :8081 for agents. Both call the same domain services.
  2. Common access layer — domain service.go files are the boundary. REST handlers and MCP tools are thin transport adapters: parse params → call service → format response. Zero business logic in transports.
  3. Clients at top level — shared across domains. Neo4j, Neli, Offer Guardian, etc. are used by multiple domain services.
  4. Centralized caching — FidoIndex, Button merchants, FPS metadata all in-memory. Shared across all request paths.
  5. Graceful degradation — each enrichment tier is independent. Missing FIDORA = nil RetailerProduct, but offers/history/scoring still populate.
  6. Replaces rover-mcp entirely — all existing tools migrate here.
  7. Domain-namespaced — new context domains (Fetch Play, etc.) are self-contained additions. Searches live under their domain (offer search under offers/, product search under products/).

#ClientServiceWhat It Provides
1FPSFIDO Product ServiceGeneric product metadata: name, brand, category
2FIDORAFIDO Assortment ServiceRetailer-specific: price, image, URL, retailerID, availability
3Offer GuardianOffer Guardian + FidoIndexAll offers for a FIDO, reverse-indexed
4NeliNeli EligibilityPer-user offer eligibility
5ButtonButton Merchant ServiceMerchant PPD, logo, stickers
6RetailerRetailer ServiceVenue type classification
7eReceipteReceipt Provider + HandbookeReceipt scanning support
8Neo4jConsumer GraphPurchase history, repurchase metrics

Search & discovery sources (from rover-mcp)

Section titled “Search & discovery sources (from rover-mcp)”
#ClientServiceWhat It Provides
9Offer SearchOffer Search ServiceSemantic offer search with eligibility filtering
10FIDO SearchFIDO Search ServiceProduct search by natural language description
11LIDARLIDAR ServiceLocation-based offer discovery
12PHSPurchase History ServiceCanonical purchase history with item details
13User ServiceUser ServiceUser profile, points balance

MethodPathDescription
GET/v1/products/:fido_id/context?user_id=XFull enrichment, single product
POST/v1/products/contextBatch enrichment ({"fido_ids": [...], "user_id": "X"})
GET/v1/products/:fido_id/offers?user_id=XOffers for a product (with eligibility)
GET/v1/users/:user_id/history?fido_id=XGraph purchase history
GET/v1/users/:user_id/candidates?lookback_days=90Repurchase candidates
MethodPathDescription
POST/v1/offers/searchSemantic offer search
POST/v1/offers/search/nearbyLocation-based offer search
MethodPathDescription
POST/v1/products/searchProduct search by description
MethodPathDescription
GET/v1/users/:user_id/purchasesPurchase history (PHS)
GET/v1/users/:user_id/profileUser profile + points
MethodPathDescription
GET/healthHealth check

ToolInputsMaps to
get_product_contextfido_id, user_idGET /v1/products/:fido_id/context
get_product_contextsfido_ids[], user_idPOST /v1/products/context
get_offers_by_fidofido_id, user_id?GET /v1/products/:fido_id/offers
get_purchase_historyfido_id, user_idGET /v1/users/:user_id/history
get_repurchase_candidatesuser_id, lookback_days?GET /v1/users/:user_id/candidates
ToolInputsMaps to
search_offersquery, limit?, user_id?POST /v1/offers/search
search_nearby_offersquery, latitude, longitude, user_id?POST /v1/offers/search/nearby
ToolInputsMaps to
search_productsdescriptions[]POST /v1/products/search
ToolInputsMaps to
get_user_purchase_historyuser_id, start_time?, end_time?, category?, brand?GET /v1/users/:user_id/purchases
get_user_profileuser_id?GET /v1/users/:user_id/profile
ToolInputsNotes
create_shopping_listcriteria?, preferences[]?, exclude[]?, user_id?Uses enrichment + offer data
optimize_shopping_listitems[]?, optimize_for?, user_id?Uses enrichment + offer data
fetch_webpageurl, depthGeneric web fetch
web_searchquery, num_resultsGoogle Custom Search
llm_feedbackmessageLogging only
welcomeStatic widget content

DataSourceCache TTLRefresh
FIDO reverse indexOffer Guardian (all active)In-memoryPeriodic rebuild every 5 min
Button merchantsButton Merchant ServiceUntil restartLazy-load on first access
FPS product metadataFIDO Product Service1 hourPer-key TTL
FIDORA retailer dataFIDORA API15 minPer-key TTL
Neli eligibilityNeli service5 minPer user-offer TTL
Neo4j historyNeo4j graph1 minPer user-product TTL
Retailer venue typeRetailer Service30 minPer-store TTL

Create the new module with domain types, services, scoring, and both transports.

Files to create:

  • go.mod
  • cmd/server/main.go — dual-port server (REST + MCP)
  • Makefile
  • internal/products/types.go — ProductContext, RetailerContext, ScoringContext, DisplayContext, ProductSearchResult
  • internal/products/service.goGetProductContext(), BatchGetProductContext(), SearchProducts()
  • internal/products/scoring.go — EV, total points, stickers, CTA (ported from calculator.go + rover-agent)
  • internal/products/scoring_test.go
  • internal/offers/types.go — OfferContext, OfferSearchResult
  • internal/offers/service.goGetOffersForProduct(), SearchOffers(), SearchNearby()
  • internal/users/types.go — UserProfile, PurchaseHistory, RepurchaseCandidate
  • internal/users/service.goGetProfile(), GetHistory(), GetCandidates()
  • internal/context/types.go — FullContext (cross-domain union)
  • internal/context/service.goFullEnrich() composing all domains
  • internal/api/handler.go — REST routes + handlers (thin, calls services)
  • internal/mcp/tools.go — all 16 MCP tool definitions (thin, calls services)

Top-level clients (all in internal/clients/):

  • fps.go, fidora.go, offer_guardian.go, fido_index.go
  • neli.go, button.go, retailer.go, ereceipt.go, neo4j.go
  • offer_search.go, fido_search.go, lidar.go
  • purchase_history.go, user.go, web.go

Cache:

  • internal/cache/cache.go — generic TTL cache

consumer-graph-worker (scheduler → REST API):

  • cmd/unified-worker/dependencies.go — add consumer-context-service HTTP client
  • internal/notification/repurchase/handler.go — call context API for offer enrichment
  • internal/scheduler/eligibility.go — use context API response for offers + eligibility

rover-agent / UX streaming gateway (→ REST API):

  • internal/enrichment/product/product_enricher.go — replace 4 individual client calls (FIDORA, Button, eReceipt, rover-mcp JSON-RPC) with single GET /v1/products/:fido_id/context call
  • Deprecate rover-mcp dependency

consumer-agent + all future agents (→ MCP):

  • Point MCP client at consumer-context-service service :8081
  • Gains all enrichment tools + migrated rover-mcp tools
  • Future scheduler agent uses MCP for the same data the scheduler gets via REST

  1. cd consumer-context-service && go test ./... — scoring tests pass
  2. cd consumer-context-service && go build ./cmd/server/ — server builds
  3. curl localhost:8080/v1/products/test-fido/context?user_id=test — returns enriched JSON
  4. MCP tool calls via :8081 return same data
  5. cd consumer-graph-worker && go build ./cmd/unified-worker/ — compiles after integration

SourceFromTo
Product scoring (EV, days since, predicted next)consumer-graph-worker/internal/scheduler/calculator.gointernal/products/scoring.go
Total points calculationrover-agent internal/enrichment/product/points.gointernal/products/scoring.go
Sticker generationrover-agent internal/enrichment/product/stickers.gointernal/products/scoring.go
CTA deep link builderrover-agent internal/enrichment/product/cta.gointernal/products/scoring.go
FIDO Product Service clientconsumer-graph-worker/internal/api/fido/client.gointernal/clients/fps.go
FIDORA clientrover-agent internal/enrichment/product/fidora_client.gointernal/clients/fidora.go
Offer Guardian + FidoIndexrover-mcp pkg/api/service/offer-guardian/internal/clients/offer_guardian.go + fido_index.go
Neli clientconsumer-graph-worker/internal/api/neli/client.gointernal/clients/neli.go
Button Merchant clientrover-agent internal/enrichment/product/button_client.gointernal/clients/button.go
Retailer Service clientconsumer-graph-worker/internal/api/retailer/client.gointernal/clients/retailer.go
eReceipt clientrover-agent internal/enrichment/product/ereceipt_client.gointernal/clients/ereceipt.go
Neo4j read queriesconsumer-graph-worker/internal/notification/repurchase/neo4j.gointernal/clients/neo4j.go
Offer Search clientrover-mcp pkg/api/service/offer-search/internal/clients/offer_search.go
FIDO Search clientrover-mcp pkg/api/service/fido-product/internal/clients/fido_search.go
LIDAR clientrover-mcp pkg/api/service/lidar/internal/clients/lidar.go
Purchase History clientrover-mcp pkg/api/service/purchase-history/internal/clients/purchase_history.go
User Service clientrover-mcp pkg/api/service/ (user profile methods)internal/clients/user.go
MCP tool registry patternrover-mcp pkg/tools/registry.gointernal/mcp/tools.go