italian-bus-search
Search Italian bus/coach and train travel data through a read-only MCP/HTTP service.
List bus providers and their status, and train operators with enabled schedule, price, live-status, and station-board capabilities.
Search bus stops/locations and one-way bus journeys across providers, returning prices, availability, and official booking links.
Search train stations and one-way train journeys across Trenitalia and Italo, returning schedules, legs, prices, availability, live status, and booking links.
Get live train status by operator, train number, service date, and origin.
Get station arrival/departure boards for enabled operators.
Use limits/truncation signals and train-journey offsets for paged results; get partial results with provider-scoped errors when one provider fails.
It does not handle accounts, bookings, payments, or CAPTCHA.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@italian-bus-searchFind one-way buses from Rome to Milan on 15 June"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Italian Travel Search
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.
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 | Demandware endpoints | Live | Live |
| Autoservizi Salemi | Albatross tenant | Live | Live |
| Autolinee Federico | Albatross tenant | Live | Live |
| MarinoBus | Albatross tenant | Live | Live |
| 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 startThe 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 |
|
| Process liveness |
|
| Adapter status and operational reason |
|
| Train operators and configured schedule/price/live capabilities |
|
| Provider location search |
|
| Normalized one-way journey search |
|
| Trenitalia/Italo station search |
|
| Live rail journeys, availability and nullable prices |
|
| Standalone live status where enabled |
|
| Live arrivals/departures where enabled |
|
| 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:builtThe 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:
HTTP and MCP adapters validate their transport-specific requests;
shared validation and
SearchServiceorchestrate searches and partial results;each provider adapter translates a remote protocol into domain types;
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 checknpm 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
timetableSourceis 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 toolsget_station_boardGet train station boardBRead-onlyIdempotent
Get an operator station board with its observation timestamp. Live boards may be unavailable independently of schedules.
| Name | Required | Description | Default |
|---|---|---|---|
| provider | Yes | ||
| direction | Yes | ||
| stationId | Yes | ||
| stationName | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| source | Yes | |
| station | Yes | |
| provider | Yes | |
| services | Yes | |
| direction | Yes | |
| observedAt | Yes |
TDQS
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.
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.
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.
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.
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.
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 statusBRead-onlyIdempotent
Get live status using operator, train number, service date, and origin identity. Live status may be unavailable independently of schedules.
| Name | Required | Description | Default |
|---|---|---|---|
| origin | Yes | ||
| provider | Yes | ||
| serviceDate | Yes | ||
| trainNumber | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| source | Yes | |
| cancelled | No | |
| freshness | Yes | |
| observedAt | Yes | |
| delayMinutes | No |
TDQS
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.
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.
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.
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.
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.
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 providersARead-onlyIdempotent
BUS ONLY. List configured bus/coach providers and their status. Never use this tool for trains or rail; use list_train_providers instead.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| providers | Yes |
TDQS
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.
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.
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.
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.
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.
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 providersARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| providers | Yes |
TDQS
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.
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.
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.
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.
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.
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 journeysARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | Real future date in YYYY-MM-DD format | |
| limit | No | ||
| adults | No | ||
| offset | No | Result offset; add limit while truncated is true | |
| origin | No | Exact shared location name for aggregate searches, or a provider-specific ID when provider is set | |
| children | No | ||
| provider | No | Provider ID; omit to search every provider | |
| searchId | No | ||
| snapshot | No | ||
| destination | No | Exact shared location name for aggregate searches, or a provider-specific ID when provider is set |
Output Schema
| Name | Required | Description |
|---|---|---|
| total | Yes | |
| errors | Yes | |
| journeys | Yes | |
| searchId | No | |
| createdAt | No | |
| expiresAt | No | |
| truncated | Yes |
TDQS
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.
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.
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.
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.
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.
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 locationsARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes | Partial city, stop, or location name | |
| offset | No | Result offset; add limit while truncated is true | |
| provider | No | Provider ID; omit to search every provider |
Output Schema
| Name | Required | Description |
|---|---|---|
| total | Yes | |
| errors | Yes | |
| locations | Yes | |
| truncated | Yes |
TDQS
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.
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.
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.
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.
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.
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 journeysARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| date | No | ||
| limit | No | ||
| adults | No | Number of adult passengers; pass explicitly when stated by the user | |
| offset | No | Result offset; paginate by adding the previous limit when truncated is true | |
| origin | No | ||
| children | No | Number of child passengers; pass explicitly when stated by the user, including zero | |
| provider | No | ||
| searchId | No | ||
| snapshot | No | ||
| destination | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| total | Yes | |
| errors | Yes | |
| journeys | Yes | |
| searchId | No | |
| createdAt | No | |
| expiresAt | No | |
| truncated | Yes |
TDQS
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.
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.
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.
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.
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.
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 stationsARead-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.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes | ||
| offset | No | ||
| provider | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| total | Yes | |
| errors | Yes | |
| locations | Yes | |
| truncated | Yes |
TDQS
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.
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.
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.
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.
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.
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.
4 tool updates
v0.3.6- Changed
search_journeys14 fields changed- removed
Input schema / properties / adults / defaultRemoved value: -1 - removed
Input schema / properties / children / defaultRemoved value: -0 - added
Input schema / properties / offsetAdded value: +{ + "default": 0, + "description": "Result offset; add limit while truncated is true", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" +} - added
Input schema / properties / searchIdAdded value: +{ + "type": "string" +} - added
Input schema / properties / snapshotAdded value: +{ + "default": false, + "type": "boolean" +} - removed
Input schema / requiredRemoved value: -[ - "origin", - "destination", - "date" -] - added
Output schema / properties / createdAtAdded value: +{ + "type": "string" +} - changed
Output schema / properties / errors / items / properties / code / enumPrevious 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" +] - added
Output schema / properties / expiresAtAdded value: +{ + "type": "string" +} - added
Output schema / properties / journeys / items / properties / destination / properties / addressAdded value: +{ + "type": "string" +} - added
Output schema / properties / journeys / items / properties / destination / properties / cityAdded value: +{ + "type": "string" +} - added
Output schema / properties / journeys / items / properties / origin / properties / addressAdded value: +{ + "type": "string" +} - added
Output schema / properties / journeys / items / properties / origin / properties / cityAdded value: +{ + "type": "string" +} - added
Output schema / properties / searchIdAdded value: +{ + "type": "string" +}
- Changed
search_locations2 fields changed- added
Input schema / properties / offsetAdded value: +{ + "default": 0, + "description": "Result offset; add limit while truncated is true", + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" +} - changed
Output schema / properties / errors / items / properties / code / enumPrevious 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" +]
- Changed
search_train_journeys13 fields changed- removed
Input schema / properties / adults / defaultRemoved value: -1 - removed
Input schema / properties / children / defaultRemoved value: -0 - added
Input schema / properties / searchIdAdded value: +{ + "type": "string" +} - added
Input schema / properties / snapshotAdded value: +{ + "default": false, + "type": "boolean" +} - removed
Input schema / requiredRemoved value: -[ - "origin", - "destination", - "date" -] - added
Output schema / properties / createdAtAdded value: +{ + "type": "string" +} - changed
Output schema / properties / errors / items / properties / code / enumPrevious 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" +] - added
Output schema / properties / expiresAtAdded value: +{ + "type": "string" +} - added
Output schema / properties / journeys / items / properties / legs / items / properties / descriptionAdded value: +{ + "type": "string" +} - added
Output schema / properties / journeys / items / properties / legs / items / properties / includedInPriceAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / journeys / items / properties / legs / items / properties / transportModeAdded value: +{ + "const": "urban_transfer", + "type": "string" +} - added
Output schema / properties / journeys / items / properties / warningsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / searchIdAdded value: +{ + "type": "string" +}
- Changed
search_train_locations2 fields changed- added
Input schema / properties / offsetAdded value: +{ + "default": 0, + "maximum": 9007199254740991, + "minimum": 0, + "type": "integer" +} - changed
Output schema / properties / errors / items / properties / code / enumPrevious 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" +]
7 tool updates
v0.3.2- Added
get_station_board - Added
get_train_status - Added
list_train_providers - Changed
search_journeys4 fields changed- changed
Input schema / properties / destination / descriptionPrevious 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" - changed
Input schema / properties / limit / defaultPrevious value: -10New value: +50 - changed
Input schema / properties / origin / descriptionPrevious 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" - changed
Output schema / properties / errors / items / properties / code / enumPrevious 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" +]
- Changed
search_locations2 fields changed- changed
Input schema / properties / limit / defaultPrevious value: -10New value: +50 - changed
Output schema / properties / errors / items / properties / code / enumPrevious 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" +]
- Added
search_train_journeys - Added
search_train_locations
3 tool updates
v0.1.2- First observed
list_providers - First observed
search_journeys - First observed
search_locations
TDQS
Scored across 8 tools
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.
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.
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.
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
Related MCP Connectors
AI marketplace — flights, tours, activities, transport & more via MCP. No auth required.
The Ferryhopper MCP server is a connector for LLMs and AI Agents in maritime travel that exposes ferry routes, schedules, and booking options. It enables AI assistants to search ports and connections across 33 countries and 190+ ferry operators, provide real-time ferry itineraries with indicative prices, and assist users with planning island-hopping or multi-leg journeys by processing natural language queries about ferry times, passenger counts, and travel durations.
Search and compare flight offers through a cache-aware Streamable HTTP MCP server for AI agents.
Live Google Maps business search, review, and photo data for AI agents over MCP.
Related MCP Servers
- AlicenseAqualityDmaintenanceReal-time train, bus, ferry and private transfer prices between 40+ Italian cities4MIT
- AlicenseNot gradedqualityCmaintenanceLive 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
- AlicenseNot gradedqualityBmaintenanceEnables 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 npmISC

virail-mcpofficial
AlicenseAqualityBmaintenanceEnables MCP-compatible AI agents to search live Virail transport connections across trains, buses, flights, ferries, and carpooling, returning real prices, schedules, and booking links.41MIT