Skip to main content
Glama

Italian Travel Search

CI

An unofficial, read-only TypeScript service for live Italian coach and train searches through HTTP and Model Context Protocol interfaces.

The project hides provider-specific station catalogs, request formats, fare structures, and multi-leg responses behind one shared application core. Italo pricing initializes an anonymous technical session and temporary planning resource; customer login, fare selection, seat reservation, and payment are excluded.

IMPORTANT

This project is not affiliated with or endorsed by Itabus, Autoservizi Salemi, Autolinee Federico, MarinoBus, or FlixBus. Provider names and trademarks belong to their owners. Before enabling a live adapter, confirm that your use complies with the provider's terms, applicable law, and any required authorization. This repository does not grant data-extraction or redistribution rights.

Features

  • One response model for locations and journeys across different booking platforms.

  • Live adapters for five bus operators and two train operators.

  • Partial results when one provider fails and another succeeds.

  • MCP over local stdio or stateless Streamable HTTP at /mcp, with backward-compatible bus tools and separate rail tools.

  • Trenitalia and Italo station and journey searches directly from their live booking APIs.

  • Official links for continuing a search manually on provider sites.

  • Searches respect the requested departure day; city searches preserve all Itabus stops, with physical-stop names/addresses where available.

  • Rail warnings identify urban transfers excluded from the quoted fare.

  • Structured, provider-scoped errors instead of leaked upstream responses.

  • Finite upstream request timeouts and bounded asynchronous quote polling.

  • One-hour location caches and 60-second journey caches.

  • Opt-in immutable journey pagination with five-minute in-memory snapshots, shared by HTTP and MCP.

  • Per-client in-memory rate limiting at 60 requests per minute.

  • Offline tests with synthetic fixtures; the test suite never contacts provider sites.

  • A small runtime dependency surface: the official MCP SDK and Zod.

Related MCP server: soloway-mcp

Supported providers

Provider ID

Operator

Integration

Locations

Journeys

itabus

Itabus

Demandware endpoints

Live

Live

salemi

Autoservizi Salemi

Albatross tenant

Live

Live

federico

Autolinee Federico

Albatross tenant

Live

Live

marinobus

MarinoBus

Albatross tenant

Live

Live

flixbus

FlixBus

Global FlixBus search API

Live

Live

Public provider behavior and limitations are documented in the bus and train API guides below.

Train stations, journeys, prices and availability come directly from Trenitalia and Italo booking APIs. No timetable download, dataset, storage volume or initialization command is required. If one operator fails, its error is reported alongside the other operator's results. ViaggiaTreno and Italo live boards/standalone status remain independent; Italo quote sessions are cleaned up best-effort.

Requirements

  • Node.js 22.18 or newer

  • npm

Quick start

git clone https://github.com/Alexxanddr/italian-travel-search.git
cd italian-travel-search
npm install
cp .env.example .env
npm start

The server listens on http://127.0.0.1:3000 by default.

curl http://127.0.0.1:3000/health
curl http://127.0.0.1:3000/providers
curl http://127.0.0.1:3000/train-providers
curl 'http://127.0.0.1:3000/locations?query=Roma&provider=marinobus'

For journey searches, use a real future date:

curl 'http://127.0.0.1:3000/journeys?origin=Roma&destination=Milano&date=YYYY-MM-DD&adults=1&children=0&provider=marinobus'

YYYY-MM-DD is a placeholder and must be replaced before running the command.

HTTP interface

Method

Path

Purpose

GET

/health

Process liveness

GET

/providers

Adapter status and operational reason

GET

/train-providers

Train operators and configured schedule/price/live capabilities

GET

/locations

Provider location search

GET

/journeys

Normalized one-way journey search

GET

/train-locations

Trenitalia/Italo station search

GET

/train-journeys

Live rail journeys, availability and nullable prices

GET

/train-status

Standalone live status where enabled

GET

/station-board

Live arrivals/departures where enabled

POST

/mcp

Stateless MCP Streamable HTTP endpoint

/locations and /journeys accept an optional provider parameter. Without it, all providers are queried independently and successful data is preserved alongside provider-scoped errors.

The complete contract is split into the Bus API and Train API, with shared behavior indexed in HTTP API.

MCP server

