PluginBench
MCP Server
Active
MIT

io.github.mayurrawte/searoute MCP Server

io.github.mayurrawte/searoute

Calculate shortest sea routes between ports with canal/strait awareness and UN/LOCODE support.

What is the io.github.mayurrawte/searoute MCP server?

The searoute-ts MCP server computes realistic shortest maritime routes between any two points on Earth using the 2025 Eurostat maritime network. It returns GeoJSON-formatted routes with distance, ETA, canal/strait passages, and vessel-draft gating for accurate shipping logistics.

This server enables maritime route planning for shipping and logistics applications. It calculates actual sea distances (not great-circle lines), accounts for canal restrictions (Suez, Panama, Kiel, Corinth, etc.), supports UN/LOCODE port codes, estimates voyage duration, and provides alternative routes. Useful for freight forwarding, supply-chain optimization, emissions reporting, and shipping-lane visualization.

How to install io.github.mayurrawte/searoute

Copy-paste configuration for popular MCP clients.

transport: stdio
Config generated by PluginBench — verify against the source before use.
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "searoute": {
      "command": "npx",
      "args": [
        "-y",
        "@searoute-ts/mcp"
      ]
    }
  }
}

Tools & capabilities

Tools this server exposes to the agent.

  • seaRoute — Calculate the shortest sea route between two coordinates or UN/LOCODE port codes, returning GeoJSON LineString with distance, duration, and passages.
  • seaRouteMulti — Plan multi-leg routes through multiple waypoints (port rotations), returning concatenated LineString with total distance and union of passages.
  • seaRouteAlternatives — Generate K-shortest alternative routes with different canal/strait combinations (e.g., via Suez vs. Cape of Good Hope).
  • lookupPort — Resolve a UN/LOCODE port code to its full metadata (name, country, coordinates).
  • resolvePort — Convert a UN/LOCODE string to [longitude, latitude] coordinates.
  • loadPorts — Fetch the ~1,600-port UN/LOCODE dataset from a CDN at runtime instead of bundling it.
  • loadNetwork — Fetch a maritime network (marnet) from a URL instead of using the bundled default, enabling offline-free operation or higher-resolution routing.

Use cases

  • Calculate door-to-door shipping distances and ETAs between ports for freight quotes and logistics planning.
  • Compare alternative routes (e.g., Suez vs. Cape of Good Hope) during canal disruptions or to optimize fuel costs.
  • Enforce vessel-draft constraints to auto-block shallow canals (Panama 15.2 m, Suez 20.1 m, Kiel 7 m) based on ship specifications.
  • Estimate CO₂e emissions and ECA/SECA zone exposure for regulatory compliance and sustainability reporting.
  • Visualize shipping lanes and port-to-port networks on interactive maps using GeoJSON output.

io.github.mayurrawte/searoute MCP server FAQ

What is the searoute-ts MCP server?

It's a maritime route-planning library that calculates the shortest realistic sea routes between ports, accounting for canals, straits, vessel draft, and alternative passages. It returns GeoJSON routes with distance, ETA, and emissions data.

Is it free?

Yes. The package is open-source (MIT license) and available on npm as @searoute-ts/mcp. The underlying Eurostat maritime network is public data.

How do I install it in Cursor or Claude?

Install via npm: `npm install @searoute-ts/mcp`. Then configure your MCP client to load the server. See the repository for detailed setup instructions.

Do I need authentication or API keys?

No. The server works offline with bundled data by default. Optionally, you can fetch updated networks from public CDNs (jsDelivr, unpkg) or GitHub Pages at runtime.

What data does it use?

The 2025 Eurostat maritime network (marnet) with ~9,800 segments at 100 km resolution by default. Higher-resolution variants (20 km, 50 km) and a ~1,600-port UN/LOCODE dataset are also available.

Can I use port codes instead of coordinates?

Yes. Import `searoute-ts/ports` to enable UN/LOCODE strings like 'CNSHA' (Shanghai) or 'NLRTM' (Rotterdam) directly in the API. You can also mix codes and coordinates.

README (reference)

Source of truth, from the repository.

