npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

colregs-mcp

v0.0.1

Published

Preview MCP server over colregs-engine: cited, plural, modality-preserving COLREGS light displays

Readme

colregs-mcp

Preview, 0.0.x. Not for navigation. The tool surface will change without notice.

An MCP server that answers "what lights may this vessel show" from colregs-engine, and hands back the answer in a form a language model cannot quietly simplify: every lawful display, every light with its own modality, every entry with its paragraph cite, and the verbatim rule text those cites resolve to.

It has no semantics of its own. The engine decides; this package only decides how the answer is written down. That is the whole job, and the response shape section is the part worth reading.

Try it

Not on npm yet. Clone and build:

git clone https://github.com/mark-brannan/colregs-mcp && cd colregs-mcp && npm install && npm run build

Then register the stdio server with your client. Claude Code:

claude mcp add colregs -- node /absolute/path/to/colregs-mcp/dist/cli.js

Any other MCP client: command node, argument dist/cli.js, no environment.

Tools

| tool | input | returns | |---|---|---| | evaluate_display | facts | every lawful display, cited and with modality per light | | applied_entries | facts | the applicability entries that hold, as {id, cite} | | rule_text | cite | verbatim paragraph text from colregs data/rules.json | | light | id | a light's definition from colregs data/lights.json |

facts is a colregs fact record: the keys and values of colregs' data/facts.json, namespaced (fact:propulsion, propulsion:sail). The input schema is generated from that file at startup, so it follows the data. A key or value outside the vocabulary is an error, never an empty answer.

Every tool description carries the coverage limits: COLREGS Part C lights only, international text only, lights at night only, pre-release data.

Two worked examples

The same two vessels as colregs-engine's README.

A 12 m sloop, under way: three displays

{ "facts": { "fact:propulsion": "propulsion:sail", "fact:activity": "activity:none",
             "fact:position": "position:underway", "fact:length_m": 11.6 } }

Trimmed response:

{
  "colregs": { "version": "0.2.0", "source": "resolved" },
  "applied": [
    { "id": "25a", "cite": "25(a)", "modality": "shall" },
    { "id": "25b", "cite": "25(b)", "modality": "may" },
    { "id": "25c", "cite": "25(c)", "modality": "may" }
  ],
  "exempted": [], "excluded": [],
  "lawful_displays": {
    "count": 3,
    "relation": "any_one_of",
    "options": [
      { "option": 1, "of": 3, "chosen": [],
        "lights": [
          { "light": "light:sidelights", "name": "sidelights", "modality": "shall", "prescribed_by": { "id": "25a", "cite": "25(a)" } },
          { "light": "light:sternlight", "name": "sternlight", "modality": "shall", "prescribed_by": { "id": "25a", "cite": "25(a)" } } ] },
      { "option": 2, "of": 3, "chosen": [ { "id": "25b", "cite": "25(b)", "modality": "may" } ],
        "lights": [
          { "light": "light:sidelights", "name": "sidelights", "combined": true, "position": "at or near the top of the mast", "modality": "may", "prescribed_by": { "id": "25b", "cite": "25(b)" } },
          { "light": "light:sternlight", "name": "sternlight", "combined": true, "position": "at or near the top of the mast", "modality": "may", "prescribed_by": { "id": "25b", "cite": "25(b)" } } ] },
      { "option": 3, "of": 3, "chosen": [ { "id": "25c", "cite": "25(c)", "modality": "may" } ],
        "lights": [
          { "light": "light:sidelights", "name": "sidelights", "modality": "shall", "prescribed_by": { "id": "25a", "cite": "25(a)" } },
          { "light": "light:sternlight", "name": "sternlight", "modality": "shall", "prescribed_by": { "id": "25a", "cite": "25(a)" } },
          { "light": "light:all_round", "name": "all-round light", "color": "red", "count": 1, "position": "upper, at or near the top of the mast", "modality": "may", "prescribed_by": { "id": "25c", "cite": "25(c)" } },
          { "light": "light:all_round", "name": "all-round light", "color": "green", "count": 1, "position": "lower, at or near the top of the mast", "modality": "may", "prescribed_by": { "id": "25c", "cite": "25(c)" } } ] }
    ]
  },
  "optional_additions": { "relation": "any_subset_of", "items": [] },
  "modality_key": { "shall": "mandatory", "may": "permitted alternative" },
  "cited_paragraphs": {
    "25(a)": "A sailing vessel underway shall exhibit: (i) sidelights; (ii) a sternlight.",
    "25(b)": "In a sailing vessel of less than 20 meters in length the lights prescribed in Rule 25(a) may be combined in one lantern carried at or near the top of the mast where it can best be seen.",
    "25(c)": "A sailing vessel underway may, in addition to the lights prescribed in Rule 25(a), exhibit at or near the top of the mast, where they can best be seen, two all-round lights in a vertical line, the upper being red and the lower green, but these lights shall not be exhibited in conjunction with the combined lantern permitted by Rule 25(b)."
  }
}