The simplest MCP configuration runs the published package directly with npx:

{
  "mcpServers": {
    "italian-travel-search": {
      "command": "npx",
      "args": ["-y", "italian-travel-search@latest"]
    }
  }
}

Alternatively, build and start the local stdio server:

npm run build
npm run mcp:built

The existing list_providers, search_locations, and search_journeys bus tools remain compatible. Location and journey searches accept optional offset/limit; increment offset by limit until truncated is false to retrieve every page. Rail adds list_train_providers, search_train_locations, search_train_journeys, get_train_status, and get_station_board. Trenitalia and Italo price and live adapters are enabled; Italo live tools retain date-unverified semantics.

For exhaustive journey searches, start with snapshot: true and continue with only the returned searchId, offset and limit. This keeps prices, errors, order and totals identical across pages. Expired IDs require a new search. The same opt-in flow exists over HTTP; see stable pagination.

{
  "mcpServers": {
    "italian-travel-search": {
      "command": "node",
      "args": ["/absolute/path/to/italian-travel-search/dist/mcp/stdio.js"]
    }
  }
}

For a container or remote MCPHub deployment, start the HTTP process and point the client to http://italian-travel-search:3000/mcp. The container image is ghcr.io/alexxanddr/italian-travel-search:latest. No dataset initialization or persistent volume is needed.

See MCP interface for the authoritative tool contract, limits, errors, and development command.

Architecture

The application has four layers:

  1. HTTP and MCP adapters validate their transport-specific requests;

  2. shared validation and SearchService orchestrate searches and partial results;

  3. each provider adapter translates a remote protocol into domain types;

  4. the domain module owns normalized data and error contracts.

Provider adapters implement one shared interface, so callers do not know whether a result came from Itabus Demandware or an Albatross tenant. See Architecture for modules, seams, data flow, caches, and test strategy.

Development

npm run dev
npm run mcp
npm run check

npm run check runs the complete offline test suite, TypeScript type checking, and the configured lint check. To add an operator, follow Adding a provider; do not infer a new integration from HTML alone or copy credentials into fixtures.

Deployment

The default server deliberately binds to loopback and keeps rate-limit and cache state in process memory. Read Operations before placing it behind a reverse proxy or running multiple replicas.

Project documentation

  • HTTP API — shared behavior and error contract.

  • Bus API — bus providers, locations, journeys, and examples.

  • Train API — rail schedules, live prices/status, provenance, and examples.

  • MCP interface — authoritative tools, results, limits, and transport contract.

  • Architecture — modules, seams, flows, and design decisions.

  • Adding a provider — research, implementation, and verification checklist.

  • Operations — configuration, deployment constraints, and failure handling.

  • Glossary — stable project terminology.

  • Contributing — local workflow and pull-request expectations.

  • Security policy — private vulnerability reporting and supported versions.

  • Agent instructions — compact routing instructions for coding agents.

Scope and limitations

  • Searches are one-way only.

  • Availability is a provider-reported signal, not a seat or price guarantee.

  • Prices and schedules can change between search and purchase; rail results expose live observation and source metadata. The legacy field name timetableSource is retained for compatibility.

  • Caches and rate limits reset when the process restarts and are not shared across replicas.

  • Upstream interfaces are unofficial and can change without notice.

  • Both interfaces intentionally exclude accounts, bookings, payments, and CAPTCHA handling.

Always complete and confirm a purchase on the official provider site linked by bookingUrl.

Contributing and security

Contributions are welcome; start with CONTRIBUTING.md. Report vulnerabilities privately according to SECURITY.md, not through a public issue.

License

Source code is available under the MIT License. The license covers this repository's code, not third-party data, provider trademarks, or access rights to upstream systems.

Available Tools

8 tools
get_station_boardGet train station boardB
Read-onlyIdempotent

Get an operator station board with its observation timestamp. Live boards may be unavailable independently of schedules.

NameRequiredDescriptionDefault
providerYes
directionYes
stationIdYes
stationNameYes

Output Schema