searoute-ts

Shortest sea route between any two points on Earth. A TypeScript / JavaScript library for maritime route planning, port-to-port distance, ETA estimation, and shipping-lane visualisation — powered by the 2025 Eurostat maritime network.

npm version npm downloads CI license types

npm install searoute-ts
import { seaRoute } from 'searoute-ts';

const route = seaRoute([121.5, 31.0], [4.4, 51.9]);
// Shanghai → Rotterdam → GeoJSON LineString, ~10,664 nm via Suez Canal

🗺️ Try the interactive demo — click two points on a map and see the route, with all options live. (source)

📏 Port-to-port sea distances — distance, sailing time and canal passages for common routes between major ports.

Works from plain JavaScript too — the package ships compiled .js plus .d.ts declarations. The -ts in the name is for searchability, not a language requirement.


Why searoute-ts

  • 🚢 Realistic shipping routes, not great-circle lines through Eurasia.
  • 🗺️ Returns GeoJSON — drop straight into Leaflet, Mapbox, deck.gl, MapLibre.
  • 🌊 2025 Eurostat marnet with explicit Suez, Panama, Bab-el-Mandeb, Malacca, Gibraltar, Dover, Kiel, Corinth, Bering, Magellan, NW/NE Passage labels.
  • 🚫 Canal & strait restrictions — force Cape of Good Hope during a Red Sea disruption with one option.
  • 📦 Vessel-draft gating — auto-block Panama (15.2 m), Suez (20.1 m), Kiel (7 m), Corinth (7.3 m) when the vessel exceeds the canal limit.
  • 🛤️ K-shortest alternatives — seaRouteAlternatives returns the baseline plus up to N realistic alternatives.
  • 🧭 Multi-leg waypoints — seaRouteMulti for port rotations and itineraries.
  • ⏱️ ETA from speed — speedKnots → durationHours.
  • 🛠️ Modern toolchain — TypeScript 5, ESM + CJS dual build, types included, Node 18+, zero peer deps.

Quick examples

Basic — shortest route

import { seaRoute } from 'searoute-ts';

const route = seaRoute([-74.04, 40.69], [-0.13, 51.5]); // NYC → London
// route.properties.length  // ≈ 3 362 nm
// route.properties.units   // 'nauticalmiles'

With ETA and units

seaRoute(shanghai, rotterdam, {
  units: 'kilometers',
  speedKnots: 22,
});
// → 19 753 km, properties.durationHours ≈ 485 h (≈ 20 days)

Red Sea / Suez disruption — force Cape of Good Hope

seaRoute(shanghai, rotterdam, {
  restrictions: ['suez', 'babelmandeb'],
});
// → routes via Cape of Good Hope, ~25 800 km

Vessel-aware — Ultra Large Container Ship

seaRoute(shanghai, newYork, {
  vesselDraftMeters: 16,  // exceeds Panama's 15.2 m TFW
});
// → Panama auto-blocked, route goes via Suez

Port codes (UN/LOCODE)

import 'searoute-ts/ports'; // enables UN/LOCODE strings on the core API
import { seaRoute } from 'searoute-ts';

seaRoute('CNSHA', 'NLRTM'); // Shanghai → Rotterdam
seaRoute('CNSHA', [4.4, 51.9]); // mixing a code and coordinates is fine too

The ~1 600-port dataset lives behind the searoute-ts/ports subpath so the core stays lean — importing it registers the resolver. You can also resolve codes yourself:

import { lookupPort, resolvePort } from 'searoute-ts/ports';

lookupPort('SGSIN'); // → { code, name: 'Singapore', country, coordinates: [lon, lat] }
resolvePort('SGSIN'); // → [103.85, 1.28]

Unknown codes throw UnknownPortError. See Port codes below for provenance.

Load the port dataset from a CDN instead of bundling it

Don't want to bundle the ~135 KB dataset? Fetch it at runtime with loadPorts — the analog of loadNetwork. The dataset also ships as a raw dist/ports.json, so jsDelivr/unpkg serve it versioned for free:

