Skip to main content
Glama
Varma904

Agentic Travel Recommendations Service

by Varma904

Agentic Travel Recommendations Service

This project is a TypeScript and Node.js proof-of-concept for a multi-tenant travel recommendation service. It exposes shared recommendation capabilities through a REST API, Streamable HTTP MCP endpoint, and command-line interface.

Key Features

  • REST API for health, member profiles, and recommendations

  • Streamable HTTP MCP endpoint

  • MCP tool: get_member_profile

  • MCP tool: get_recommendations

  • Authoritative, member-derived tenant resolution

  • Partner-specific recommendation caps

  • Partner-specific category exclusions

  • Deterministic recommendation generation

  • Fail-closed partner configuration behavior

  • Request IDs and structured JSON logs

  • Minimal CLI demonstration

  • Multi-stage Docker build

  • Automated tests

Architecture at a Glance

REST / MCP / CLI
       |
       v
RecommendationService
       |
       v
MemberDataService
       |
       | member.partnerId
       v
PartnerConfigurationService
       |
       v
CandidateGenerator
       |
       v
RecommendationPolicy
       |
       | exclusions then cap
       v
Final Recommendations

Callers provide only memberId; they do not select the authoritative partnerId. The member profile determines the partner configuration, and REST, MCP, and CLI all reuse the same business layer.

Quick Start

npm ci
npm run dev

The service is available by default at http://localhost:3000.

Type Checking

npm run typecheck
npm run typecheck:test
npm run typecheck:all

Tests

npm test

The current verified baseline is 46 passing tests across 6 files.

Production Build

npm run build
npm start

REST API

GET /health
GET /api/members/:memberId
GET /api/recommendations/:memberId

Example requests:

curl http://localhost:3000/api/members/MEMBER-001
curl http://localhost:3000/api/recommendations/MEMBER-001

MCP

The MCP server is exposed through:

POST /mcp

It provides these tools:

  • get_member_profile

  • get_recommendations

Both tools accept only the member identifier:

{
  "memberId": "MEMBER-001"
}

The implementation uses the official @modelcontextprotocol/sdk Streamable HTTP transport. The caller does not supply partnerId; it is resolved from the authoritative member profile.

CLI

npm run cli -- MEMBER-001

Demo members:

  • MEMBER-001BANK_A

  • MEMBER-002BANK_B

  • MEMBER-003CREDIT_UNION_C

Docker

docker build -t agentic-travel-recommendations .
docker run --rm -p 3000:3000 agentic-travel-recommendations

The image uses a multi-stage build, a Node 24 runtime, and a non-root runtime user. The HTTP server handles graceful shutdown signals.

Section A — Architecture & Trade-offs

Architecture Overview

The service is a stateless TypeScript and Node.js application that exposes the same recommendation workflow through REST, Streamable HTTP MCP, and a CLI. Each transport validates its input and delegates to the shared RecommendationService; transport handlers do not implement partner policy themselves.

The authoritative tenant flow is:

memberId
→ MemberDataService
→ MemberProfile.partnerId
→ PartnerConfigurationService
→ CandidateGenerator
→ RecommendationPolicy
→ final recommendations

Callers supply memberId and never select the authoritative partnerId. The member profile returned by MemberDataService determines which partner configuration is retrieved. Both upstream services also correlate the identity embedded in a response with the identity requested, and RecommendationService performs an additional partner-identity check before generation.

Candidate generation is intentionally independent of partner policy. The deterministic generator first produces raw candidates from the member profile; the generic policy layer then removes candidates in excludedCategories and applies recommendationCap, in that order. Only the resulting recommendations are returned. The Member Data Service and Partner Configuration Service are mocked for this proof-of-concept, and partner configuration access is read-only.

Design Trade-offs

Correctness over availability. If authoritative partner configuration is missing, unavailable, schema-invalid, or identity-mismatched, the request fails closed. The service does not substitute permissive defaults or return unrestricted recommendations. This may reduce availability during an upstream failure, but prevents recommendations from escaping the correct tenant policy.