NameRequiredDescription
sourceYes
stationYes
providerYes
servicesYes
directionYes
observedAtYes

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, openWorld and non-destructive, so the safety profile is covered. The description adds genuine behavioral value by warning that live boards can be unavailable independently of schedules, but says nothing about how failure is surfaced, rate limits, or freshness of the data.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, zero filler, with the core action and its scope front-loaded. Every clause carries information the agent cannot get from the structured fields.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be described, and the availability caveat is useful. But for a tool with four required parameters at 0% schema coverage and no explanation of the stationId/stationName pairing, the description leaves a real invocation gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the description names none of the four required parameters. The unusual requirement of both stationId and stationName — and where to obtain them (e.g. search_train_locations) — is undocumented, so an agent gets no help resolving these inputs.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Get an operator station board') plus a scope nuance (observation timestamp). It is distinguishable from get_train_status and search_train_journeys by being station-level rather than train- or journey-level, though it never says so explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The sentence 'Live boards may be unavailable independently of schedules' implies this tool covers live station boards and is separate from schedule queries, which hints at usage. However, no sibling is named and there is no explicit when-to-use/when-not-to-use guidance despite six related tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_train_statusGet train statusB
Read-onlyIdempotent

Get live status using operator, train number, service date, and origin identity. Live status may be unavailable independently of schedules.

NameRequiredDescriptionDefault
originYes
providerYes
serviceDateYes
trainNumberYes

Output Schema

NameRequiredDescription
sourceYes
cancelledNo
freshnessYes
observedAtYes
delayMinutesNo

TDQS

B3.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive and open-world, so safety is covered; the description adds a genuinely useful behavioral caveat that live status data may be missing regardless of schedule availability — important for an agent deciding whether to retry or fall back. It does not mention auth, rate limits, or refresh cadence.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the action and inputs, with no filler. The second sentence earns its place as a real caveat rather than padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-value explanation is not required, and annotations cover the safety profile. What remains missing is input format guidance (date and origin identifier conventions) and the expected behavior/response when live status is unavailable — a notable gap for a 4-parameter, zero-coverage schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% across four required parameters. The description only restates the parameter names in prose ("operator, train number, service date, and origin identity") and adds no format guidance for serviceDate, trainNumber, or origin, and does not mention the trenitalia/italo enum restriction. "Origin identity" vaguely implies an identifier but leaves the code-vs-name question open.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb+resource ("Get live status") and enumerates the identifying inputs, which clearly separates it from the search_* siblings that return schedules rather than live state. It stops short of naming an alternative tool outright, but the retrieved artifact is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: the phrase "Live status may be unavailable independently of schedules" hints that schedules are a separate concern, but the description never says when to prefer this over search_train_journeys or get_station_board, nor what to do when status is unavailable. No exclusions or prerequisites are given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_providersList Italian bus providersA
Read-onlyIdempotent

BUS ONLY. List configured bus/coach providers and their status. Never use this tool for trains or rail; use list_train_providers instead.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
providersYes

TDQS

A4.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false and openWorldHint=true, so the safety profile is fully covered by structured data. The description adds only the modal exclusivity rule, which is really routing guidance, and says nothing about what "status" entails or any auth/config prerequisites. Adequate against the lower bar set by annotations, but no extra behavioral value.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, zero filler, with the most important constraint ("BUS ONLY") front-loaded in capitals. Nothing could be removed without losing routing information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With no parameters, full annotation coverage and an output schema covering return values, the description supplies nearly everything an agent needs. The only small gap is that "status" is never characterized, though the output schema plausibly covers it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, so there is nothing for the description to disambiguate and the baseline of 4 applies. The description correctly implies a no-argument enumeration by saying "list configured ... providers".

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("List configured bus/coach providers and their status") and immediately constrains domain with "BUS ONLY". It explicitly separates itself from list_train_providers, so an agent can distinguish it from siblings without reading any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit prohibition ("Never use this tool for trains or rail") and names the correct alternative (list_train_providers). Both the when and the when-not are stated with no inference required.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_train_providersList Italian train providersA
Read-onlyIdempotent

TRAINS ONLY. Use for requests involving trains or rail. Lists train operators and whether schedule, price, live-status, and station-board capabilities are enabled.

NameRequiredDescriptionDefault

No parameters

Output Schema