import { seaRoute, loadPorts } from 'searoute-ts';

// Pin a version for reproducibility, or use @latest to always get the newest.
await loadPorts('https://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/ports.json');

seaRoute('CNSHA', 'NLRTM'); // works — the fetched dataset is now registered
https://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/ports.json      # newest
https://cdn.jsdelivr.net/npm/searoute-ts@<version>/dist/ports.json   # frozen/immutable

(dist/ports.json ships from the release that adds port codes onward — pin any version at or after it for reproducibility.)

loadPorts registers the fetched dataset (so code strings resolve) and returns it. It uses the global fetch (Node ≥18 / browsers); pass { fetch } to override.

Multi-leg / port rotation

import { seaRouteMulti } from 'searoute-ts';

seaRouteMulti(
  [shanghai, singapore, mumbai, rotterdam],
  { units: 'kilometers', returnPassages: true },
);
// → one concatenated LineString, total length, union of passages

Alternative routes (Yen-style canal permutation)

import { seaRouteAlternatives } from 'searoute-ts';

const alts = seaRouteAlternatives(shanghai, rotterdam, { k: 4 });
//  baseline           19 753 km via Suez
//  no-malacca         20 759 km
//  no-suez            25 315 km (via Panama)
//  no-suez-no-panama  25 845 km (via Cape of Good Hope)

Fetch the network from a URL instead of bundling it (optional)

The network is bundled by default, so seaRoute works offline with zero setup. If you'd rather not ship the ~1 MB network (e.g. to trim a browser bundle, or to use an updated network without upgrading the package), fetch it at runtime and pass it via the existing network option:

import { seaRoute, loadNetwork } from 'searoute-ts';

// CORS-enabled, served from GitHub Pages (or point at your own host / a CDN).
const network = await loadNetwork('https://mayurrawte.github.io/searoute-ts/marnet.json');

const route = seaRoute(shanghai, rotterdam, { network });

Only the fetch is async — seaRoute itself stays synchronous. loadNetwork uses the global fetch (Node ≥18 and all browsers); pass { fetch } to supply your own. This is purely opt-in; nothing changes if you don't use it.

Which option should I use?

ApproachHowData versionWorks offlineBest for
Bundled (default)seaRoute(a, b) — no networkpinned to your installed package✅Most users; zero config, deterministic
Latest via URLloadNetwork('…/marnet.json')always the newest hosted❌ needs networkAlways-current data without upgrading
Pinned via CDNloadNetwork('https://cdn.jsdelivr.net/npm/searoute-ts@2.0.1/…')frozen (immutable)❌ needs networkReproducible builds

Versioning the hosted network

