@cyanheads/noaa-marine-mcp-server 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** noaa-marine-mcp-server
4
- **Version:** 0.3.1
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `0.12.3`
6
- **Engines:** Bun ≥1.3.0, Node ≥24.0.0
4
+ **Version:** 0.3.2
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.2`
6
+ **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
8
- **Zod:** ^4.4.3
8
+ **Zod:** ^4.6.4
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
@@ -37,6 +37,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
37
37
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
38
38
  - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler.
39
39
  - **Secrets in env vars only** — never hardcoded.
40
+ - **Cut noise.** Add only what earns its place: no speculative generality, no guards for states the framework already prevents (Zod-validated params, classified errors), no abstraction until a third caller proves it, no option nothing sets.
40
41
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
41
42
 
42
43
  ---
@@ -145,7 +146,13 @@ export function getServerConfig() {
145
146
 
146
147
  ### Server identity and instructions
147
148
 
148
- This server's `createApp()` identity is the bare repository name in both `name` and `title`: `noaa-marine-mcp-server`. `instructions` is optional server-level orientation sent to clients on every `initialize`; use it for deployment guidance and cross-tool workflows instead of repeating the same context across tool descriptions.
149
+ This server's `createApp()` identity is the bare repository name in both `name` and `title`: `noaa-marine-mcp-server`. Never add `description` or `websiteUrl` — the framework derives canonical metadata from `package.json`, and a copy in `createApp()` is drift. `instructions` is optional server-level orientation sent to clients on every `initialize`; use it for deployment guidance and cross-tool workflows instead of repeating the same context across tool descriptions.
150
+
151
+ ### Session posture and shutdown
152
+
153
+ `createApp({ sessionMode: 'stateless' })` declares the HTTP session posture in `src/` rather than leaving it to a deployment's `MCP_SESSION_MODE`, which still wins whenever it carries a meaningful value (an empty string and an unsubstituted `${…}` placeholder read as unset and fall through to the option). Stateless is correct here: no tool gates on `ctx.requestInput`. A server that does gain one declares `{ default: 'stateful', require: 'stateful' }` so startup fails with a `ConfigurationError` instead of serving a mode a 2025-era HTTP client can never answer in. Stdio is never refused.
154
+
155
+ `teardown(core)` is the `setup()` counterpart — it releases a watcher, socket, or non-`unref()`'d timer after the transport stops and before the logger closes. Neither service here allocates one (both hold an in-memory station cache and nothing else), so the hook is unused; add it the moment a service acquires a handle it must close. `SIGTERM`/`SIGINT` exit the process explicitly once shutdown settles.
149
156
 
150
157
  ---
151
158
 
@@ -171,7 +178,7 @@ Handlers receive a unified `ctx` object. Key properties:
171
178
 
172
179
  Handlers throw — the framework catches, classifies, and formats.
173
180
 
174
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit hint when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
181
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit hint when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring.
175
182
 
176
183
  ```ts
177
184
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
@@ -253,9 +260,9 @@ src/
253
260
 
254
261
  ## Skills
255
262
 
256
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry.
263
+ Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant for those agents.
257
264
 
258
- **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
265
+ **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
259
266
 
260
267
  Available skills:
261
268
 
@@ -274,8 +281,9 @@ Available skills:
274
281
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
275
282
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
276
283
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
277
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
278
- | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
284
+ | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
285
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixup commits autosquashed into the stack, PR body kept in sync. Release PR mode only |
286
+ | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
279
287
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
280
288
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
281
289
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
@@ -294,7 +302,7 @@ Available skills:
294
302
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
295
303
  | `api-workers` | Cloudflare Workers runtime |
296
304
 
297
- **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
305
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `framework-skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
298
306
 
299
307
  When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
300
308
 
@@ -310,9 +318,10 @@ When you complete a skill's checklist, check the boxes and add a completion time
310
318
  | `bun run rebuild` | Clean + build |
311
319
  | `bun run clean` | Remove build artifacts |
312
320
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
313
- | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
314
- | `bun run lint:mcp` | Run the MCP definition linter standalone |
315
- | `bun run lint:packaging` | Run packaging surface checks (also part of devcheck) |
321
+ | `bun run audit:fix` | `bun audit fix` — upgrade vulnerable packages to the lowest safe version within existing ranges (`--dry-run` previews, `--latest` rewrites ranges). First response when `devcheck` flags a transitive advisory; then `bun update <name>`, then `bun dedupe` |
322
+ | `bun run audit:refresh` | Delete `bun.lock` and reinstall. Last resort after `audit:fix`, `bun update <name>`, and `bun dedupe` — re-resolves every ranged dep (the framework pin included) and rewrites the lockfile as `lockfileVersion: 2` |
323
+ | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
324
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, README version-badge parity (run by devcheck) |
316
325
  | `bun run list-skills` | Print the skill registry |
317
326
  | `bun run tree` | Generate directory structure doc |
318
327
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -328,11 +337,11 @@ When you complete a skill's checklist, check the boxes and add a completion time
328
337
 
329
338
  ## Bundling
330
339
 
331
- `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips dependency-shipped agent docs plus platform-specific native bindings that root-anchored `.mcpbignore` patterns cannot reach. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
340
+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
332
341
 
333
- **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
342
+ **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match, that every `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.X}"` (the host substitutes nothing else — `"${X}"` reaches the server as that literal string), and that an optional string option carries `"default": ""`.
334
343
 
335
- **README install badges** (Claude Desktop `.mcpb`, Cursor, VS Code) and the `base64` / `encodeURIComponent` config-generation commands are ship-time concerns — run the `polish-docs-meta` skill, which carries the badge format, layout, and generation snippets in `skills/polish-docs-meta/references/readme.md`.
344
+ **README install badges** (Claude Desktop `.mcpb`, Cursor, VS Code) and the `base64` / `encodeURIComponent` config-generation commands are ship-time concerns — run the `polish-docs-meta` skill, which carries the badge format, layout, and generation snippets in `framework-skills/polish-docs-meta/references/readme.md`.
336
345
 