NameRequiredDescription
providersYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, non-destructive and openWorld, so the safety profile is covered. The description adds value by disclosing that results are operators annotated with capability flags rather than raw schedule data — useful framing that keeps the agent from expecting journey results.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences with the domain restriction front-loaded, which is the right ordering. There is mild redundancy between "TRAINS ONLY" and "requests involving trains or rail", but nothing that wastes real reading effort.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With zero parameters, rich annotations, and an output schema handling return values, the description is nearly complete. The only substantive gap is that it never distinguishes this provider-listing call from search_train_journeys or get_train_status, which matters given seven siblings.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool takes zero parameters, which is the baseline-4 case; there is nothing the description could clarify about inputs. Schema description coverage is 100% and there are no required or enum parameters to disambiguate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ("Lists train operators") plus the payload content (capability enablement for schedule, price, live-status, station-board). The "TRAINS ONLY" qualifier separates it from the sibling list_providers, which presumably spans other transport modes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

"Use for requests involving trains or rail" gives a domain-level routing cue but no when-not and no alternatives. An agent asking for an actual itinerary could still plausibly land here instead of search_train_journeys or get_train_status, since neither sibling is named or excluded.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_journeysSearch Italian bus journeysA
Read-onlyIdempotent

BUS ONLY. Search one-way bus/coach journeys. Never use for trains or rail; use search_train_journeys instead. Results include official links for continuing manually on provider sites. For stable pagination start with snapshot:true; continue using only searchId, offset and limit.

NameRequiredDescriptionDefault
dateNoReal future date in YYYY-MM-DD format
limitNo
adultsNo
offsetNoResult offset; add limit while truncated is true
originNoExact shared location name for aggregate searches, or a provider-specific ID when provider is set
childrenNo
providerNoProvider ID; omit to search every provider
searchIdNo
snapshotNo
destinationNoExact shared location name for aggregate searches, or a provider-specific ID when provider is set

Output Schema

NameRequiredDescription
totalYes
errorsYes
journeysYes
searchIdNo
createdAtNo
expiresAtNo
truncatedYes

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, openWorld and non-destructive, so the safety bar is met without prose. The description adds genuine non-schema behavior: results carry official provider links for manual continuation, and snapshot:true is required for stable pagination. It does not mention rate limits or result ordering, which keeps it below a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four short sentences, zero filler, with the mode restriction front-loaded and the alternative and pagination protocol following in priority order. Every clause carries operational information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With 10 parameters, partial schema coverage and an output schema that already documents return values, the description supplies exactly the missing operational pieces: the snapshot/searchId pagination protocol and the manual-continuation nature of results. Passenger and date parameter handling is left implicit, a minor gap for a zero-required-parameter tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is only 50%, so the description must compensate—and it does for the trickiest fields: snapshot:true semantics and the constraint that continuation pages use only searchId, offset and limit (not the original search criteria). adults, children and the limit maximum are left to the schema, so not full marks.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

"BUS ONLY. Search one-way bus/coach journeys" gives a specific verb plus resource and an explicit transport-mode scope. It also names the sibling it must not be confused with (search_train_journeys), so an agent can discriminate it without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit negative guidance ("Never use for trains or rail; use search_train_journeys instead") plus a positive workflow rule for pagination (start with snapshot:true, then continue using only searchId, offset and limit). Both when-to-use and when-not-to-use are stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_locationsSearch Italian bus locationsA
Read-onlyIdempotent

BUS ONLY. Find bus/coach location IDs and exact names. Never use for train stations; use search_train_locations instead. For an aggregate bus search, reuse exact names shared across providers.

NameRequiredDescriptionDefault
limitNo
queryYesPartial city, stop, or location name
offsetNoResult offset; add limit while truncated is true
providerNoProvider ID; omit to search every provider

Output Schema