You choose the version by choosing the URL:

  • @latest / rolling — the GitHub Pages URL above always serves the current network. Convenient, but it can change under you.

  • Pinned & immutable — because the package is on npm, jsDelivr and unpkg serve every published version automatically, with immutable per-version URLs:

    https://cdn.jsdelivr.net/npm/searoute-ts@latest/dist/marnet.json   # newest
    https://cdn.jsdelivr.net/npm/searoute-ts@2/dist/marnet.json        # newest 2.x
    https://cdn.jsdelivr.net/npm/searoute-ts@2.0.1/dist/marnet.json    # frozen
    

    A pinned URL never changes, so your routes stay reproducible. (These standalone-JSON CDN paths land with the package once the network ships as a separate asset — see issue #10; until then, use the GitHub Pages URL.)

For production, prefer a pinned URL (or just the bundled default) so your distances don't shift when the network is updated.

Higher-resolution networks (optional)

The bundled network is Eurostat's 100 km marnet_plus. Eurostat also publishes finer resolutions, which give more accurate coastal routing and shorter-hop fidelity at the cost of a larger download and slightly slower first-route graph construction. Two moderate resolutions ship as subpath exports so you only pay for them if you import them:

import { DEFAULT_MARNET } from 'searoute-ts/marnet-20km'; // or 'searoute-ts/marnet-50km'
import { seaRoute } from 'searoute-ts';

seaRoute(origin, destination, { network: DEFAULT_MARNET });

Like the bundled default, each variant ships once as a shared dist/data/marnet-<res>.cjs asset that both the CJS and ESM builds load at runtime, so importing a variant doesn't duplicate the network across builds.

ImportResolutionSegmentsJSON sizegzippedCoastal accuracy
searoute-ts (bundled default)100 km9,847~1.3 MB~0.18 MBBaseline — good for global routing
searoute-ts/marnet-50km50 km15,498~1.9 MB~0.27 MBModest step up
searoute-ts/marnet-20km20 km29,581~3.6 MB~0.51 MBNoticeably finer coastal hops
via loadNetwork (see below)10 km48,301~5.9 MB~0.84 MBHigh — larger download
via loadNetwork (see below)5 km72,478~9.0 MB~1.24 MBHighest — largest download

The 10 km and 5 km networks are large enough that bundling them would dominate the install, so they are not shipped in the package. Generate them from the Eurostat source with scripts/build-marnet.cjs (the script header documents the GDAL conversion), host the resulting JSON, and load it with loadNetwork — or pass any FeatureCollection<LineString> to the network option directly.

Output shape

{
  type: 'Feature',
  geometry: { type: 'LineString', coordinates: [[lon, lat], ...] },
  properties: {
    length: number,                    // in `units`, in-water only
    units: 'nauticalmiles' | 'kilometers' | 'miles' | ...,
    bbox: [minLon, minLat, maxLon, maxLat],
    greatCircleLength: number,         // haversine between inputs, same units
    detourRatio: number,               // routeKm / greatCircleKm
    originSnapKm: number,              // input → network distance
    destinationSnapKm: number,
    durationHours?: number,            // if `speedKnots` set
    passages?: ('suez' | 'panama' | ...)[],  // if `returnPassages: true`
    ecaKm?: number,                    // if `emissions` + `searoute-ts/eca` imported
    ecaFraction?: number,              // ecaKm / length (0–1)
    co2eTonnes?: number,               // if `emissions` + `vesselClass`/factor
  }
}

length runs between the two snapped network vertices. It does not include the legs from your inputs to the network (originSnapKm, destinationSnapKm, always in km), even with appendOriginDestination: true, which only adds the raw points to the line. For a door-to-door figure, add them yourself:

const { length, originSnapKm, destinationSnapKm } = seaRoute(a, b, { units: 'kilometers' }).properties;
const doorToDoorKm = originSnapKm + length + destinationSnapKm;

On hops shorter than the network resolution (~100 km by default, ~20 km with searoute-ts/marnet-20km), the snapped vertices can sit closer together than the inputs, so length can come out below greatCircleLength (detourRatio < 1). If both inputs snap to the same vertex, NoRouteError is thrown.

Full options

seaRoute(origin, destination, {
  units:                   'nauticalmiles',          // any Turf unit
  restrictions:            ['suez', 'babelmandeb'],  // block passages (see table below)
  via:                     ['panama'],               // require passages (inverse of restrictions)
  allowArctic:             false,                    // default — blocks NWP & NEP
  vesselDraftMeters:       15,                       // auto-restrict canals
  speedKnots:              22,                       // → properties.durationHours
  appendOriginDestination: false,                    // prepend/append raw inputs
  returnPassages:          true,                     // populate properties.passages
  maxSnapDistanceKm:       50,                       // SnapFailedError if exceeded
  network:                 customMarnet,             // BYO FeatureCollection
  antimeridian:            'split',                  // 'unwrap' | 'split' dateline handling
  emissions:               true,                     // → properties.ecaKm / co2eTonnes
  vesselClass:             'panamax',                // CO₂e estimate class
  co2eFactorKgPerKm:       225,                      // override the class factor
  glecInflation:           0.15,                     // +15% distance for CO₂e (GLEC)
});

Inputs can be [lon, lat] arrays, GeoJSON Feature<Point>, bare Point objects, or a UN/LOCODE string (e.g. 'CNSHA') once searoute-ts/ports is imported.

Antimeridian (dateline) handling

Routes that cross the ±180° meridian (e.g. Yokohama → LA) come back wrapped to [-180, 180] by default, which many map renderers draw as a straight streak across the whole map. Pass antimeridian to get map-ready geometry:

seaRoute(yokohama, la, { antimeridian: 'unwrap' }); // continuous LineString (may exceed ±180)
seaRoute(yokohama, la, { antimeridian: 'split' });  // MultiLineString cut at ±180 (RFC 7946)

'unwrap' shifts longitudes by multiples of 360° so the line never jumps the dateline (ideal for MapLibre/Leaflet/Deck.gl). 'split' cuts the route into a MultiLineString at ±180°, keeping every coordinate in range. Both apply to seaRoute and seaRouteMulti; properties.length is unchanged either way.

Forcing routes through a passage (via)

restrictions blocks a passage; via requires one — the inverse. Use it to compare explicit routings, e.g. "via Suez" against "via Cape of Good Hope", or to force a Pacific + Panama routing between Asia and Europe:

seaRoute('CNSHA', 'NLRTM', { via: ['suez'] });   // through Suez (the default)
seaRoute('CNSHA', 'NLRTM', { via: ['panama'] });  // across the Pacific + Panama instead

via accepts the same passage names as restrictions and visits multiple passages in the order given. It routes origin → passage → destination through each passage's location using the multi-leg machinery, so it composes with the other options. A passage named in via is never blocked out from under the requirement (via: ['northeast'] reaches the Northeast Passage without also needing allowArctic). Naming the same passage in both via and restrictions is a contradiction and throws NoRouteError.

Emissions & ECA/SECA reporting

Opt in with emissions: true for two rough estimates on properties:

import 'searoute-ts/eca';                 // load the ECA/SECA zones (enables ecaKm)
import { seaRoute } from 'searoute-ts';

const r = seaRoute('CNSHA', 'NLRTM', {
  emissions: true,
  vesselClass: 'panamax',                 // → co2eTonnes
});
r.properties.ecaKm;        // km of the route inside emission-control zones
r.properties.ecaFraction;  // that as a fraction of route length (0–1)
r.properties.co2eTonnes;   // rough CO₂e estimate for the voyage
  • ecaKm — how much of the route lies inside ECA/SECA emission-control areas (Baltic, North Sea, Mediterranean, North American and US Caribbean), which drives fuel-type/cost. The zones ship behind the searoute-ts/eca subpath export (to keep the core lean); importing it registers them. They are bounding-box approximations of the IMO MARPOL Annex VI areas — good for estimates, not compliance. Swap in higher-fidelity polygons with registerEcaZones.
  • co2eTonnes — a deliberately simple distance × vessel-class factor estimate, not a certified figure. Factors are derived transparently from a representative fuel burn and the IMO HFO CO₂ conversion (see VESSEL_CLASSES); override with co2eFactorKgPerKm. GLEC recommends inflating shortest-path distance by ~15 % for real-world deviations — pass glecInflation: 0.15.

Restrictable passages

The first twelve are natively labelled in the Eurostat marnet (exact match on the feature's pass attribute). The remaining four are detected via bounding boxes.

NameTypeNotes
sueznativeSuez Canal
panamanativePanama Canal
gibraltarnativeStrait of Gibraltar
babelmandebnativeBab-el-Mandeb (babalmandab alias)
malaccanativeMalacca Strait
dovernativeDover Strait
kielnativeKiel Canal
corinthnativeCorinth Canal
beringnativeBering Strait
magellannativeStrait of Magellan
northwestnativeNorthwest Passage (blocked by default)
northeastnativeNortheast Passage (blocked by default)
bosporusbboxBosphorus
ormuzbboxStrait of Hormuz
sundabboxSunda Strait
cape_hornbboxCape Horn region

The Northwest and Northeast Passages are mathematically the shortest path for many Asia ↔ Europe routes but are ice-blocked most of the year, so they are blocked by default. Opt in with allowArctic: true.

Validated against industry distances

12 real-world lanes within ±10% of published Searoutes / Sea-Distances figures.

Lanesearoute-tsIndustry ref.
Shanghai → Rotterdam (Suez)19 753 km~19 300 km
Singapore → Rotterdam (Suez)15 630 km~15 500 km
Mumbai → Rotterdam (Suez)11 918 km~11 800 km
NY → Rotterdam6 227 km~6 200 km
NY → LA (Panama)9 154 km~9 100 km
Yokohama → LA9 145 km~8 800 km
Singapore → LA (trans-Pacific)14 364 km~14 300 km
Caldera (CL) → Bahía Blanca (AR)4 810 km~5 180 km

All checks pass in the test suite.

Errors

  • SnapFailedError — input cannot be projected onto the network within maxSnapDistanceKm. Carries .side: 'origin' | 'destination' and .distanceKm: number.
  • NoRouteError — no path exists between the snapped origin and destination (e.g. all viable canals blocked).

API reference

import {
  seaRoute,                  // single shortest route
  seaRouteMulti,             // ordered waypoints (multi-leg)
  seaRouteAlternatives,      // K-shortest alternatives
  loadNetwork,               // optional: fetch a network from a URL/CDN
  CANAL_MAX_DRAFT_M,         // { panama: 15.2, suez: 20.1, kiel: 7, corinth: 7.3 }
  DEFAULT_MARNET,            // bundled FeatureCollection<LineString>
  PASSAGE_BBOXES,            // passage bbox lookup
  clearFinderCache,          // drop the PathFinder cache (tests / hot reload)
  SnapFailedError,
  NoRouteError,
  UnknownPortError,          // thrown for unresolved UN/LOCODE strings
  registerPortResolver,      // plug in a custom port dataset
  // types
  type Passage,
  type Antimeridian,
  type SeaRouteOptions,
  type SeaRouteFeature,
  type SeaRouteMultiFeature,
  type SeaRouteProperties,
  type LoadNetworkOptions,
  type MarnetNetwork,
  type MarnetProperties,
} from 'searoute-ts';

import {
  lookupPort,                // UN/LOCODE → { code, name, country, coordinates }
  resolvePort,               // UN/LOCODE → [lon, lat]
  PORTS,                     // the raw dataset (Record<code, PortRecord>)
  PORT_COUNT,
  type Port,
  type PortRecord,
} from 'searoute-ts/ports';

Port codes (UN/LOCODE)

Origins and destinations may be given as UN/LOCODE strings (e.g. 'CNSHA') instead of coordinates. The port dataset ships behind the searoute-ts/ports subpath export, so consumers only pay for it if they use it — importing the subpath (for any of its exports, or purely for its side effect) registers a resolver into the core so seaRoute('CNSHA', 'NLRTM') works.

  • ~1 600 seaports, keyed by UN/LOCODE (primary codes and aliases).
  • Source: marchah/sea-ports (MIT), itself derived from UN/LOCODE. Regenerate with scripts/build-ports.cjs.
  • Coordinates are approximate (port-city granularity) — the routing engine snaps them onto the network anyway, so this is fine for distance/visualisation.
  • Unknown or malformed codes throw UnknownPortError.

Use from an AI agent (MCP)

A companion Model Context Protocol server, @searoute-ts/mcp (source), lets AI agents (Claude Desktop, the claude CLI, etc.) compute real sea routes instead of guessing — asking "how far is Shanghai to Rotterdam by sea, avoiding Suez?" calls the library directly. It exposes two tools, sea_route and sea_route_alternatives, and accepts port codes ('CNSHA') or coordinates.

claude mcp add searoute -- npx -y @searoute-ts/mcp

Or add it to any MCP client config:

{
  "mcpServers": {
    "searoute": {
      "command": "npx",
      "args": ["-y", "@searoute-ts/mcp"]
    }
  }
}

See the server's README for the full tool reference. For the rail leg, add @railroute-ts/mcp alongside it (claude mcp add railroute -- npx -y @railroute-ts/mcp).

Multimodal: add the rail leg (railroute-ts)

Sea distance is rarely the whole shipment. The sibling library railroute-ts routes over the OpenStreetMap rail network (Europe bundled, same API shape, same GeoJSON output), so a port-to-inland quote or a GLEC/CountEmissions-style report is one extra call:

import 'searoute-ts/ports';
import { seaRoute } from 'searoute-ts';
import { railRoute } from 'railroute-ts';
import { EUROPE_NETWORK } from 'railroute-ts/networks/europe';

const sea  = seaRoute('CNSHA', 'NLRTM', { units: 'kilometers', emissions: true, vesselClass: 'panamax' });
const rail = railRoute([4.47, 51.92], [8.92, 44.41], { network: EUROPE_NETWORK, speedKmh: 60 }); // Rotterdam → Genoa

sea.properties.length;        // ≈ 19,753 km  Shanghai → Rotterdam via Suez
sea.properties.co2eTonnes;    // ≈ 4448 t CO₂e (rough, see Emissions above)
rail.properties.length;       // ≈ 1,180 km  Rotterdam → Genoa via the Gotthard base tunnel
rail.properties.gaugeChanges; // 0 — standard gauge all the way

npm install railroute-ts — docs & interactive demo. Both libraries also ship MCP servers, so an AI agent can chain sea_route → rail_route for door-to-door distance (see below).

How it works

A two-page deep-dive (graph data, snapping, Dijkstra, restrictions, antimeridian fix, draft logic, alternatives) is in DOCS.md.

FAQ

Is this for navigation? No. The routes are network paths suitable for visualisation and rough distance/duration estimates, not for piloting ships.

Does it support weather routing? No. For weather-aware routing see VISIR-2.

Why are my Asia→Europe routes going through Bering Strait? They aren't, by default — the Northwest and Northeast Passages are blocked. Pass allowArctic: true to enable them.

Can I use my own network? Yes — seaRoute(origin, destination, { network }). Useful for inland waterways or AIS-derived custom graphs. For higher-resolution Eurostat data (5/10/20/50 km), see Higher-resolution networks — 20 km and 50 km ship as subpath exports.

Does it handle the Red Sea / Suez crisis? Yes — pass restrictions: ['suez', 'babelmandeb'] to force Cape of Good Hope routing.

Cannot find module '…/searoute-ts/dist/lib/utils' on 1.x? 1.x (up to 1.2.1) shipped an ESM build with extensionless imports under "main", so plain Node can't load it without patching. Fixed in 2.0: install searoute-ts@^2, because a ^1 range never picks it up. When upgrading, use the named import { seaRoute }. Failures now throw NoRouteError / SnapFailedError instead of returning null, and nautical-mile lengths are ~24 % smaller, because 1.x over-counted them. See the 2.0.0 migration notes in the CHANGELOG.

Is the great-circle distance correct across the antimeridian? Yes — the marnet has been normalised so the Pacific is a connected graph, and all distances use haversine internally.

What's the bundle size? What you import at runtime is small: the core plus the bundled 100 km marnet (~1.1 MB JSON, shipped once as a shared dist/data/marnet.cjs asset both builds load, rather than inlined into each). Tree-shakeable, so the optional searoute-ts/marnet-20km / marnet-50km networks only load if you import them. They do add to the npm tarball, though — including them the package is ~1.1 MB packed / ~7 MB unpacked (each variant is a single shared asset, not duplicated per build). If you need the finer networks without the install cost, generate and host them and use loadNetwork instead.

Credits

License

MIT © Mayur Rawte

Related MCP servers

AI assistant access to Mindvalley products, masterclasses, programs, and certifications.

0
Shell
MIT
View repository →

Find and verify trustworthy US home-services contractors by their un-buyable HomeClip Trust Score.

Search, read, and create speech-to-text transcripts on-device with the Whisper Notes Mac app.

0
MIT
View repository →

Deterministic dev tools for AI agents over MCP: hashing, encoding, formatting, codegen, and more.

View repository →

Normalize AGENTS.md / CLAUDE.md / .cursor/rules / .clinerules / .windsurf/rules from one source.

0
TypeScript
View repository →

74 paid web-analysis APIs (SEO, security, TLS, DNS, email) as MCP tools. USDC via x402.

6
TypeScript
MIT
View repository →