Skip to main content
Glama

TasteBridge

Common ground for two tastes. TasteBridge is an agentic tool that finds movies, shows, books, music, podcasts, games, places or travel destinations that two people will both enjoy. It runs on the Qloo Taste AI API and is available three ways: a web demo, a CLI and an MCP server that any AI agent can call.

Built for the Qloo Agentic Hackathon. Every title, score and tag it shows comes from a Qloo API response: TasteBridge uses no LLM and never invents results.

Try it

Related MCP server: qloo-mcp

The problem

Most recommenders serve one person. Real choices are shared: a couple picking a film, friends choosing a book-club title, travel buddies looking for a bar in a new city. Splitting the difference by hand usually ends with whatever one person already likes.

TasteBridge is for anyone who has to agree on something with someone else, and for AI assistants that need a reliable, explainable "what would we both like?" tool instead of guessing.

What it does

  1. Takes two people and up to 5 favorites each (any domain: "Amelie", "artist: Daft Punk", "book: Dune").

  2. Resolves every favorite to a Qloo entity.

  3. Compares the two taste profiles: shared taste tags and what is distinctive to each person.

  4. Asks Qloo for candidates in the target category, scored against both people's favorites, with per-favorite explainability.

  5. Re-ranks for fairness, so a pick that only one person would love does not win just on raw affinity.

  6. Explains every pick with Qloo data: affinity, the share of the signal coming from each person (and their strongest favorite), and the shared tags the pick carries.

How Qloo is used

All calls are GET requests to https://hackathon.api.qloo.com with the X-Api-Key header.

Step

Endpoint

Key parameters

Purpose

Resolve

GET /search

query, types (optional, from a movie:/book:/... prefix), take=5

Name to entity ID. Result types that /v2/insights cannot use (albums, localities) are skipped.

Compare

GET /v2/insights/compare

a.signal.interests.entities, b.signal.interests.entities, take

Shared tags (results.tags) and each side's tags (results.a, results.b).

Recommend

GET /v2/insights

filter.type, signal.interests.entities, filter.exclude.entities, feature.explainability=true, take, filter.location.query (places, destinations)

Candidates with query.affinity and query.explainability (contribution of each input entity).

Explain

GET /v2/insights + GET /entities

filter.results.entities=<candidate>, feature.explainability=true; entity_ids

Scores one candidate against the seeds and finds the tags it shares with each seed.

Supported filter.type values: urn:entity:movie, tv_show, book, artist, podcast, videogame, place, destination, brand. (The hackathon API accepts urn:entity:videogame; urn:entity:video_game returns HTTP 400. Verified live on 2026-10-21.)

Responses are cached privately in server memory for 10 minutes. No Qloo response data is stored in this repository: the offline tests use clearly fictional fixtures.

The agent loop

The agent is deterministic and fully inspectable. Each run returns its plan and a trace of every Qloo call (endpoint, HTTP status, latency).

plan      validate input, choose filter.type, decide whether a location is required
resolve   GET /search for each favorite
compare   GET /v2/insights/compare (person A vs person B)
recommend GET /v2/insights with all favorites as signals and explainability on
rank      group score = affinity x (0.6 + 0.4 x balance)
explain   reasons built only from Qloo fields

Balance comes from Qloo's explainability: the contribution scores of each person's favorites are summed into a per-person share, and balance = 1 - (max share - min share). A 50/50 pick has balance 1. A pick driven 90/10 by one person has balance 0.2, which lowers its group score even if its raw affinity is higher. Ties are broken by affinity, then name, then ID, so the same Qloo response always gives the same ranking.

If a step fails, the agent stops at that step with a clear message (for example, no usable match for one person's favorites). A failed taste comparison is not fatal: recommendations still use both people's favorites.

Architecture

            +--------------------------- server.mjs (127.0.0.1:4793) ---------------------------+
 browser -> |  /             web demo (public/index.html, app.js, style.css)                     |
            |  /api/common-ground   -> agent.findCommonGround()                                  |
 AI agent ->|  /mcp          MCP Streamable HTTP (spec 2025-11-25), 5 tools -> src/tools.mjs     |
            |  /health                                                                         |
            +------------------------------------------+---------------------------------------+
                                                       |
                 cli.mjs -> agent.findCommonGround()   |
                                                       v
                                  src/qloo.mjs  QlooClient (GET, X-Api-Key, timeout, cache, error codes)
                                                       |
                                                       v
                                         https://hackathon.api.qloo.com

File

Role

server.mjs

HTTP server: MCP endpoint, web demo, JSON API, security headers, Host/Origin validation, per-IP rate limit

src/qloo.mjs

Qloo client and response normalization

src/agent.mjs

Plan, resolve, compare, recommend, rank, explain

src/tools.mjs

The five MCP tools with Zod input schemas

cli.mjs

Command-line agent

scripts/smoke.mjs

Live smoke test (writes only status, counts and latency to logs/)

tests/

Unit, MCP/HTTP and live tests (node:test)

MCP tools

Tool

What it does

qloo_resolve_entities

Names to Qloo entities (/search)

qloo_recommend

Recommendations for one category from seed entity IDs, with explainability

qloo_compare_tastes

Shared and distinctive taste tags for two sets of entities

qloo_explain_match

Why a candidate fits a set of seeds: affinity, per-seed contribution, shared tags

tastebridge_common_ground

The whole agent loop for two people in one call

All tools are read-only (readOnlyHint: true). The server uses the official MCP TypeScript SDK (@modelcontextprotocol/server and @modelcontextprotocol/node) in stateless Streamable HTTP mode (protocol revision 2025-11-25) and only accepts localhost Host/Origin headers by default.

Point any MCP client that supports Streamable HTTP at http://127.0.0.1:4793/mcp. A raw JSON-RPC call looks like this:

curl -s http://127.0.0.1:4793/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2025-11-25' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"tastebridge_common_ground","arguments":{"people":[{"name":"Alex","favorites":["Amelie","artist: Daft Punk"]},{"name":"Sam","favorites":["Blade Runner","Interstellar"]}],"category":"movies","count":3}}}'

PowerShell (2025-11-25 requests are answered as a short SSE stream, so the snippet reads the data: line):

$body = @{jsonrpc='2.0'; id=1; method='tools/list'; params=@{}} | ConvertTo-Json -Depth 8
$resp = Invoke-WebRequest -UseBasicParsing -Uri http://127.0.0.1:4793/mcp -Method Post -ContentType 'application/json' `
  -Headers @{Accept='application/json, text/event-stream'; 'MCP-Protocol-Version'='2025-11-25'} -Body $body
$text = [Text.Encoding]::UTF8.GetString($resp.RawContentStream.ToArray())
$json = if ($text.TrimStart().StartsWith('{')) { $text } else { ($text -split '\r?\n' | Where-Object { $_ -like 'data: *' } | Select-Object -First 1).Substring(6) }
($json | ConvertFrom-Json).result.tools.name

Setup and run

Requirements: Node.js 22 or newer and a Qloo hackathon API key.

Windows (PowerShell)

cd 03-qloo-taste-agent
npm.cmd install
Set-Content -Path .env -Value 'QLOO_API_KEY=your_key_here'
npm.cmd start            # http://127.0.0.1:4793
npm.cmd test             # unit + MCP/HTTP tests; the live test runs only when a key is set
npm.cmd run smoke        # live smoke test against the Qloo API

(npm.cmd avoids the PowerShell execution-policy prompt that npm can trigger.)

macOS / Linux

cd 03-qloo-taste-agent
npm install
echo 'QLOO_API_KEY=your_key_here' > .env
npm start
npm test

CLI

node cli.mjs --a-name Alex --a "Amelie; artist: Daft Punk; book: Dune" \
             --b-name Sam  --b "Blade Runner; Interstellar" --category movies --count 5
node cli.mjs --a "Amelie" --b "Arctic Monkeys" --category places --location Lisbon --json
node cli.mjs --a "Amelie" --b "Blade Runner" --plan-only     # prints the plan, no API call

Categories: movies, tv, books, music, podcasts, games, places (needs --location), destinations, brands.

Configuration

Variable

Default

Meaning

QLOO_API_KEY

(none)

Qloo hackathon key. Read from the environment or .env; never logged or returned.

PORT

4793

HTTP port

HOST

127.0.0.1

Bind address. Anything other than localhost requires ALLOWED_HOSTS.

ALLOWED_HOSTS

(none)

Comma-separated public hostnames accepted in Host/Origin when deployed behind a public URL.

The JSON API and MCP endpoint share a per-IP limit of 30 requests per minute to protect the API quota.

Tests

npm test runs node --test:

  • Qloo client: request format (GET, base URL, X-Api-Key), missing key fails closed without a request, HTTP error mapping, timeouts, caching, key never serialized, best-match selection, image host filtering.

  • Tools and agent: every MCP tool with mocked HTTP, fairness ranking, explanations, stage errors, input validation, and a check that every returned title exists in the mocked Qloo response.

  • MCP over HTTP: initialize, tools/list, tools/call for all five tools, tool errors, the web demo files, the REST endpoint, Origin checks and the rate limiter.

  • Live: one end-to-end run against the real API, skipped automatically when QLOO_API_KEY is not set (set QLOO_LIVE=0 to skip it on purpose).

Limitations

  • Exactly two people per request; the Qloo compare endpoint is pairwise.

  • Results depend on Qloo hackathon data coverage. Some favorites may not resolve, and some places or regions may have little data.

  • Places need a city or area; recommendations without one would not be useful.

  • Qloo explainability only lists inputs with a meaningful contribution, so a person whose favorites barely influence a candidate gets a 0% share for it. When explainability is missing, balance is treated as neutral.

  • Shared-tag explanations depend on the tags Qloo returns; some picks have none.

  • The UI is English only and keeps no history or accounts.

Privacy and data

  • The API key lives only in .env (git-ignored) or the process environment.

  • No Qloo responses are written to disk except the smoke-test summary (status codes, counts and latency only).

  • The web page loads images only from images.qloo.com, under a strict Content Security Policy.

License

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides cross-media recommendations from TasteDive, enabling AI agents to discover related music, movies, TV shows, books, games, and podcasts through a free API key.
    1 npm
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to resolve and search taste entities and tags, get recommendations, demographics, and taste analysis, and run an audience-finding agent that turns a creative work description into audience affinities, local places, and a ready-to-use pitch.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Exposes status, sample taste signals, Qloo entity search, and budget-constrained two-person date-night planning tools plus a transparent planning prompt. Enables external agents to discover tools, search cultural entities, generate and inspect balanced itineraries, and replan after user feedback over stdio or Streamable HTTP.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Exposes two Qloo-grounded tools, resolve_taste_anchor and recommend_for_group, so any agent can turn what individual people like into per-person signals instead of stereotypes. Group picks are ranked by how many people each option covers, then by affinity, letting agents answer 'what should this group do?' with grounded recommendations.
    MIT