NameRequiredDescription
totalYes
errorsYes
locationsYes
truncatedYes

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent and openWorld, so the safety profile is covered; the description's real contribution is the hard domain boundary (bus only) and the cross-provider name-matching tip. It says nothing, however, about result volume, truncation behavior, or how provider scoping affects results — details an open-world search tool would benefit from.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences, zero filler, with the most decisive constraint ('BUS ONLY') front-loaded. The exclusion and the alternative arrive before any optional advice.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and rich annotations, the description only needs to convey scope and routing, which it does fully. It lacks only minor operational detail (e.g. what a returned location ID looks like, how provider filtering changes the result set) to be airtight for a 4-parameter search tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 75% and the schema already describes query as a partial name and offset as truncation-driven pagination. The description does not explain the provider or limit parameters, so it adds no meaning beyond the schema; baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Find bus/coach location IDs and exact names') and immediately scopes it with 'BUS ONLY', making it distinguishable from the train siblings at a glance. The output artifact (location IDs and exact names) is named, not just the action.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the exclusion ('Never use for train stations') and routes the agent to the correct alternative (search_train_locations). It also adds a positive usage rule for the aggregate case ('reuse exact names shared across providers'), so both when-to-use and when-not-to-use are covered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_train_journeysSearch Italian train journeysA
Read-onlyIdempotent

TRAINS ONLY. Requires origin, destination, and date. When the user states passenger counts, always pass adults and children explicitly. Each price is the total for those passengers; never infer discounts or convert it to a per-person price. For stable pagination start with snapshot:true; continue using only searchId, offset and limit.

NameRequiredDescriptionDefault
dateNo
limitNo
adultsNoNumber of adult passengers; pass explicitly when stated by the user
offsetNoResult offset; paginate by adding the previous limit when truncated is true
originNo
childrenNoNumber of child passengers; pass explicitly when stated by the user, including zero
providerNo
searchIdNo
snapshotNo
destinationNo

Output Schema

NameRequiredDescription
totalYes
errorsYes
journeysYes
searchIdNo
createdAtNo
expiresAtNo
truncatedYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/idempotent/openWorld, so the bar is lower, yet the description adds real domain behavior: each price is the total for the passenger set and must not be inferred as per-person or discounted. The snapshot/searchId continuation protocol is also a meaningful behavioral disclosure absent from annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four tight sentences, front-loaded with the 'TRAINS ONLY' constraint and the required inputs, followed by the two highest-risk pitfalls (passenger counts and price interpretation) and the pagination recipe. No filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values needn't be described, and the pagination contract is fully specified. Remaining gaps are minor: no mention of provider selection (trenitalia vs italo) or expected date format, which an agent would need to guess for a 10-parameter open-world search.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With only 30% schema coverage, the description carries weight and does so for the least obvious parameters: it explains adults/children semantics and how snapshot, searchId, offset, and limit interact for pagination. It adds nothing about provider (the one enum), date format, or origin/destination resolution, leaving some gaps.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with 'TRAINS ONLY' and names the required inputs (origin, destination, date), making it clear this is a rail-journey search rather than the generic sibling search_journeys. It stops short of explicitly naming that sibling, so the differentiation is implied rather than stated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives concrete operative guidance: pass adults/children explicitly when the user states counts, and use snapshot:true first for stable pagination then continue with only searchId/offset/limit. It doesn't state when to prefer this tool over search_journeys or list_providers, so it falls short of full when/when-not coverage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_train_locationsSearch Italian train stationsA
Read-onlyIdempotent

TRAINS ONLY. Use for every request involving trains or rail to find operator-aware stations. Prefer names across operators; station IDs are operator-specific.

NameRequiredDescriptionDefault
limitNo
queryYes
offsetNo
providerNo

Output Schema

NameRequiredDescription
totalYes
errorsYes
locationsYes
truncatedYes

TDQS

A3.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), so the description earns credit for adding a real gotcha: station IDs are operator-specific while names span operators. That is a substantive behavioral fact an agent would otherwise learn only by failure. It does not mention result size or pagination behaviour.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three short sentences with the scope constraint front-loaded in caps, so an agent reads the key discriminator first. Terse enough that the missing parameter guidance is an omission rather than clutter.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values needn't be explained, and the read-only nature removes mutation concerns. Against 4 parameters with 0% schema coverage, however, the description leaves pagination and provider filtering entirely to inference, which is a meaningful gap for a search tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description carries the full burden. It partially compensates for the query parameter by steering toward station names and warning that IDs are operator-specific, but limit, offset, and the provider enum receive no explanation at all, and the pagination parameters are never mentioned.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('find operator-aware stations') with an emphasised scope marker ('TRAINS ONLY') that separates it from the generic search_locations sibling. It stops short of naming that sibling explicitly, so the differentiation is implied rather than spelled out.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