337
346
  ---
338
347
 
@@ -353,16 +362,22 @@ security: false # optional — true ONLY for a source
353
362
  ...
354
363
  ```
355
364
 
356
- `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's own source code, never for a routine dependency or transitive CVE bump. When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
365
+ `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's *own source code*, never for a routine dependency or transitive CVE bump (record those under `## Dependencies`). When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
357
366
 
358
367
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
359
368
 
360
- **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
369
+ **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Include only sections with entries — don't ship empty headers.
361
370
 
362
371
  **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject omits the version number (GitHub prepends it). See `changelog/template.md` for the full format reference.
363
372
 
364
373
  ---
365
374
 
375
+ ## Publishing
376
+
377
+ **Every release goes through a release PR, straight-through** — `git-wrapup`'s "Release PR mode", mode `straight-through`. One run: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-and-publish` then fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. A caller's brief may run a given release as `gated` instead — a `release-pr-review` pass on the open PR before `release-and-publish`. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history.
378
+
379
+ ---
380
+
366
381
  ## Imports
367
382
 
368
383
  ```ts
@@ -389,7 +404,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
389
404
  - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
390
405
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
391
406
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
392
- - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = package name; `interface.shortDescription` from `package.json` description
393
- - [ ] `.codex-plugin/mcp.json` updated — server name key matches `package.json` name; env vars added for any required API keys
394
- - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry with server name key, env vars for any required API keys
407
+ - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description
408
+ - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset
409
+ - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — mirror the `user_config` block in `manifest.json`. Never write `"KEY": ""` into `env`
395
410
  - [ ] `bun run devcheck` passes
package/CLAUDE.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Server:** noaa-marine-mcp-server
4
- **Version:** 0.3.1
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `0.12.3`
6
- **Engines:** Bun ≥1.3.0, Node ≥24.0.0
4
+ **Version:** 0.3.2
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.2`
6
+ **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
  **MCP SDK:** `@modelcontextprotocol/server` ^2.0.0 (protocol revisions 2026-07-28 and 2025-*)
8
- **Zod:** ^4.4.3
8
+ **Zod:** ^4.6.4
9
9
 
10
10
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
11
11
 
@@ -37,6 +37,7 @@ Tailor suggestions to what's actually missing or stale — don't recite the full
37
37
  - **Use `ctx.state`** for tenant-scoped storage. Never access persistence directly.
38
38
  - **Need input the caller didn't supply?** `return ctx.requestInput(...)` and read `ctx.inputs` when the handler is re-entered. Never `await` for user input mid-handler.
39
39
  - **Secrets in env vars only** — never hardcoded.
40
+ - **Cut noise.** Add only what earns its place: no speculative generality, no guards for states the framework already prevents (Zod-validated params, classified errors), no abstraction until a third caller proves it, no option nothing sets.
40
41
  - **Close the loop on issues.** When implementing work tracked by a GitHub issue, comment on the issue with what landed and close it. Do both — a comment without a close leaves stale issues open; a close without a comment leaves no record of what shipped. The comment is for future readers — state the concrete changes, not the conversation that produced them.
41
42
 
42
43
  ---