Fresh configuration over caching. The first release retrieves partner configuration for every recommendation request rather than adding cache infrastructure. This keeps behavior simple and ensures each successful request uses current policy. It accepts additional upstream latency and load; a short-lived cache is appropriate later only if measured performance justifies the consistency trade-off.

Deterministic generation over an external LLM. Candidate generation is reproducible, testable, cost-free, and operationally predictable. This limits the sophistication of personalization, but makes policy behavior and assessment results easy to verify. A future LLM or ranking component could replace candidate generation without changing deterministic policy enforcement.

Handling Partner Configuration Changes

The Partner Configuration Service is a read-only dependency. If a partner changes its recommendation cap from unlimited to 3, or adds cruise to excludedCategories, the recommendation service needs no code change and no tenant-specific branch. The next successful request reads the current configuration, and the generic policy logic applies the new exclusion and cap values.

As long as the configuration remains compatible with the existing schema, the application does not require redeployment. If configuration caching is introduced later, it must have an intentionally short TTL or reliable invalidation strategy because stale configuration could temporarily violate the partner’s current policy.

Section B — Production Readiness & Incident Response

Incident Runbook Entry

Scenario: A member reports that the AI Concierge displayed a cruise recommendation even though their partner excludes cruises.

  1. Identify and correlate. Obtain the request or correlation ID from the report when available and locate the corresponding structured logs. Record the operation, memberId, authoritative partnerId once resolved, resultCode, and HTTP status where applicable. Travel history and recommendation payloads are intentionally not logged, so use identifiers and outcome metadata for correlation.

  2. Verify the authoritative tenant. Retrieve the affected member through MemberDataService and confirm that the requested memberId equals the returned member.memberId. Derive the tenant only from member.partnerId. Do not trust a partner ID supplied by a frontend, MCP caller, query parameter, or support report.

  3. Verify partner configuration. Retrieve configuration using member.partnerId, then confirm configuration.partnerId === member.partnerId. Inspect excludedCategories and recommendationCap, and determine whether cruise is excluded in the current authoritative configuration. Missing, unavailable, malformed, or identity-mismatched configuration must cause the service to fail closed rather than use permissive defaults.

  4. Reproduce the pipeline. Run the member through the same recommendation workflow. A cruise in raw CandidateGenerator output is not itself a defect because generation deliberately ignores partner policy. Verify that RecommendationPolicy processes raw candidates → remove excluded categories → apply recommendation cap → final recommendations, and confirm that cruises are absent from the final result.

  5. Isolate the failure location. If cruises appear in raw candidates but not final recommendations, policy is operating correctly. Investigate a stale client response, a response associated with the wrong member, another consumer or endpoint bypassing the expected workflow, or a difference between the reported time and current configuration. If a cruise survives RecommendationPolicy, inspect category comparison or normalization, authoritative configuration contents and identity, and recent policy changes or regressions.

  6. Contain. If the correct partner policy cannot be established or safely reproduced, fail closed instead of returning potentially non-compliant recommendations. Do not attempt to modify the read-only Partner Configuration Service from this application.

  7. Fix and verify. Correct the defect in the responsible layer and add a regression test reproducing the exact failure. Run:

    npm run typecheck:all
    npm test
    npm run build

    Verify the affected partner, at least one unaffected tenant, REST behavior, and MCP behavior when relevant.

  8. Follow up. Record the root cause, affected partner and member scope, impact window, remediation, regression coverage, and preventive action.

Part B2 — Required Reasoning Question

An AI coding assistant could plausibly produce an implementation that Zod-validates upstream member and partner records but never correlates returned identities with requested identities. The code would be type-safe, schema validation and happy-path tests would pass, and a superficial review would see reasonable defensive validation. The missing cross-tenant invariant would still create a serious policy risk.

For example, MEMBER-001 belongs to BANK_A. RecommendationService requests BANK_A’s configuration, but a buggy or misrouted upstream service returns a completely schema-valid BANK_B configuration with an unlimited cap and no category exclusions. Zod correctly accepts its shape, but applying that policy to MEMBER-001 could bypass BANK_A’s restrictions.

