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.
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.
What it does
An AI agent (e.g. a travel concierge embedded in a partner-branded portal) needs two things from this service:
- A member's profile — loyalty tier, past bookings, partner affiliation.
- 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:
- AI agent speaks MCP.
- This service exposes two MCP tools (
get_member_profile,get_recommendations) and an equivalent REST API for non-MCP callers and for debugging. - Member data service (mocked) — returns member ID, loyalty tier, partner ID, and last five bookings.
- Partner configuration service (mocked, read-only) — returns per-partner rules.
Two design properties matter most:
- REST and MCP share the same service layer.
recommendationsService.tsis called from bothapi/routes/recommendations.tsandmcp/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?)insrc/rules/partnerRulesEngine.tshas 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
appliedRulesblock 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: trueresponses.
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
filteredCountsuddenly 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.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。