agentic-travel-recs

agentic-travel-recs

Exposes tools for retrieving member profiles and personalized travel recommendations with multi-tenant partner rule enforcement, enabling AI agents to respect business constraints like caps and exclusions.

Category
Visit Server

README

Agentic Travel Recommendations Service

A multi-tenant travel recommendations service that exposes both a REST API and an MCP server, with partner-specific rule enforcement (recommendation caps, category exclusions). Built as an exploration of MCP server design and multi-tenant rule enforcement patterns for AI-agent-facing internal services.

Node TypeScript Tests License

What it does

An AI agent (e.g. a travel concierge embedded in a partner-branded portal) needs two things from this service:

  1. A member's profile — loyalty tier, past bookings, partner affiliation.
  2. Personalized travel recommendations that respect the partner's business rules.

Different partners have different rules. Some cap recommendations at 3 per session; others exclude entire categories like cruises. The service enforces those rules in a single code path so they can't drift between the REST and MCP interfaces.

Quick start

npm install
npm test          # 13 tests: rules engine + integration
npm run demo      # spawns the MCP server and walks 4 partner scenarios
npm run dev:api   # REST API on :3000
npm run dev:mcp   # MCP server on stdio

The demo is an actual MCP client that spawns the server as a subprocess and calls its tools the way an AI agent would.

Seeded scenarios

The mocks include four partners exercising every combination of rule shape:

Member Partner Loyalty Partner rules
M-1001 Bank A Gold No cap, no exclusions
M-1002 Credit Union B Platinum Excludes cruises
M-1003 Membership Org C Silver Cap of 3 recommendations
M-1004 Airline D Gold Cap of 5, excludes cars and cruises

Project structure

src/
  mcp/server.ts                    # MCP server, stdio transport, two tools
  api/server.ts                    # REST API (Express)
  api/routes/recommendations.ts
  services/
    recommendationsService.ts      # orchestrator — shared by REST & MCP
    memberDataService.ts           # client for the (mocked) member data upstream
    partnerConfigService.ts        # client for the (mocked) partner config upstream, 60s TTL cache
  rules/partnerRulesEngine.ts      # single source of truth for partner-rule enforcement
  mocks/                           # in-memory mocks of the two upstreams
  types/index.ts                   # shared domain types
  utils/logger.ts                  # pino JSON logger
cli/demo.ts                        # MCP client that spawns the server and walks the scenarios
tests/                             # vitest — unit tests for the rules engine + integration
docker/Dockerfile                  # multi-stage image; default CMD runs the REST API

Architecture

Four moving parts:

  1. AI agent speaks MCP.
  2. This service exposes two MCP tools (get_member_profile, get_recommendations) and an equivalent REST API for non-MCP callers and for debugging.
  3. Member data service (mocked) — returns member ID, loyalty tier, partner ID, and last five bookings.
  4. Partner configuration service (mocked, read-only) — returns per-partner rules.

Two design properties matter most:

  • REST and MCP share the same service layer. recommendationsService.ts is called from both api/routes/recommendations.ts and mcp/server.ts. Partner rules run in exactly one code path — it's structurally impossible to ship an MCP path that bypasses them.
  • Rules enforcement is a pure function. enforcePartnerRules(candidates, partnerConfig, requestedMax?) in src/rules/partnerRulesEngine.ts has no I/O and no side effects. Roughly 40 lines with a dedicated unit-test file, so partner-rule regressions can be caught in seconds.

Every response includes an appliedRules block reporting which categories were filtered and how many results were capped — so a downstream agent can explain to a user why cruises aren't showing up, and an on-call engineer can diagnose "empty result" cases without reading logs.

Design trade-offs

stdio MCP transport rather than Streamable HTTP.
Stdio has no public network surface — no auth, no rate limiting, no session management to design. The MCP server co-locates as a sidecar with the AI agent that consumes it. Streamable HTTP would be the upgrade path for remotely-hosted use; the tool contracts wouldn't change.

Rule-based candidate scoring, not ML.
The recommender uses a legible scoring function: baseline + historical-type affinity + tier boost − recent-destination penalty. Swapping in an ML model later would leave the rules engine, MCP tools, and REST contracts untouched. A broken model would silently degrade recommendations; a broken rules engine would violate a partner contract. Ship the trustworthy piece first.

60-second partner config cache.
Short enough that a partner rule change propagates within about a minute, long enough to reduce upstream load by roughly 99% on the hot path. A cache-bust endpoint could be added later for partners that need instant propagation.

Handling partner configuration changes

Partner config is read-only from this service's perspective, so a partner changing their cap or adding an exclusion happens entirely upstream. Within the 60-second TTL, the change is picked up on the next call. The rules engine reads it fresh, and the appliedRules block in the response reflects the new state.

The only case that requires a code change here is if a partner adds a new kind of rule (e.g. a time-of-day restriction). That would need a new field in the PartnerConfig type and a new step in enforcePartnerRules. A new instance of an existing rule shape needs no code change — that's what the four seeded partners demonstrate.

Production considerations

Things designed for the person on-call at 2 a.m.:

  • Every recommendation call logs one INFO line with { memberId, partnerId, returned, filteredCount, cappedCount }. One grep answers "did the rules run?"
  • The appliedRules block on every response means the same question can be answered by a curl, without logs.
  • The rules engine has 7 unit tests including a specific "order matters" test that fails if filter and cap are reversed.
  • Known error types map to stable HTTP status codes and structured MCP isError: true responses.

Things I'd add before running this in production:

  • Timeouts and one retry with backoff on upstream calls, plus a circuit breaker with a documented fallback behavior.
  • Per-partner dashboards for error rate and p99 latency; an alert if filteredCount suddenly spikes for a partner (usually the signal that someone shipped a bad config).
  • Load testing with realistic traffic mix to validate the cache TTL choice.
  • Streamable HTTP transport for MCP if remote hosting becomes a requirement.

About the build process

The initial scaffold — types, mocks, service layer skeleton, and the first pass at the MCP server — was AI-assisted. Everything after that (the Partner D onboarding to prove multi-tenant behavior, the integration test for it, the real readiness probe replacing the stub, and a cross-platform fix for a Windows path bug in the MCP entrypoint) was written by me. The Windows bug is the one I'd point out: the AI's original entrypoint check used import.meta.url === \file://${process.argv[1]}`, which silently fails on Windows because the two sides of the comparison use different path formats. Green unit tests didn't catch it because they never spawn the MCP server as a subprocess. I caught it by running the demo end-to-end, and fixed it by normalizing both sides through pathToFileURL`.

License

MIT — see LICENSE.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured