Repository navigation
Releases: ferry-vn/ferry-sdk
Release list
v1.0.0
The npm packages are not published yet. The Claude Desktop bundle (
ferry-mcp.mcpb, below) is self-contained; the SDK, CLI and MCP server can be built from source withnpm ci && npm run build.
First public release: @ferryvn/sdk, @ferryvn/cli and @ferryvn/mcp under Apache-2.0. Every endpoint and response shape was re-verified against backend v3.2.16. That includes api_key permissions, real response bodies, error shapes, rate limits and webhooks.
Security
- MCP Streamable HTTP mode — DNS rebinding. Before this release,
ferry-mcp --httpaccepted requests with anyHost/Origin. A malicious web page could therefore call its tools with your API key, including write tools when they were enabled. Changes:Hostand browserOriginheaders are now validated.- An optional bearer token (
FERRY_MCP_HTTP_TOKEN) is added. It is mandatory for non-loopback binds. - Only POST is accepted, the body is limited to 1 MB, and request/header timeouts are set.
/healthzwas added for health checks.
- SDK in browsers. The client refuses to start with an API key in a browser unless
dangerouslyAllowBrowser: trueis set. The key has organization-admin power. - Logging. Hooks and debug logs redact
Authorization.FerryApiError.toJSON()omits response bodies, which may contain personal data. - CSV output. CLI CSV output neutralises spreadsheet formula injection.
Added
- Route prices —
ferry.prices.forRoute(),ferry pricesand the MCP toolget_route_prices: the selling price (incl. VAT and your price additions, plus the per-ticket harbor fee) of every voyage × seat type × ticket type on a route for a date or range, with age rules;livere-prices a few voyages from their seat map. Vehicle places and ticket types the API cannot book (e.g. Superdong invitations at 30 000 ₫) are hidden by default. - SDK
- New resources:
customers: create, get, update, list, searchorganization:me,reservableCredittransactionsanddebtLedger: read-onlybookings.getPublicandbookings.listChildOrgBookings
- Booking builder (
ferry.bookingDrafts.prepare/create,buildLegTickets). You pass seats (or let it pick adjacent ones) and passengers. It picks ticket types by age and nationality with the backend's operator rules, matches prices, checks free seats, duplicate social ids and the operator's input-length rules, resolves nationalities (ISO code or operator id), quotes the total (tickets + harbor fees − vouchers) and reports every issue locally. - Voyage and seat helpers:
isVoyageBookable,voyageUnbookableReason,voyageDeparture,voyageHarborFee,seatRef,resolveNational,parseIssueTicketError/formatIssueTicketError,nowInVietnam. - Per-request options on every method:
signal,timeoutMs,maxRetries,headers,requestId. iterate()auto-pagination on every cursor list, pluscollect().hooks(onRequest,onResponse,onRetry) and adebuglogger.- An
x-request-idon every request, exposed on errors. - A locations cache, which makes repeated name lookups cost one request.
- Errors:
- New classes
FerryPermissionError(403),FerryConflictError(409),FerryServerError(5xx),FerryConnectionError,FerryAbortError,FerryConfigErrorandFerryBookingDraftError. FerryErrorCodeconstants anderrorCodeName().userMessage(Vietnamese text from the backend).
- New classes
- Webhook helpers:
parseWebhook(typed byx-event-type),safeEqual,WEBHOOK_EVENTS. - Date and money helpers:
todayInVietnam,addDays,isValidYmd,formatVnd,ticketFileUrls. - Enums:
OrderPaymentStatus,TicketStatus,TicketPaymentStatus,Gender.
- New resources:
- CLI
- Profiles (
--profile,profiles list|use,FERRY_PROFILE). --output table|json|ndjson|csv.- New commands:
booking draft(passengers from CSV/JSON),booking update|tickets|public,orders list,customers,account,transactions,debts,vouchers,disruptions,calendar,operators,api,doctor,completion. whoami --check.- Interactive confirmation for
booking issue(shows the total and reservable credit) andcancel request. - Distinct exit codes: 3 auth, 4 not found, 5 rate limited.
--date today|tomorrow.--debug.
- Profiles (
- MCP
- New read tools:
get_voyage_calendar,find_customers,get_disruptions,get_account,list_vouchers. - New write tool
create_customer, and the read-onlyquote_booking(validates and prices a booking without creating it). create_bookingnow takes seat names and passengers, and builds the payload with the booking builder.- Write confirmation through MCP elicitation when the client supports it.
- A write budget (
FERRY_MCP_MAX_WRITES) and a JSON audit log (FERRY_MCP_AUDIT_FILE). structuredContentwith compact JSON text.- Prompts
plan_ferry_tripandcheck_booking, and the resourceferry://locations. - A Claude Desktop bundle (
.mcpb), a Dockerfile for HTTP mode, and an MCP Registryserver.json.
- New read tools:
- Repository
- Biome, coverage thresholds, publint, are-the-types-wrong, and a pack-and-install test.
- A CI matrix: Node 22 and 24; an SDK runtime smoke test on Node 20; Windows and macOS.
- A tag-triggered release workflow (GitHub release + npm), Dependabot, and nightly read-only UAT contract tests.
- Lockstep versioning (
npm run version:set). openapi/sdk.json, a filtered partner surface. Generated types shrink from 412 KB to 224 KB.
Verified on UAT
Before release, the SDK, CLI and MCP were run read-only against UAT (api.ferryvn.net, 2026-10-07) on six operators. Fixed from what real data showed:
- Seat maps counted seats the operator does not render (Lightning68: 48 extra seats); free seats now equal the voyage's
no_remain. - Repeated seat names (Saigon Watergo
C23/C24) silently mapped to one seat; they are now resolved or refused withNAME#POSITION_IDrefs. - The default nationality fell back to the first list entry when no entry was flagged default (GreenLines); it is now Vietnam.
- Auto-picked seats could mix seat types and scatter a group; ticket types were ranked by the "default" flag instead of price; priced-but-unconfigured types (invitations) could be chosen; the NATIONAL_ID rule and the operators' social-id / home-town length rules were not applied.
- The quote ignored harbor fees and vouchers.
boatLayouts.latestByVoyageId/byVoyageIdreturnedundefinedon an empty 204 (nowFerryNotFoundError).- CLI/MCP listed operator-disabled voyages that cannot be sold (all UAT voyages at the time); they are now hidden by default (
--include-unavailable/include_unavailable).issue_ticket_error(a JSON string) is now parsed; times show asHH:mm;VoyageIsDisabledand 403 errors carry actionable hints; MCPget_seat_mapis grouped by seat type andlist_bookingsreturns brief rows. - Sanitized UAT supply fixtures (
packages/sdk/test/fixtures/uat-supply.json) back the regression tests.
Verified on production
The same checks were then run read-only against production (api.ferry.vn, 2026-10-07; 478 voyages on 51 routes, one live seat map per operator, nothing created or issued). Fixed:
- Thạnh Thới and Bình An Hà Tiên sell motorbike/car places as seat types (
Xe gắn máy,XEMAY<195,OTO4-5), often cheaper than a passenger seat: auto-pick only uses passenger seat types (those with a default price, never vehicle places —isVehicleSeatType), and a passenger put on a vehicle place without a plate gets a warning. Vehicle places price every ticket type the same; the lowest type id is used instead of failing. - Their seat maps carry no ticket-type rules (Thạnh Thới publishes them on the voyage instead): the builder falls back to the voyage's rules and warns that FerryVN validates ticket types against the seat map and may refuse the booking (
InvalidTicketTypeId). - Operators' name-length rule (Thạnh Thới, Bình An Hà Tiên, Cảng Sa Kỳ: 4–12 characters), which FerryVN does not check at creation, is reported as a warning.
- Vouchers: unique vouchers cannot be combined and per-customer limits are applied, as the server does at creation (the check endpoint does neither).
- CLI/MCP hints for
CanNotGetBoatVoyage(operator seat map unavailable) andCannotFindBoatLayoutFromVoyage(no stored seat map yet); MCP flags vehicle places inget_seat_mapand says search prices are indicative (Thạnh Thới listed 290 000 ₫ while its live seat map charged 365 000 ₫). - Contract tests run against production with
FERRY_CONTRACT_ENV=production FERRY_CONTRACT_API_KEY=…. - Write path checked with two real
Requestedbookings (no payment, no issuance): the server's totals matched the builder's quote to the đồng (Saigon Watergo 418 000 ₫; Superdong Sóc Trăng → Côn Đảo 684 182 ₫ incl. 36 000 ₫ harbor fees). The CLI now also shows the organization's cost (total_net_price). - Sanitized production supply fixtures (
packages/sdk/test/fixtures/prod-supply.json) back the new regression tests.
Pre-release audit
A line-by-line audit of the three packages before publishing (2026-10-18). Fixed:
- SDK transport and errors: an abort fired during an async
onRequesthook is honoured; per-calltimeoutMs/maxRetriesare validated and timeouts capped below thesetTimeoutoverflow (MAX_TIMEOUT_MS); header redaction keeps only the auth scheme and hides cookies andx-api-keyentirely; query strings (search terms, phone numbers) stay out of error messages andtoJSON(); HTML error pages no longer become the message; string error codes (disruption endpoints) are read. - SDK resources:
routes/orders/vouchers/bookings.getthrowFerryNotFoundErroron an empty body;cancelTicketRequests.getthrows instead of returningnull;voyageCounts.listrequiresfrom/to(the backend refuses without them);routes.listtakesorderlike the ot...