Skip to content

MCP for AI agents

6 min read

The ferry-mcp server lets Claude and other AI agents work with the Ferry partner API through the Model Context Protocol: find voyages, view seat maps and prices, look up customers, read bookings, and request ticket cancellations. The server lives in the open-source repository ferry-vn/ferry-sdk, next to the SDK and the CLI.

With this server you can ask Claude things like “find voyages from Rạch Giá to Phú Quốc tomorrow and compare prices”, “are there adjacent seats for 4 people”, “what is the status of the booking for customer 0901…”, or “quote this booking but do not create it”. The server runs with your API key, so it sees exactly your organization’s data; see Authentication with an API key.

Besides tools, the server provides two ready-made prompts (plan_ferry_trip, check_booking) and a resource ferry://locations with the pier catalogue. The default environment is production; set FERRY_ENV=uat to use UAT, but UAT is not a sandbox: issuing tickets there still books real tickets and spends real money (see Environments).

Claude Desktop is the simplest route and needs no Node.js:

  1. Download the ferry-mcp.mcpb file from the latest release on GitHub and open it.
  2. Enter your API key.
  3. If you want it, allow write operations.

Claude Code needs Node.js 22.12 or later; npx fetches the @ferryvn/mcp package from npm when the server starts:

Terminal window
claude mcp add ferryvn -e FERRY_API_KEY=<key> -- npx -y @ferryvn/mcp

To allow write operations, add -e FERRY_MCP_ENABLE_WRITES=true to the claude mcp add command. If you installed it globally with npm install -g @ferryvn/mcp, you can replace npx -y @ferryvn/mcp with ferry-mcp. For other MCP applications, register the server the way that application expects: the command is npx with the arguments -y @ferryvn/mcp, the environment variable is FERRY_API_KEY, and FERRY_ENV is optional.

Read tools are always available:

ToolWhat it does
search_locations, list_routesFind piers (accent-insensitive) and view routes with their operators
search_voyages, get_voyage_calendarFind voyages by date or date range; see voyages per day
get_route_prices, get_seat_mapSelling prices of a route; free seats by seat type with prices and harbor fee
quote_bookingValidate a booking and return the total, creating nothing
find_customersLook up customers
get_booking, list_bookings, list_cancel_requestsRead a booking, the booking list, and cancellation requests
get_disruptions, get_account, list_vouchersDelays or cancellations affecting your bookings, balances and credit, vouchers

Write tools exist only when FERRY_MCP_ENABLE_WRITES=true (exactly this string):

ToolEffect
create_customerCreates a customer
create_bookingCreates a validated booking with its seats and passengers, Requested and unpaid; charges nothing
request_cancellationHolds the cancellation fee immediately and marks the tickets cancelled at once
  • Never exposed: ticket issuance, payments, transactions, withdrawals and top-ups, balance or credit adjustments, debt ledgers, voucher and price changes, and organization, user, API key, and webhook settings. A booking the agent creates stops at the unpaid state; issuing tickets is a human job through the Dashboard, the SDK, or the CLI.
  • A human confirms every write: if the application supports MCP confirmation dialogs, the server shows a dialog that the model cannot answer on your behalf. If not, the model must pass user_confirmed: true after showing you exactly what will happen. The dialog names the change: voyages, seats, passengers, and total; for a cancellation, the voyage and the passengers whose tickets are cancelled.
  • Strict arguments: unknown or misspelt arguments are rejected rather than silently dropped, because a dropped nationality would change the ticket type and price.
  • Write budget: at most FERRY_MCP_MAX_WRITES write calls per process, default 20, to stop a runaway agent loop.
  • Audit log: each write attempt is one JSON line on stderr, and is also appended to the file named in FERRY_MCP_AUDIT_FILE if set. The log holds ids and counts only, no passenger personal data.
  • Personal data in reads: bookings contain names, ID numbers, and phone numbers. The server’s instructions tell the model to share them only with the people concerned. If you do not want the agent to touch this data, hide the tools with FERRY_MCP_DISABLED_TOOLS, for example list_bookings,find_customers.
Environment variableMeaning
FERRY_API_KEYPartner API key; required unless you only look up piers and routes
FERRY_ENV, FERRY_BASE_URLproduction (default) or uat; or an explicit address
FERRY_MCP_ENABLE_WRITEStrue to enable the three write tools
FERRY_MCP_MAX_WRITESMaximum write calls per process (default 20)
FERRY_MCP_AUDIT_FILEFile to append write audit lines to
FERRY_MCP_DISABLED_TOOLSTools to leave out, comma-separated
FERRY_MCP_HTTP_HOST, FERRY_MCP_HTTP_TOKEN, FERRY_MCP_HTTP_ALLOW_WRITES, FERRY_MCP_ALLOWED_HOSTS, FERRY_MCP_ALLOWED_ORIGINSHTTP mode only, see the next section
FERRY_LANG, FERRY_TIMEOUT_MSOptional: message language and timeout

Invalid values stop the server at startup with a clear message.

By default the server runs over stdio for an application on the same machine. When you need to run it as a service, ferry-mcp --http --port 3333 serves http://127.0.0.1:3333/mcp (plus GET /healthz for health checks). The server acts with your API key, so this mode has protections:

  • DNS-rebinding protection: the Host header must be a loopback name and a browser Origin must be a loopback origin. Add more with FERRY_MCP_ALLOWED_HOSTS and FERRY_MCP_ALLOWED_ORIGINS.
  • Token required whenever anything beyond loopback can reach it: set FERRY_MCP_HTTP_TOKEN (16 characters or more) so every request must send Authorization: Bearer <token>. The server refuses to start without the token when it binds a non-loopback address, or when you allow a non-loopback host or origin (for example behind a reverse proxy).
  • Read-only by default: HTTP mode has no application confirmation dialog, so the write tools are left out even with FERRY_MCP_ENABLE_WRITES=true, unless you also set FERRY_MCP_HTTP_ALLOW_WRITES=true.
  • Request limits: POST only, with a 1 MB body limit.

The repository includes a Dockerfile for HTTP mode. Beyond a private network, put the service behind TLS.

MisconceptionRealityCorrect practice
Ask the agent to issue tickets or payThe server has no tool that does eitherIssue tickets through the Dashboard, SDK, or CLI after a human review
Enabling writes lets the agent book on its ownEvery write still needs human confirmationRead the dialog content carefully before agreeing
Expose the HTTP port to a network without a tokenThe server acts with your API keySet FERRY_MCP_HTTP_TOKEN and put it behind TLS
The agent can share customer data freelyRead results contain personal informationHide the read tools with FERRY_MCP_DISABLED_TOOLS when not needed
Trying on UAT is as safe as a sandboxIssuing tickets on UAT books real tickets and spends real moneyOnly issue on voyages agreed with Ferry; see Environments

Configuration details and the release file are in the MCP package README on GitHub and on the releases page. The raw HTTP reference is on Scalar UI.

What to look upWhere
Full tool list and environment variablesThe MCP package README on GitHub
Installer file for Claude DesktopThe latest release on GitHub
HTTP routes and schemas behind each toolScalar, groups Voyages, Bookings, Cancel Ticket Requests
PageWhen to read
TypeScript SDKCall the API from your own code
The ferry CLIManual work with confirmation when issuing tickets
Authentication with an API keyUnderstand the power of the key the server uses
The booking flowUnderstand the booking state create_booking produces