Three is the right number, and the response is built so that "a 12 m sloop shows sidelights and a sternlight" cannot be read off it. The fact record describes a situation, not a fitted vessel. There is no equipment fact in colregs' data/facts.json (checked: nothing in that file names a lantern, a fitting or a piece of gear), so nothing in the input can settle 25(b) against 25(c). The Rules leave that to the skipper, and so does this.

A fishing vessel aground: a veto, with its source

{ "facts": { "fact:propulsion": "propulsion:power", "fact:activity": "activity:fishing",
             "fact:position": "position:aground", "fact:length_m": 30 } }
{
  "applied": [
    { "id": "26c-id", "cite": "26(c)(i)", "modality": "shall" },
    { "id": "30d-anchor", "cite": "30(d)", "modality": "shall" },
    { "id": "30d-red", "cite": "30(d)", "modality": "shall-if-practicable" }
  ],
  "excluded": [
    { "id": "30a", "cite": "30(a)", "by": { "id": "26c-id", "cite": "26(c)(i)" } },
    { "id": "30b", "cite": "30(b)", "by": { "id": "26c-id", "cite": "26(c)(i)" } }
  ],
  "lawful_displays": {
    "count": 1, "relation": "exactly_one",
    "options": [ { "option": 1, "of": 1, "chosen": [], "lights": [
      { "light": "light:all_round", "color": "red",   "count": 1, "position": "upper", "modality": "shall", "prescribed_by": { "id": "26c-id", "cite": "26(c)(i)" } },
      { "light": "light:all_round", "color": "white", "count": 1, "position": "lower", "modality": "shall", "prescribed_by": { "id": "26c-id", "cite": "26(c)(i)" } },
      { "light": "light:all_round", "color": "red",   "count": 2, "arrangement": "vertical", "modality": "shall-if-practicable", "prescribed_by": { "id": "30d-red", "cite": "30(d)" } } ] } ]
  },
  "modality_key": { "shall": "mandatory", "shall-if-practicable": "mandatory where practicable, with a stated fallback" },
  "cited_paragraphs": { "26(c)(i)": "…", "30(d)": "…", "30(a)": "…", "30(b)": "…" }
}

Rule 26(a) says a fishing vessel shows only the lights of that Rule; the anchor lights of 30(a)/(b) are struck and the response says which entry struck them. The excluded paragraphs are still cited verbatim so the veto can be checked, not just believed.

Response shape

Three properties, each tested in test/examples.test.ts:

  1. Cited, verbatim. Every entry id travels with its paragraph cite, and cited_paragraphs holds the text of every paragraph a response cites, straight from colregs data/rules.json. This package writes no prose of its own into a response; the only sentences in it are the Rules'.
  2. Modality per light. shall, may, shall-if-practicable and the rest are kept on each light, not summarised per display. Option 3 above is two shall lights and two may lights, and it says so four times. modality_key carries colregs' own one-line glossary for the modalities the response uses.
  3. Plural by construction. lawful_displays opens with count and relation (exactly_one or any_one_of) before any lights appear; each option numbers itself n of count and names the choice entries that distinguish it; and there is no top-level lights field to mistake for the answer. The tool description says why plural is correct, in the terms above.

A vessel that lawfully shows nothing gets one empty display, not an error, matching the engine. An error is reserved for input outside the vocabulary.

Every response carries colregs.version and, on evaluate_display, colregs.source, both straight from the engine. The server refuses to start if its own copy of colregs and the engine's are different releases.

Why a separate package

colregs-engine is pure, total and has no runtime dependencies, and formal-methods work is planned on top of it. A transport and a server SDK belong somewhere else. This is that somewhere.

Dependencies and stability

  • colregs-engine is a git dependency on that repository's main branch, because the engine is not yet published to npm. package-lock.json records the commit an install resolved; npm update colregs-engine moves it to the current tip. Breakage from an engine change is accepted at this stage.
  • colregs is the published data package. The engine and this package must resolve the same release; a split install fails at startup with a message saying so.
  • Version 0.0.x. Tool names, argument names and response fields may all change. The three response properties above are the commitment; nothing else is.

Licence

Apache-2.0. Nothing here is advice to mariners; the fitness-for-navigation disclaimer in colregs carries over.