'Use for every request involving trains or rail' gives a clear selection rule, and 'TRAINS ONLY' implicitly excludes non-rail lookups. The caveat about station IDs being operator-specific is actionable, but no alternative tool is named for the non-train case.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 4 tool updatesv0.3.6
    • Changedsearch_journeys14 fields changed
      • removedInput schema / properties / adults / default
        Removed value: -1
      • removedInput schema / properties / children / default
        Removed value: -0
      • addedInput schema / properties / offset
        Added value: +{
        +  "default": 0,
        +  "description": "Result offset; add limit while truncated is true",
        +  "maximum": 9007199254740991,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • addedInput schema / properties / searchId
        Added value: +{
        +  "type": "string"
        +}
      • addedInput schema / properties / snapshot
        Added value: +{
        +  "default": false,
        +  "type": "boolean"
        +}
      • removedInput schema / required
        Removed value: -[
        -  "origin",
        -  "destination",
        -  "date"
        -]
      • addedOutput schema / properties / createdAt
        Added value: +{
        +  "type": "string"
        +}
      • changedOutput schema / properties / errors / items / properties / code / enum
        Previous value: -[
        -  "INVALID_INPUT",
        -  "UNKNOWN_LOCATION",
        -  "NO_JOURNEYS",
        -  "PROVIDER_UNAVAILABLE",
        -  "INCOMPATIBLE_RESPONSE",
        -  "RATE_LIMITED",
        -  "AMBIGUOUS_LOCATION",
        -  "COVERAGE_UNAVAILABLE",
        -  "QUOTE_UNAVAILABLE",
        -  "LIVE_STATUS_UNAVAILABLE",
        -  "TRAIN_NOT_FOUND"
        -]New value: +[
        +  "SEARCH_EXPIRED",
        +  "SEARCH_TOO_LARGE",
        +  "INVALID_INPUT",
        +  "UNKNOWN_LOCATION",
        +  "NO_JOURNEYS",
        +  "PROVIDER_UNAVAILABLE",
        +  "INCOMPATIBLE_RESPONSE",
        +  "RATE_LIMITED",
        +  "AMBIGUOUS_LOCATION",
        +  "COVERAGE_UNAVAILABLE",
        +  "QUOTE_UNAVAILABLE",
        +  "LIVE_STATUS_UNAVAILABLE",
        +  "TRAIN_NOT_FOUND"
        +]
      • addedOutput schema / properties / expiresAt
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / journeys / items / properties / destination / properties / address
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / journeys / items / properties / destination / properties / city
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / journeys / items / properties / origin / properties / address
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / journeys / items / properties / origin / properties / city
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / searchId
        Added value: +{
        +  "type": "string"
        +}
    • Changedsearch_locations2 fields changed
      • addedInput schema / properties / offset
        Added value: +{
        +  "default": 0,
        +  "description": "Result offset; add limit while truncated is true",
        +  "maximum": 9007199254740991,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • changedOutput schema / properties / errors / items / properties / code / enum
        Previous value: -[
        -  "INVALID_INPUT",
        -  "UNKNOWN_LOCATION",
        -  "NO_JOURNEYS",
        -  "PROVIDER_UNAVAILABLE",
        -  "INCOMPATIBLE_RESPONSE",
        -  "RATE_LIMITED",
        -  "AMBIGUOUS_LOCATION",
        -  "COVERAGE_UNAVAILABLE",
        -  "QUOTE_UNAVAILABLE",
        -  "LIVE_STATUS_UNAVAILABLE",
        -  "TRAIN_NOT_FOUND"
        -]New value: +[
        +  "SEARCH_EXPIRED",
        +  "SEARCH_TOO_LARGE",
        +  "INVALID_INPUT",
        +  "UNKNOWN_LOCATION",
        +  "NO_JOURNEYS",
        +  "PROVIDER_UNAVAILABLE",
        +  "INCOMPATIBLE_RESPONSE",
        +  "RATE_LIMITED",
        +  "AMBIGUOUS_LOCATION",
        +  "COVERAGE_UNAVAILABLE",
        +  "QUOTE_UNAVAILABLE",
        +  "LIVE_STATUS_UNAVAILABLE",
        +  "TRAIN_NOT_FOUND"
        +]
    • Changedsearch_train_journeys13 fields changed
      • removedInput schema / properties / adults / default
        Removed value: -1
      • removedInput schema / properties / children / default
        Removed value: -0
      • addedInput schema / properties / searchId
        Added value: +{
        +  "type": "string"
        +}
      • addedInput schema / properties / snapshot
        Added value: +{
        +  "default": false,
        +  "type": "boolean"
        +}
      • removedInput schema / required
        Removed value: -[
        -  "origin",
        -  "destination",
        -  "date"
        -]
      • addedOutput schema / properties / createdAt
        Added value: +{
        +  "type": "string"
        +}
      • changedOutput schema / properties / errors / items / properties / code / enum
        Previous value: -[
        -  "INVALID_INPUT",
        -  "UNKNOWN_LOCATION",
        -  "NO_JOURNEYS",
        -  "PROVIDER_UNAVAILABLE",
        -  "INCOMPATIBLE_RESPONSE",
        -  "RATE_LIMITED",
        -  "AMBIGUOUS_LOCATION",
        -  "COVERAGE_UNAVAILABLE",
        -  "QUOTE_UNAVAILABLE",
        -  "LIVE_STATUS_UNAVAILABLE",
        -  "TRAIN_NOT_FOUND"
        -]New value: +[
        +  "SEARCH_EXPIRED",
        +  "SEARCH_TOO_LARGE",
        +  "INVALID_INPUT",
        +  "UNKNOWN_LOCATION",
        +  "NO_JOURNEYS",
        +  "PROVIDER_UNAVAILABLE",
        +  "INCOMPATIBLE_RESPONSE",
        +  "RATE_LIMITED",
        +  "AMBIGUOUS_LOCATION",
        +  "COVERAGE_UNAVAILABLE",
        +  "QUOTE_UNAVAILABLE",
        +  "LIVE_STATUS_UNAVAILABLE",
        +  "TRAIN_NOT_FOUND"
        +]
      • addedOutput schema / properties / expiresAt
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / journeys / items / properties / legs / items / properties / description
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / journeys / items / properties / legs / items / properties / includedInPrice
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / journeys / items / properties / legs / items / properties / transportMode
        Added value: +{
        +  "const": "urban_transfer",
        +  "type": "string"
        +}
      • addedOutput schema / properties / journeys / items / properties / warnings
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / searchId
        Added value: +{
        +  "type": "string"
        +}
    • Changedsearch_train_locations2 fields changed
      • addedInput schema / properties / offset
        Added value: +{
        +  "default": 0,
        +  "maximum": 9007199254740991,
        +  "minimum": 0,
        +  "type": "integer"
        +}
      • changedOutput schema / properties / errors / items / properties / code / enum
        Previous value: -[
        -  "INVALID_INPUT",
        -  "UNKNOWN_LOCATION",
        -  "NO_JOURNEYS",
        -  "PROVIDER_UNAVAILABLE",
        -  "INCOMPATIBLE_RESPONSE",
        -  "RATE_LIMITED",
        -  "AMBIGUOUS_LOCATION",
        -  "COVERAGE_UNAVAILABLE",
        -  "QUOTE_UNAVAILABLE",
        -  "LIVE_STATUS_UNAVAILABLE",
        -  "TRAIN_NOT_FOUND"
        -]New value: +[
        +  "SEARCH_EXPIRED",
        +  "SEARCH_TOO_LARGE",
        +  "INVALID_INPUT",
        +  "UNKNOWN_LOCATION",
        +  "NO_JOURNEYS",
        +  "PROVIDER_UNAVAILABLE",
        +  "INCOMPATIBLE_RESPONSE",
        +  "RATE_LIMITED",
        +  "AMBIGUOUS_LOCATION",
        +  "COVERAGE_UNAVAILABLE",
        +  "QUOTE_UNAVAILABLE",
        +  "LIVE_STATUS_UNAVAILABLE",
        +  "TRAIN_NOT_FOUND"
        +]
  2. 7 tool updatesv0.3.2
    • Addedget_station_board
    • Addedget_train_status
    • Addedlist_train_providers
    • Changedsearch_journeys4 fields changed
      • changedInput schema / properties / destination / description
        Previous value: -"Exact location name or ID returned by search_locations"New value: +"Exact shared location name for aggregate searches, or a provider-specific ID when provider is set"
      • changedInput schema / properties / limit / default
        Previous value: -10New value: +50
      • changedInput schema / properties / origin / description
        Previous value: -"Exact location name or ID returned by search_locations"New value: +"Exact shared location name for aggregate searches, or a provider-specific ID when provider is set"
      • changedOutput schema / properties / errors / items / properties / code / enum
        Previous value: -[
        -  "INVALID_INPUT",
        -  "UNKNOWN_LOCATION",
        -  "NO_JOURNEYS",
        -  "PROVIDER_UNAVAILABLE",
        -  "INCOMPATIBLE_RESPONSE",
        -  "RATE_LIMITED"
        -]New value: +[
        +  "INVALID_INPUT",
        +  "UNKNOWN_LOCATION",
        +  "NO_JOURNEYS",
        +  "PROVIDER_UNAVAILABLE",
        +  "INCOMPATIBLE_RESPONSE",
        +  "RATE_LIMITED",
        +  "AMBIGUOUS_LOCATION",
        +  "COVERAGE_UNAVAILABLE",
        +  "QUOTE_UNAVAILABLE",
        +  "LIVE_STATUS_UNAVAILABLE",
        +  "TRAIN_NOT_FOUND"
        +]
    • Changedsearch_locations2 fields changed
      • changedInput schema / properties / limit / default
        Previous value: -10New value: +50
      • changedOutput schema / properties / errors / items / properties / code / enum
        Previous value: -[
        -  "INVALID_INPUT",
        -  "UNKNOWN_LOCATION",
        -  "NO_JOURNEYS",
        -  "PROVIDER_UNAVAILABLE",
        -  "INCOMPATIBLE_RESPONSE",
        -  "RATE_LIMITED"
        -]New value: +[
        +  "INVALID_INPUT",
        +  "UNKNOWN_LOCATION",
        +  "NO_JOURNEYS",
        +  "PROVIDER_UNAVAILABLE",
        +  "INCOMPATIBLE_RESPONSE",
        +  "RATE_LIMITED",
        +  "AMBIGUOUS_LOCATION",
        +  "COVERAGE_UNAVAILABLE",
        +  "QUOTE_UNAVAILABLE",
        +  "LIVE_STATUS_UNAVAILABLE",
        +  "TRAIN_NOT_FOUND"
        +]
    • Addedsearch_train_journeys
    • Addedsearch_train_locations
  3. 3 tool updatesv0.1.2
    • First observedlist_providers
    • First observedsearch_journeys
    • First observedsearch_locations

TDQS

A3.9/5.0

Scored across 8 tools

Disambiguation5/5

Each tool has a clearly distinct scope, reinforced by explicit BUS ONLY / TRAINS ONLY markers and warnings against cross-use. Paired tools like search_locations vs search_train_locations and list_providers vs list_train_providers are easy to tell apart.

Naming Consistency4/5

All names follow a snake_case verb_noun pattern, so the set is highly readable. Minor inconsistency appears in qualifiers: some train-specific tools include train, but get_station_board does not, and list_providers is generic while list_train_providers is explicit.

Tool Count5/5

Eight tools is well-scoped for a bus search server that also exposes train search/status capabilities. Each tool has a clear role and no obvious redundancy.

Completeness4/5

The surface covers providers, locations, journey search, station boards, and live train status, which is strong for a search aggregator. Minor gaps exist around inspecting a specific journey or deeper station/stop details, but core workflows are covered.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    Live intercity bus-trip search across Ukraine and Europe — real-time prices, seats, carriers, cheapest-day-of-month calendar, and trip details with passenger discounts. Read-only, no API key; also available as a hosted remote endpoint at https://mcp.soloway.com.ua/mcp.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables searching for buses and trains, and when direct routes are unavailable, it provides the underlying data needed to assemble multi-leg itineraries with transfers.
    2 npm
    ISC
  • A
    license
    A
    quality
    B
    maintenance
    Enables MCP-compatible AI agents to search live Virail transport connections across trains, buses, flights, ferries, and carpooling, returning real prices, schedules, and booking links.
    4
    1
    MIT