I would catch this with an adversarial regression test: request BANK_A while a deliberately faulty configuration-service double returns the valid BANK_B configuration. I would expect InvalidUpstreamDataError, assert that no recommendation result is produced, and explicitly verify that CandidateGenerator.generate was not called. I would add the corresponding member-data test proving that the requested memberId must equal the returned member.memberId.

Before acting on AI-generated code, I would trace authority and execution order rather than rely on types alone. I would verify that member.partnerId, never caller input, selects the tenant; both returned identities match their authoritative requests; and missing, unavailable, or mismatched configuration fails closed without a permissive fallback. I would also confirm that generation cannot begin before policy identity is safely established and that negative, adversarial tests cover these cases alongside normal happy paths.

Section C — AI Usage Log

Interaction 1 — Architecture Review

What I asked

I asked the AI coding assistant to review the challenge and help design a minimal architecture that one engineer could realistically implement. The requested scope included domain models, mocked upstream services, recommendation logic, REST, MCP, CLI, automated testing, and containerization, while avoiding infrastructure that the proof-of-concept did not require.

What the AI provided

It proposed separating domain models, service contracts, mocked upstream services, candidate generation, partner policy, orchestration, and transport adapters. It initially suggested stdio as the simplest MCP transport.

What I kept, changed, or rejected

I kept the layered separation because it lets REST, MCP, and CLI call one business layer instead of implementing rules independently. I rejected stdio as the primary MCP transport and redirected the design to the official MCP SDK’s Streamable HTTP transport at POST /mcp. The assignment describes an internal API that agents should discover and invoke, and HTTP fits the containerized service architecture. I made that choice after comparing the proposal with the assignment’s integration requirements rather than accepting the simplest option automatically.

Interaction 2 — Incremental Implementation

What I asked

I did not ask for the entire application in one prompt. I divided implementation into bounded steps: domain models, schemas and errors, upstream contracts and mocks, candidate generation, recommendation policy, orchestration, REST, MCP, CLI, observability, and Docker. After each step, I reviewed the reported behavior and required typechecking and tests before proceeding.

What the AI provided

The assistant implemented each bounded component with focused tests and reported the files changed and verification results. This made individual design choices visible and reviewable instead of hiding them inside a large generated patch.

What I kept, changed, or rejected

I kept the shared RecommendationService, deterministic CandidateGenerator, separate RecommendationPolicy, member-derived tenant resolution, read-only configuration contract, and shared REST/MCP/CLI business logic. This structure makes tenant policy independently testable and prevents transport-specific rule implementations. I also intentionally retained deterministic generation instead of adding an external LLM dependency. The assessment focuses on service design and policy enforcement, and reproducible output is easier to test, debug, and demonstrate. Each increment was accepted only after its behavior matched the architectural invariants and the checks passed.

Interaction 3 — Production and Security Audit

What I asked

Once the application worked, I asked the AI to stop adding features and audit the repository from the perspectives of a senior engineer, multi-tenant security reviewer, on-call production owner, and REST/MCP API reviewer.

What the AI provided

The audit found that schema-valid upstream responses were not originally correlated with the member or partner identity requested. It also found that malformed JSON could fail before request-ID middleware established request context. Additional lower-priority improvements were suggested.

What I kept, changed, or rejected

I accepted both high-value findings because they affected tenant correctness and safe operations. For identity correlation, the implementation now verifies that a returned memberId matches the requested member, that configuration.partnerId matches the authoritative member.partnerId, and that RecommendationService repeats the configuration identity check defensively. Mismatches fail closed, and adversarial regression tests verify that candidate generation never starts when authoritative configuration cannot be established.

For malformed JSON, request context and the request ID are now established before parsing. Invalid bodies receive a safe structured 400 response without parser details, stack traces, filesystem paths, or raw request content.