@@ -145,7 +146,13 @@ export function getServerConfig() {
145
146
 
146
147
  ### Server identity and instructions
147
148
 
148
- This server's `createApp()` identity is the bare repository name in both `name` and `title`: `noaa-marine-mcp-server`. `instructions` is optional server-level orientation sent to clients on every `initialize`; use it for deployment guidance and cross-tool workflows instead of repeating the same context across tool descriptions.
149
+ This server's `createApp()` identity is the bare repository name in both `name` and `title`: `noaa-marine-mcp-server`. Never add `description` or `websiteUrl` — the framework derives canonical metadata from `package.json`, and a copy in `createApp()` is drift. `instructions` is optional server-level orientation sent to clients on every `initialize`; use it for deployment guidance and cross-tool workflows instead of repeating the same context across tool descriptions.
150
+
151
+ ### Session posture and shutdown
152
+
153
+ `createApp({ sessionMode: 'stateless' })` declares the HTTP session posture in `src/` rather than leaving it to a deployment's `MCP_SESSION_MODE`, which still wins whenever it carries a meaningful value (an empty string and an unsubstituted `${…}` placeholder read as unset and fall through to the option). Stateless is correct here: no tool gates on `ctx.requestInput`. A server that does gain one declares `{ default: 'stateful', require: 'stateful' }` so startup fails with a `ConfigurationError` instead of serving a mode a 2025-era HTTP client can never answer in. Stdio is never refused.
154
+
155
+ `teardown(core)` is the `setup()` counterpart — it releases a watcher, socket, or non-`unref()`'d timer after the transport stops and before the logger closes. Neither service here allocates one (both hold an in-memory station cache and nothing else), so the hook is unused; add it the moment a service acquires a handle it must close. `SIGTERM`/`SIGINT` exit the process explicitly once shutdown settles.
149
156
 
150
157
  ---
151
158
 
@@ -171,7 +178,7 @@ Handlers receive a unified `ctx` object. Key properties:
171
178
 
172
179
  Handlers throw — the framework catches, classifies, and formats.
173
180
 
174
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit hint when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`) bubble freely and don't need declaring.
181
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive `ctx.fail(reason, …)` typed against the reason union. TypeScript catches typos at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. `recovery` is required (≥ 5 words, lint-validated) — the single source of truth for the agent's next move. Pass `ctx.recoveryFor('reason')` as the throw's data to put it on the wire (`data.recovery.hint`, mirrored into `content[]` text); override with an explicit hint when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring.
175
182
 
176
183
  ```ts
177
184
  import { JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
@@ -253,9 +260,9 @@ src/
253
260
 
254
261
  ## Skills
255
262
 
256
- Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry.
263
+ Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant for those agents.
257
264
 
258
- **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
265
+ **Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
259
266
 
260
267
  Available skills:
261
268
 
@@ -274,8 +281,9 @@ Available skills:
274
281
  | `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
275
282
  | `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
276
283
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
277
- | `git-wrapup` | Land working-tree changes as a versioned commit + annotated tag — version bump, changelog, verify, tag. Local only. |
278
- | `release-and-publish` | Push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
284
+ | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
285
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixup commits autosquashed into the stack, PR body kept in sync. Release PR mode only |
286
+ | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
279
287
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
280
288
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
281
289
  | `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
@@ -294,7 +302,7 @@ Available skills:
294
302
  | `api-telemetry` | OTel catalog: spans, metrics, completion logs, env config, cardinality rules |
295
303
  | `api-workers` | Cloudflare Workers runtime |
296
304
 
297
- **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
305
+ **Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `framework-skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
298
306
 
299
307
  When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
300
308
 
@@ -310,9 +318,10 @@ When you complete a skill's checklist, check the boxes and add a completion time
310
318
  | `bun run rebuild` | Clean + build |
311
319
  | `bun run clean` | Remove build artifacts |
312
320
  | `bun run devcheck` | Lint + format + typecheck + security + changelog sync |
313
- | `bun run audit:refresh` | Delete `bun.lock`, reinstall, and re-run `bun audit`. Use when `devcheck` flags a transitive advisory — Bun's `update` is sticky on transitive resolutions, so the advisory may be a stale-lockfile false positive. If it survives the refresh, it's real. |
314
- | `bun run lint:mcp` | Run the MCP definition linter standalone |
315
- | `bun run lint:packaging` | Run packaging surface checks (also part of devcheck) |
321
+ | `bun run audit:fix` | `bun audit fix` — upgrade vulnerable packages to the lowest safe version within existing ranges (`--dry-run` previews, `--latest` rewrites ranges). First response when `devcheck` flags a transitive advisory; then `bun update <name>`, then `bun dedupe` |
322
+ | `bun run audit:refresh` | Delete `bun.lock` and reinstall. Last resort after `audit:fix`, `bun update <name>`, and `bun dedupe` — re-resolves every ranged dep (the framework pin included) and rewrites the lockfile as `lockfileVersion: 2` |
323
+ | `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
324
+ | `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, README version-badge parity (run by devcheck) |
316
325
  | `bun run list-skills` | Print the skill registry |
317
326
  | `bun run tree` | Generate directory structure doc |
318
327
  | `bun run format` | Auto-fix formatting (safe fixes only) |
@@ -328,11 +337,11 @@ When you complete a skill's checklist, check the boxes and add a completion time
328
337
 
329
338
  ## Bundling
330
339
 
331
- `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips dependency-shipped agent docs plus platform-specific native bindings that root-anchored `.mcpbignore` patterns cannot reach. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
340
+ `bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Cloudflare Workers deployments are unaffected. Consumers who don't need it can delete `manifest.json` and `.mcpbignore`; `lint:packaging` skips cleanly.
332
341
 
333
- **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match.
342
+ **Adding an env var requires both files:** `server.json` (registry discovery, `environmentVariables[]`) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `lint:packaging` (run by `devcheck`) verifies the env var names match, that every `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.X}"` (the host substitutes nothing else — `"${X}"` reaches the server as that literal string), and that an optional string option carries `"default": ""`.
334
343
 
335
- **README install badges** (Claude Desktop `.mcpb`, Cursor, VS Code) and the `base64` / `encodeURIComponent` config-generation commands are ship-time concerns — run the `polish-docs-meta` skill, which carries the badge format, layout, and generation snippets in `skills/polish-docs-meta/references/readme.md`.
344
+ **README install badges** (Claude Desktop `.mcpb`, Cursor, VS Code) and the `base64` / `encodeURIComponent` config-generation commands are ship-time concerns — run the `polish-docs-meta` skill, which carries the badge format, layout, and generation snippets in `framework-skills/polish-docs-meta/references/readme.md`.
336
345
 
337
346
  ---
338
347
 
@@ -353,16 +362,22 @@ security: false # optional — true ONLY for a source
353
362
  ...
354
363
  ```
355
364
 
356
- `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's own source code, never for a routine dependency or transitive CVE bump. When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
365
+ `breaking: true` renders a `· ⚠️ Breaking` badge — use it when consumers must update code on upgrade (signature changes, removed APIs, config renames). `security: true` renders a `· 🛡️ Security` badge and pairs with a `## Security` body section — set it only for a security fix in this server's *own source code*, never for a routine dependency or transitive CVE bump (record those under `## Dependencies`). When both are set, badges render `· ⚠️ Breaking · 🛡️ Security`.
357
366
 
358
367
  `agent-notes` is an optional free-form field for maintenance agents processing the release downstream. Content here won't appear in the rendered CHANGELOG — it's consumed by agents running the `maintenance` skill. Use it for adoption instructions that don't fit the human-facing sections: new files to create, fields to populate, one-time migration steps. Omit entirely when there's nothing to say.
359
368
 
360
- **Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
369
+ **Section order:** the Keep a Changelog sequence — Added, Changed, Deprecated, Removed, Fixed, Security — then `Dependencies` last. Include only sections with entries — don't ship empty headers.
361
370
 
362
371
  **Tag annotations** render as GitHub Release bodies via `--notes-from-tag`. They must be structured markdown — never a flat comma-separated string. Subject omits the version number (GitHub prepends it). See `changelog/template.md` for the full format reference.
363
372
 
364
373
  ---
365
374
 
375
+ ## Publishing
376
+
377
+ **Every release goes through a release PR, straight-through** — `git-wrapup`'s "Release PR mode", mode `straight-through`. One run: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-and-publish` then fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. A caller's brief may run a given release as `gated` instead — a `release-pr-review` pass on the open PR before `release-and-publish`. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history.
378
+
379
+ ---
380
+
366
381
  ## Imports
367
382
 
368
383
  ```ts
@@ -389,7 +404,7 @@ import { getMyService } from '@/services/my-domain/my-service.js';
389
404
  - [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
390
405
  - [ ] Registered in `createApp()` arrays (directly or via barrel exports)
391
406
  - [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
392
- - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = package name; `interface.shortDescription` from `package.json` description
393
- - [ ] `.codex-plugin/mcp.json` updated — server name key matches `package.json` name; env vars added for any required API keys
394
- - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry with server name key, env vars for any required API keys
407
+ - [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description
408
+ - [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset
409
+ - [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — mirror the `user_config` block in `manifest.json`. Never write `"KEY": ""` into `env`
395
410
  - [ ] `bun run devcheck` passes
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-0.3.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/noaa-marine-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/noaa-marine-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/noaa-marine-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-0.3.2-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/noaa-marine-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/noaa-marine-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/noaa-marine-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
@@ -27,128 +27,120 @@
27
27
 
28
28
  ---
29
29
 
30
- ## Tools
30
+ ## Overview
31
31
 
32
- Seven tools covering the full US marine operational workflow — station discovery, tide predictions, observed water levels, tidal current predictions and profiles, and live offshore buoy conditions:
32
+ US tide, current, and buoy data from NOAA CO-OPS and NDBC. Find tide, water-level, and current stations plus NDBC buoys, then fetch tide predictions, observed water levels, tidal currents, and live buoy conditions from any MCP client. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
33
+
34
+ ### Tools
33
35
 
34
36
  | Tool | Description |
35
37
  |:-----|:------------|
36
- | `noaa_marine_find_stations` | Find CO-OPS tide/water-level/current stations and NDBC buoys near a location or by name/state. Required first step to resolve place names or coordinates to station IDs. |
37
- | `noaa_marine_get_tide_predictions` | High/low tide predictions for a CO-OPS tide station over a date range. Supports 6-minute interval output and multiple datums (defaults to MLLW — US nautical chart standard). |
38
- | `noaa_marine_get_water_level` | Observed water level (real-time or historical) for a CO-OPS station, paired with predictions to compute storm surge or anomalous drawdown. |
39
- | `noaa_marine_get_currents` | Tidal current predictions for a CO-OPS current station: max flood/ebb speeds, slack times, and directions. Defaults to MAX_SLACK (practical passage-planning view). |
40
- | `noaa_marine_get_conditions` | Live marine conditions from an NDBC buoy: wave height/period/direction, wind, sea-surface temp, air temp, and barometric pressure. |
41
- | `noaa_marine_get_current_profile` | Observed ocean-current depth profile from an NDBC ADCP buoy: speed and direction at each depth bin. Distinct from `noaa_marine_get_currents`, which is a CO-OPS tidal-current forecast. |
42
- | `noaa_marine_get_ocean_observations` | Sub-surface water-column observations from an NDBC station: water temperature, salinity, dissolved oxygen, chlorophyll, turbidity, and pH at each reported depth. The water-column counterpart to `noaa_marine_get_conditions` (surface weather and sea state). |
38
+ | `noaa_marine_find_stations` | Find CO-OPS tide/water-level/current stations and NDBC buoys by location, name, state, or data capability. |
39
+ | `noaa_marine_get_tide_predictions` | High/low tide predictions or a 6-minute curve for a CO-OPS tide station. |
40
+ | `noaa_marine_get_water_level` | Observed water level paired with predictions for a CO-OPS station, with a storm-surge residual summary. |
41
+ | `noaa_marine_get_currents` | CO-OPS tidal current predictions — max flood/ebb/slack events or a 6-minute curve. |
42
+ | `noaa_marine_get_conditions` | Live NDBC buoy conditions: waves, wind, sea-surface and air temperature, pressure. |
43
+ | `noaa_marine_get_current_profile` | Observed ocean-current depth profile from an NDBC ADCP buoy. |
44
+ | `noaa_marine_get_ocean_observations` | Sub-surface water-column observations (temperature, salinity, oxygen, and more) from an NDBC station. |
43
45
 
44
- ### `noaa_marine_find_stations`
46
+ ### Resources
45
47
 
46
- Unified station discovery across CO-OPS (3,450+ tide/water-level stations, 4,430+ current stations) and NDBC (1,354+ active buoys worldwide).
48
+ | Resource | Description |
49
+ |:---|:---|
50
+ | `noaa-marine://station/{station_id}` | Metadata for a CO-OPS or NDBC station by ID: name, coordinates, source, data capabilities, and — for NDBC — physical platform class. |
47
51
 
48
- - Filter by proximity (latitude/longitude + radius), name substring, state/territory, source (CO-OPS vs NDBC), or `types`: data capabilities (tide, current, water_level, met, current_profile) or NDBC platform class (buoy)
49
- - Returns a unified station list with source, coordinates, distance, data capabilities, and — for NDBC — the physical platform class (buoy, fixed, oilrig, dart, tao, usv, other)
50
- - Station lists are cached in-memory (6-hour TTL) — first call after startup may be slightly slower
51
- - Required first step: CO-OPS and NDBC use non-overlapping ID systems; guessing a station ID reliably fails
52
+ All resource data is also reachable via tools — use `noaa_marine_find_stations` to discover station IDs before accessing the resource.
52
53
 
53
- ---
54
+ ## Capability reference
55
+
56
+ ### `noaa_marine_find_stations` <sub>tool</sub>
54
57
 
55
- ### `noaa_marine_get_tide_predictions`
58
+ - Filter by proximity (`latitude`/`longitude` + `radius_km`, default 100 km, max 1000 km), name/ID substring, US state/territory (CO-OPS only), source (`coops`/`ndbc`/`all`), or `types`: data capabilities (`tide`, `current`, `water_level`, `met`, `current_profile`) or NDBC platform class (`buoy`)
59
+ - Returns up to `limit` (default 20, max 200) unified stations with source, coordinates, distance, data capabilities, and — for NDBC — physical platform class (buoy, fixed, oilrig, dart, tao, usv, other)
60
+ - `total_found` and `truncated` report the full match count before the limit is applied
61
+ - Station lists are cached in-memory with a 6-hour TTL — first call after startup may be slightly slower
62
+ - Typed `incomplete_coordinates` error when only one of latitude/longitude is supplied; `no_results` when nothing matches
56
63
 
57
- CO-OPS MLLW tide predictions for planning tidal windows.
64
+ ---
65
+
66
+ ### `noaa_marine_get_tide_predictions` <sub>tool</sub>
58
67
 
59
- - High/low events (default) or 6-minute continuous curve
68
+ - `hilo` (default, high/low events) or `6min` continuous curve; up to 1 year per request
60
69
  - Eight datums: MLLW (default, US nautical chart), MHHW, MSL, MTL, MHW, MLW, CD, STND
61
- - Time zone options: local standard/daylight (default), GMT, local standard only
62
- - Units: English (feet, default) or metric (meters)
63
- - Maximum date range: 1 year per request (typed error `date_range_exceeded` for longer ranges)
70
+ - Time zone (`lst_ldt` default, `gmt`, `lst`) and units (`english` default feet, `metric` meters)
71
+ - Typed `date_range_exceeded`, `invalid_date_range`, `station_not_found`, and `no_predictions` errors
64
72
 
65
73
  ---
66
74
 
67
- ### `noaa_marine_get_water_level`
68
-
69
- Observed water level vs. predicted — the storm surge view.
75
+ ### `noaa_marine_get_water_level` <sub>tool</sub>
70
76
 
71
- - 6-minute observed water level readings with quality flags
72
- - Paired tide predictions fetched in parallel (failure degrades gracefully — observed levels still returned)
73
- - Optional residual summary: max surge and max drawdown when both series are present
74
- - Maximum date range: 31 days per request for 6-minute data
77
+ - 6-minute observed water level with quality flags (`p` preliminary, `v` verified) and optional sensor `sigma`
78
+ - Paired 6-minute tide predictions fetched in parallel — failure degrades gracefully, observed levels still return
79
+ - `residual_summary` (max surge, max drawdown) only when both series are present
80
+ - Up to 31 days per request; typed `date_range_exceeded`, `station_not_found`, and `no_data` errors
75
81
 
76
82
  ---
77
83
 
78
- ### `noaa_marine_get_currents`
84
+ ### `noaa_marine_get_currents` <sub>tool</sub>
79
85
 
80
- CO-OPS tidal current predictions for passage planning.
81
-
82
- - MAX_SLACK interval (default): max flood, max ebb, and slack events only — the actionable view for transiting inlets and channels
83
- - 6-minute interval: full continuous current curve for charting or integration
84
- - Current station IDs use alphanumeric format (e.g., `ACT4176`), distinct from numeric tide station IDs — use `find_stations` with `types: ["current"]` to discover them
86
+ - `MAX_SLACK` (default): max flood, max ebb, and slack events only — the actionable view for passage planning
87
+ - `6min`: continuous current curve; units `english` (knots, default) or `metric` (m/s)
88
+ - Current station IDs are alphanumeric (e.g. `ACT4176`), distinct from numeric tide/water-level IDs
89
+ - Up to 1 year per request; typed `date_range_exceeded`, `invalid_date_range`, `station_not_found`, and `no_predictions` errors
85
90
 
86
91
  ---
87
92
 
88
- ### `noaa_marine_get_conditions`
89
-
90
- Live NDBC buoy observations (most recent ~45 days, updated every 10 minutes).
93
+ ### `noaa_marine_get_conditions` <sub>tool</sub>
91
94
 
92
- - Wave height (m), dominant and average period (sec), mean wave direction
93
- - Wind speed and gust (m/s), wind direction
94
- - Sea-surface temperature, air temperature, dew point (°C)
95
- - Barometric pressure (hPa)
96
- - All sensor fields nullable (`null` when buoy sensor did not report — normal for offshore buoys)
97
- - All values in SI units except `TIDE` (feet) and `VIS` (nautical miles), which are rarely populated at offshore buoys
95
+ - Wave height/period/direction, wind speed/gust/direction, sea-surface and air temperature, dew point, barometric pressure
96
+ - All values SI except `tide_ft` (feet) and `visibility_nmi` (nautical miles), both rarely populated at offshore buoys
97
+ - Every sensor field is nullable — `null` when the buoy did not report, never a fabricated value
98
+ - Updated roughly every 10 minutes; typed `buoy_not_found` and `no_sensor_data` errors
98
99
 
99
100
  ---
100
101
 
101
- ### `noaa_marine_get_current_profile`
102
-
103
- Observed ocean-current depth profile from an NDBC ADCP buoy — the most recent measurement at each depth bin.
102
+ ### `noaa_marine_get_current_profile` <sub>tool</sub>
104
103
 
105
- - Depth (m), direction (degrees true, the direction the current flows toward), and speed (cm/s) per bin
106
- - Distinct from `noaa_marine_get_currents`: this is an NDBC *observed* acoustic-Doppler measurement, not a CO-OPS tidal-current *prediction*
107
- - Most NDBC stations serve no ADCP profile — use `find_stations` with `source="ndbc"` and `types: ["current_profile"]` to discover the ones that do
108
- - Direction or speed is `null` for a bin when NDBC did not report that component
104
+ - Depth (m), direction (degrees true, flow-toward), and speed (cm/s) per bin, shallowest first
105
+ - Observed NDBC ADCP measurement — distinct from `noaa_marine_get_currents`, a CO-OPS tidal-current *prediction*
106
+ - Most NDBC stations serve no ADCP profile; use `find_stations` with `types: ["current_profile"]` to discover ones that do
107
+ - Direction or speed is `null` per bin when the sensor did not report that component; typed `profile_not_found` and `no_current_data` errors
109
108
 
110
- ### `noaa_marine_get_ocean_observations`
109
+ ---
111
110
 
112
- Sub-surface water-column observations from an NDBC station — the most recent reading at each reported depth.
111
+ ### `noaa_marine_get_ocean_observations` <sub>tool</sub>
113
112
 
114
- - Water temperature (°C), conductivity (mS/cm), salinity (psu), dissolved oxygen (% saturation and ppm), chlorophyll (µg/l), turbidity (FTU), pH, and redox potential (mV) per depth
115
- - The water-column counterpart to `noaa_marine_get_conditions`: this reports what the water is doing below the surface, that one reports surface weather and sea state
116
- - Sensor coverage is sparse — most stations report only temperature and salinity; any value the station did not report comes back `null`, never a fabricated zero
117
- - Sub-surface sensors are on only a subset of NDBC stations and carry no station-catalog flag, so there is no capability filter — call it on candidate `source="ndbc"` station IDs and expect the `observations_not_found` error on the many stations that serve no ocean file
113
+ - Water temperature, conductivity, salinity, dissolved oxygen (% and ppm), chlorophyll, turbidity, pH, and redox potential per depth
114
+ - Water-column counterpart to `noaa_marine_get_conditions` (surface weather and sea state)
115
+ - Sensor coverage is sparse — most stations report only temperature and salinity; unreported values are `null`, never a fabricated zero
116
+ - No capability filter identifies ocean-sensor coverage — call on candidate `source="ndbc"` IDs and expect `observations_not_found` on stations with no `.ocean` file
118
117
 
119
- ## Resources and prompts
118
+ ---
120
119
 
121
- | Type | Name | Description |
122
- |:-----|:-----|:------------|
123
- | Resource | `noaa-marine://station/{station_id}` | Metadata for a CO-OPS or NDBC station by ID: name, coordinates, source, data capabilities, state, and — for NDBC — physical platform class. |
120
+ ### `noaa-marine://station/{station_id}` <sub>resource</sub>
124
121
 
125
- All resource data is also reachable via tools. Use `noaa_marine_find_stations` to discover station IDs before accessing the resource.
122
+ - Station record as `application/json` — name, coordinates, source, capabilities, state, and (NDBC) platform class
123
+ - `station_id` comes from `noaa_marine_find_stations`
124
+ - Cached with a 6-hour TTL (`cacheHint`)
126
125
 
127
126
  ## Features
128
127
 
129
- Built on [`@cyanheads/mcp-ts-core`](https://www.npmjs.com/package/@cyanheads/mcp-ts-core):
130
-
131
- - Declarative tool and resource definitions — single file per primitive, framework handles registration and validation
132
- - Unified error handling — handlers throw, framework catches, classifies, and formats with typed error contracts
133
- - Structured logging with optional OpenTelemetry tracing
134
- - STDIO and Streamable HTTP transports
135
- - MCP 2026 cache hints for static definition lists and station metadata
136
- - Pluggable auth: `none`, `jwt`, `oauth`
128
+ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
137
129
 
138
- NOAA-specific:
130
+ CO-OPS / NDBC-specific:
139
131
 
140
132
  - In-memory station cache (6-hour TTL) for CO-OPS and NDBC station lists — discovery is fast after first startup
141
133
  - CO-OPS and NDBC integrated in a unified station model — `find_stations` fans out across both sources in parallel
142
- - NDBC fixed-width text parser: `MM` (missing sensor data) normalized to `null`, not passed through as strings
143
- - Paired water-level + prediction fetches for storm surge residual computation
134
+ - NDBC fixed-width text parser normalizes `MM` (missing sensor data) to `null`, never passes it through as a string
135
+ - Paired water-level and prediction fetches for storm-surge residual computation
144
136
  - CO-OPS `application=` courtesy parameter sent on every request (configurable via `NOAA_APPLICATION_ID`)
145
137
 
146
138
  Agent-friendly output:
147
139
 
148
- - Datum echoed on every tide/water-level response — agents can state units and reference correctly without assumptions
149
- - `total_found` on `find_stations` shows count before `limit` slice so agents know whether to re-query
150
- - All NDBC sensor fields explicitly nullable with per-field unit documentation — agents don't fabricate missing readings
151
- - Typed station source (`coops` | `ndbc`) on every station record, plus a data-capability `type` and (NDBC only) a `platform` class where applicable — agents can branch on data, not string parsing
140
+ - Datum echoed on every tide/water-level response so agents state units and reference correctly without assumptions
141
+ - `total_found` on `find_stations` shows the count before the `limit` slice, so agents know whether to re-query
142
+ - All NDBC sensor fields explicitly nullable — agents don't fabricate missing readings
143
+ - Typed station `source` (`coops` | `ndbc`) plus a data-capability `type` and (NDBC only) a `platform` class — agents branch on data, not string parsing
152
144
 
153
145
  ## Getting started
154
146
 
@@ -169,7 +161,7 @@ Connect directly via Streamable HTTP — no install, no API key:
169
161
  }
170
162
  ```
171
163
 
172
- ### Self-hosted / local
164
+ ### Self-Hosted / Local
173
165
 
174
166
  Add the following to your MCP client configuration file:
175
167
 
@@ -272,7 +264,7 @@ cp .env.example .env
272
264
  | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
273
265
  | `MCP_HTTP_PORT` | Port for HTTP server. | `3010` |
274
266
  | `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
275
- | `MCP_SESSION_MODE` | HTTP session mode: `auto`, `stateful`, or `stateless`. This server pins `stateless`; the framework's `auto` schema default resolves to `stateful`. | `stateless` |
267
+ | `MCP_SESSION_MODE` | HTTP session mode: `auto`, `stateful`, or `stateless`. The server declares `stateless` in `src/index.ts`; set this to override. | `stateless` |
276
268
  | `MCP_LOG_LEVEL` | Log level (RFC 5424). | `info` |
277
269
  | `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
278
270
  | `OTEL_ENABLED` | Enable [OpenTelemetry instrumentation](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry). | `false` |
@@ -335,7 +327,7 @@ See [`CLAUDE.md`/`AGENTS.md`](./CLAUDE.md) for development guidelines and archit
335
327
 
336
328
  ## Contributing
337
329
 
338
- Issues and pull requests are welcome. Run checks and tests before submitting:
330
+ Issues are welcome. Run checks and tests before submitting:
339
331
 
340
332
  ```sh
341
333
  bun run devcheck
@@ -0,0 +1,30 @@
1
+ ---
2
+ summary: "Adopts mcp-ts-core 0.13.2: sessionMode is declared in src/index.ts rather than left to MCP_SESSION_MODE, argument rejections carry the structured error envelope, a CO-OPS/NDBC 500 now retries, and the framework skill tree moves to framework-skills/."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.3.2 — 2026-09-16
8
+
9
+ ## Added
10
+
11
+ - **`createApp({ sessionMode: 'stateless' })`** declares the HTTP session posture in `src/index.ts` instead of relying on the `MCP_SESSION_MODE` env default; the `/.well-known/mcp.json` server card now publishes the resolved mode under `_meta` key `io.github.cyanheads.mcp-ts-core/sessionMode`.
12
+ - **`bun run audit:fix`** (`bun audit fix`) — first response to a transitive advisory, ahead of `bun update <name>`, `bun dedupe`, and `audit:refresh`.
13
+
14
+ ## Changed
15
+
16
+ - **Framework** `@cyanheads/mcp-ts-core` `0.12.3` → `^0.13.2`.
17
+ - **Argument rejections carry `structuredContent.error`** — an invalid tool call now returns `isError: true` with `error.code` `-32602` alongside the existing readable text.
18
+ - **A CO-OPS/NDBC upstream 500 retries** instead of failing on the first attempt; a 501 carries `data.retryable: false` (cyanheads/mcp-ts-core#323).
19
+ - **`NOAA_APPLICATION_ID`** reads an empty string or an unsubstituted `${…}` placeholder as unset, falling back to the `noaa-marine-mcp-server` default instead of sending the literal text (cyanheads/mcp-ts-core#427).
20
+ - **`SIGTERM`/`SIGINT` end the process explicitly** — exit `0` once shutdown settles, `1` on the 10s ceiling.
21
+ - **Bun engines floor raised to `>=1.4.0`.**
22
+ - **Framework skill tree moved `skills/` → `framework-skills/`** so the `.mcpb` bundle and plugin installs no longer ship development skills (cyanheads/mcp-ts-core#428).
23
+ - **Repo docs refreshed** — README restructured around an Overview and a per-primitive capability reference; issue forms, `CONTRIBUTING.md`, and `.claude-plugin/plugin.json` (`$schema`, keywords) updated for the current conventions.
24
+
25
+ ## Dependencies
26
+
27
+ - `zod` `^4.4.3` → `^4.6.4`
28
+ - `@biomejs/biome` `^2.5.10` → `^2.5.13`
29
+ - `ignore` `^7.0.6` → `^7.0.9`
30
+ - `tsc-alias` `^1.9.2` → `^1.9.5`
@@ -117,30 +117,13 @@ security: false
117
117
  in that unrelated item's metadata.
118
118
 
119
119
  TAG ANNOTATIONS — the annotated tag body renders as the GitHub Release body
120
- via `gh release create --notes-from-tag`. The tag is a derivative of this
121
- changelog entry — a condensed, scannable version, not a copy. Format:
122
-
123
- <theme — omit version number, GitHub prepends it>
124
- ← blank line
125
- <1-2 sentence context: what this release does>
126
- ← blank line
127
- Dependency bumps: ← section header
128
- ← blank line
129
- - `@cyanheads/mcp-ts-core` ^0.9.1 → ^0.9.6 ← bullet
130
- ← blank line
131
- Changed: ← only sections with entries
132
- ← blank line
133
- - `format()` output includes `query` in text mode
134
- ← blank line
135
- Added:
136
- ← blank line
137
- - `manifest.json` scaffolded for MCPB bundle support
138
- - Install badges (Claude Desktop, Cursor, VS Code)
139
- ← blank line
140
- <N> tests pass; `bun run devcheck` clean. ← footer
141
-
142
- Never a flat comma-separated string. Always structured markdown with
143
- sections. The tag must scan well as a rendered GitHub Release page.
120
+ via `gh release create --notes-from-tag`. It is a condensed digest of this
121
+ entry, never a copy, and its format is owned by the `release-and-publish`
122
+ skill (step 4, "Create the annotated tag"): the entry's `summary:` as the
123
+ theme line without the version, flat headline bullets — no Keep-a-Changelog
124
+ section headers, no gates line — at most one deps line, issue backlinks,
125
+ and the changelog link last. In release-PR mode the `git-wrapup` skill
126
+ authors that digest as the PR body's `## Changes` and the tag copies it.
144
127
  -->
145
128
 
146
129
  ## Added
package/dist/index.js CHANGED
@@ -18,6 +18,7 @@ import { initNdbcService } from './services/ndbc/ndbc-service.js';
18
18
  await createApp({
19
19
  name: 'noaa-marine-mcp-server',
20
20
  title: 'noaa-marine-mcp-server',
21
+ sessionMode: 'stateless',
21
22
  tools: [
22
23
  noaaMarineFindStations,
23
24
  noaaMarineGetTidePredictions,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oEAAoE,CAAC;AAC/G,OAAO,EAAE,sBAAsB,EAAE,MAAM,kEAAkE,CAAC;AAC1G,OAAO,EAAE,uBAAuB,EAAE,MAAM,mEAAmE,CAAC;AAC5G,OAAO,EAAE,2BAA2B,EAAE,MAAM,wEAAwE,CAAC;AACrH,OAAO,EAAE,qBAAqB,EAAE,MAAM,iEAAiE,CAAC;AACxG,OAAO,EAAE,8BAA8B,EAAE,MAAM,2EAA2E,CAAC;AAC3H,OAAO,EAAE,4BAA4B,EAAE,MAAM,yEAAyE,CAAC;AACvH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oEAAoE,CAAC;AAC7G,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,wBAAwB;IAC9B,KAAK,EAAE,wBAAwB;IAC/B,KAAK,EAAE;QACL,sBAAsB;QACtB,4BAA4B;QAC5B,uBAAuB;QACvB,qBAAqB;QACrB,uBAAuB;QACvB,2BAA2B;QAC3B,8BAA8B;KAC/B;IACD,SAAS,EAAE,CAAC,yBAAyB,CAAC;IACtC,OAAO,EAAE,EAAE;IACX,UAAU,EAAE;QACV,YAAY,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;QACzD,gBAAgB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;QAC7D,0BAA0B,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;KACxE;IACD,YAAY,EACV,iDAAiD;QACjD,qFAAqF;QACrF,uCAAuC;QACvC,uEAAuE;QACvE,uEAAuE;QACvE,4DAA4D;QAC5D,mGAAmG;QACnG,2EAA2E;QAC3E,qIAAqI;QACrI,mGAAmG;QACnG,gFAAgF;QAChF,mFAAmF;IAErF,KAAK,CAAC,IAAI;QACR,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;QACvC,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAC1D,eAAe,EAAE,CAAC;IACpB,CAAC;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;GAGG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,MAAM,oEAAoE,CAAC;AAC/G,OAAO,EAAE,sBAAsB,EAAE,MAAM,kEAAkE,CAAC;AAC1G,OAAO,EAAE,uBAAuB,EAAE,MAAM,mEAAmE,CAAC;AAC5G,OAAO,EAAE,2BAA2B,EAAE,MAAM,wEAAwE,CAAC;AACrH,OAAO,EAAE,qBAAqB,EAAE,MAAM,iEAAiE,CAAC;AACxG,OAAO,EAAE,8BAA8B,EAAE,MAAM,2EAA2E,CAAC;AAC3H,OAAO,EAAE,4BAA4B,EAAE,MAAM,yEAAyE,CAAC;AACvH,OAAO,EAAE,uBAAuB,EAAE,MAAM,oEAAoE,CAAC;AAC7G,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACrE,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAElE,MAAM,SAAS,CAAC;IACd,IAAI,EAAE,wBAAwB;IAC9B,KAAK,EAAE,wBAAwB;IAC/B,WAAW,EAAE,WAAW;IACxB,KAAK,EAAE;QACL,sBAAsB;QACtB,4BAA4B;QAC5B,uBAAuB;QACvB,qBAAqB;QACrB,uBAAuB;QACvB,2BAA2B;QAC3B,8BAA8B;KAC/B;IACD,SAAS,EAAE,CAAC,yBAAyB,CAAC;IACtC,OAAO,EAAE,EAAE;IACX,UAAU,EAAE;QACV,YAAY,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;QACzD,gBAAgB,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;QAC7D,0BAA0B,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE;KACxE;IACD,YAAY,EACV,iDAAiD;QACjD,qFAAqF;QACrF,uCAAuC;QACvC,uEAAuE;QACvE,uEAAuE;QACvE,4DAA4D;QAC5D,mGAAmG;QACnG,2EAA2E;QAC3E,qIAAqI;QACrI,mGAAmG;QACnG,gFAAgF;QAChF,mFAAmF;IAErF,KAAK,CAAC,IAAI;QACR,MAAM,YAAY,GAAG,eAAe,EAAE,CAAC;QACvC,gBAAgB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAC1D,eAAe,EAAE,CAAC;IACpB,CAAC;CACF,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/noaa-marine-mcp-server",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "mcpName": "io.github.cyanheads/noaa-marine-mcp-server",
5
5
  "description": "Find NOAA tide stations and NDBC buoys, fetch tide predictions, water levels, tidal currents, and live buoy conditions via MCP. STDIO or Streamable HTTP.",
6
6
  "type": "module",
@@ -24,6 +24,7 @@
24
24
  "rebuild": "bun run scripts/clean.ts && bun run scripts/build.ts",
25
25
  "clean": "bun run scripts/clean.ts",
26
26
  "devcheck": "bun run scripts/devcheck.ts",
27
+ "audit:fix": "bun audit fix",
27
28
  "audit:refresh": "rm -f bun.lock && bun install && bun audit",
28
29
  "tree": "bun run scripts/tree.ts",
29
30
  "list-skills": "bun run scripts/list-skills.ts",
@@ -82,24 +83,24 @@
82
83
  "license": "Apache-2.0",
83
84
  "packageManager": "bun@1.4.0",
84
85
  "engines": {
85
- "bun": ">=1.3.0",
86
+ "bun": ">=1.4.0",
86
87
  "node": ">=24.0.0"
87
88
  },
88
89
  "publishConfig": {
89
90
  "access": "public"
90
91
  },
91
92
  "dependencies": {
92
- "@cyanheads/mcp-ts-core": "0.12.3",
93
+ "@cyanheads/mcp-ts-core": "^0.13.2",
93
94
  "pino-pretty": "^13.1.3",
94
- "zod": "^4.4.3"
95
+ "zod": "^4.6.4"
95
96
  },
96
97
  "devDependencies": {
97
- "@biomejs/biome": "^2.5.10",
98
+ "@biomejs/biome": "^2.5.13",
98
99
  "@socketsecurity/bun-security-scanner": "^1.1.2",
99
100
  "@types/node": "^26.2.0",
100
101
  "depcheck": "^1.4.7",
101
- "ignore": "^7.0.6",
102
- "tsc-alias": "^1.9.2",
102
+ "ignore": "^7.0.9",
103
+ "tsc-alias": "^1.9.5",
103
104
  "typescript": "^7.0.2",
104
105
  "vitest": "^4.1.11"
105
106
  }
package/server.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "url": "https://github.com/cyanheads/noaa-marine-mcp-server",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.3.1",
9
+ "version": "0.3.2",
10
10
  "remotes": [
11
11
  {
12
12
  "type": "streamable-http",
@@ -19,7 +19,7 @@
19
19
  "registryBaseUrl": "https://registry.npmjs.org",
20
20
  "identifier": "@cyanheads/noaa-marine-mcp-server",
21
21
  "runtimeHint": "bun",
22
- "version": "0.3.1",
22
+ "version": "0.3.2",
23
23
  "packageArguments": [
24
24
  {
25
25
  "type": "positional",
@@ -55,7 +55,7 @@
55
55
  "registryBaseUrl": "https://registry.npmjs.org",
56
56
  "identifier": "@cyanheads/noaa-marine-mcp-server",
57
57
  "runtimeHint": "bun",
58
- "version": "0.3.1",
58
+ "version": "0.3.2",
59
59
  "packageArguments": [
60
60
  {
61
61
  "type": "positional",