I deferred lower-priority ideas such as persistent MCP sessions, additional distributed infrastructure, and more advanced observability because they were unnecessary for the four-week proof-of-concept and would increase operational scope. I evaluated each recommendation against the assignment requirements, tenant correctness, testability, operational risk, and delivery scope. The assistant supplied options and implementation help, but I reviewed the reasoning, selected the changes, and verified them through focused tests and end-to-end checks.

Four-Week First Step

What Ships First

The four-week target is a first shippable internal proof-of-concept. It demonstrates the required workflow with safe tenant enforcement and operational basics; it is not a claim that every capability needed for a broad production rollout is complete.

Week 1 — Service Foundation

  • Establish the TypeScript and Node.js service foundation.

  • Define domain models, strict Zod boundary validation, and typed errors.

  • Add the MemberDataService contract and mock implementation.

  • Add the read-only PartnerConfigurationService contract and mock implementation.

  • Establish authoritative, member-derived tenant resolution and the initial unit-test foundation.

Goal: Establish safe service boundaries and tenant authority before implementing recommendation logic.

Week 2 — Recommendation Workflow

  • Implement the deterministic CandidateGenerator independently of partner rules.

  • Implement RecommendationPolicy, including category exclusions and recommendation caps.

  • Enforce the required order: exclusions first, then the cap.

  • Add RecommendationService orchestration and fail-closed configuration behavior.

  • Cover policy and orchestration with focused unit tests.

Goal: Prove that contractual partner rules are deterministic and independent of candidate generation.

Week 3 — Interfaces and End-to-End Flow

  • Expose the REST endpoints and Streamable HTTP MCP endpoint.

  • Provide the MCP tools get_member_profile and get_recommendations.

  • Add the CLI demonstration.

  • Route REST, MCP, and CLI through the shared business layer.

  • Add REST/MCP integration tests and tenant-override tests.

Goal: Demonstrate the complete recommendation workflow through the interfaces required by the assignment.

Week 4 — Production Readiness and Delivery

  • Add request IDs, correlation fields, structured JSON logging, and safe error handling.

  • Handle malformed JSON safely and implement graceful shutdown.

  • Add the Docker multi-stage build and non-root runtime.

  • Typecheck production source and tests separately.

  • Perform the production/security audit and add adversarial identity-correlation tests.

  • Complete final end-to-end and container verification.

  • Prepare the README, incident runbook, and demonstration video.

Goal: Make the proof-of-concept supportable by the team that owns it on call.

What Comes Later

The following work is deliberately deferred until after the first four-week release:

  1. Real upstream integrations. Replace the mocked MemberDataService and PartnerConfigurationService implementations with real arrivia REST clients while preserving the existing service contracts and identity-correlation invariants.

  2. Existing authentication and authorization integration. Integrate with arrivia’s existing identity and gateway mechanisms rather than introducing a new identity platform. Authorization must preserve member-derived tenant authority.

  3. Network resilience. For real upstream HTTP dependencies, validate and configure request and connection timeouts, bounded retries where operations are safe to retry, and explicit failure behavior. Configuration uncertainty must continue to fail closed.

  4. Performance validation. Run realistic load and performance tests before optimizing. Consider short-lived partner-configuration caching only if measurements justify it. Stale policy is a correctness risk, so any cache requires a clear freshness and invalidation strategy.

  5. Recommendation intelligence. Potentially replace or augment the deterministic CandidateGenerator with an LLM, ranking model, or richer personalization. RecommendationPolicy must remain deterministic and outside the model so generated output cannot override partner rules.

  6. Production observability. Connect the existing structured events and correlation IDs to arrivia’s approved metrics, tracing, alerting, and operational tooling.

  7. MCP evolution. Consider stateful or resumable MCP behavior only when a concrete product requirement needs cross-request state. The current stateless Streamable HTTP implementation is intentional for this service.

-
license - not tested
-
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • Hotel booking MCP server. Search, book, and manage reservations across 250K+ properties worldwide.

  • AI marketplace — flights, tours, activities, transport & more via MCP. No auth required.

  • MCP server exposing the Backtest360 engine API as tools for AI agents.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Varma904/agentic-travel-recommendations'

If you have feedback or need assistance with the MCP directory API, please join our Discord server