@cyanheads/noaa-marine-mcp-server 0.6.1 → 0.7.0
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 +33 -16
- package/CLAUDE.md +33 -16
- package/Dockerfile +9 -3
- package/README.md +23 -97
- package/changelog/0.6.x/0.6.2.md +27 -0
- package/changelog/0.7.x/0.7.0.md +28 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.js +14 -8
- package/dist/mcp-server/tools/definitions/noaa-marine-find-stations.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.js +2 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-conditions.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-current-profile.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-current-profile.tool.js +2 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-current-profile.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.js +7 -3
- package/dist/mcp-server/tools/definitions/noaa-marine-get-currents.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-monthly-means.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-monthly-means.tool.js +3 -2
- package/dist/mcp-server/tools/definitions/noaa-marine-get-monthly-means.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-ocean-observations.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-ocean-observations.tool.js +2 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-ocean-observations.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.js +4 -3
- package/dist/mcp-server/tools/definitions/noaa-marine-get-tide-predictions.tool.js.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.d.ts.map +1 -1
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.js +5 -4
- package/dist/mcp-server/tools/definitions/noaa-marine-get-water-level.tool.js.map +1 -1
- package/package.json +11 -10
- package/server.json +3 -3
- package/skills/noaa-marine-guide/SKILL.md +63 -0
package/AGENTS.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# Developer Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** noaa-marine-mcp-server
|
|
4
|
-
**Version:** 0.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.
|
|
4
|
+
**Version:** 0.7.0
|
|
5
|
+
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
|
-
**MCP SDK:** `@modelcontextprotocol/server` ^2.2.0
|
|
8
|
-
**Zod:** ^4.6.
|
|
7
|
+
**MCP SDK:** `@modelcontextprotocol/server` ^2.4.0 (resolved 2.4.0; protocol revisions 2026-07-28 and 2025-*)
|
|
8
|
+
**Zod:** ^4.6.7
|
|
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
|
|
|
@@ -151,6 +151,8 @@ export function getServerConfig() {
|
|
|
151
151
|
|
|
152
152
|
`parseEnvConfig` maps Zod schema paths → env var names so errors name the variable (`NOAA_APPLICATION_ID`) not the path (`applicationId`). Throws `ConfigurationError`, which the framework prints as a clean startup banner.
|
|
153
153
|
|
|
154
|
+
For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag cannot be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else.
|
|
155
|
+
|
|
154
156
|
### Server identity and instructions
|
|
155
157
|
|
|
156
158
|
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.
|
|
@@ -159,7 +161,7 @@ This server's `createApp()` identity is the bare repository name in both `name`
|
|
|
159
161
|
|
|
160
162
|
`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.
|
|
161
163
|
|
|
162
|
-
`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.
|
|
164
|
+
`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 (0, or 1 if a step does not settle within the framework's 10-second ceiling).
|
|
163
165
|
|
|
164
166
|
---
|
|
165
167
|
|
|
@@ -169,13 +171,13 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
169
171
|
|
|
170
172
|
| Property | Description |
|
|
171
173
|
|:---------|:------------|
|
|
172
|
-
| `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino and `notifications/message
|
|
174
|
+
| `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino **and** `notifications/message` to the client, so treat it as client-visible. |
|
|
173
175
|
| `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any JSON-serializable value; reads return its JSON form (a `Date` comes back as an ISO string). |
|
|
174
|
-
| `ctx.requestInput` | Suspend and ask the caller for more input.
|
|
175
|
-
| `ctx.inputs` | The request's responses, limited to what the client declared (`elicitation` and its form/url modes, `sampling`, `roots`). Client-supplied: a consent gate trusts only a `ctx.state` record it stored when it asked (see the `api-context` skill). |
|
|
176
|
-
| `ctx.clientCapabilities` | What the client declared for this request, `undefined` when no view exists.
|
|
177
|
-
| `ctx.enrich` | Success-path agent context
|
|
178
|
-
| `ctx.content` | Non-text image
|
|
176
|
+
| `ctx.requestInput` | Suspend and ask the caller for more input — `return ctx.requestInput({ inputRequests: { key: inputRequired.elicit({ message, requestedSchema }) } })`. Never returns; the handler is re-entered with the answers. Always present. |
|
|
177
|
+
| `ctx.inputs` | The request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped` — limited to what the client declared (`elicitation` and its form/url modes, `sampling`, `roots`). Client-supplied: a consent gate trusts only a `ctx.state` record it stored when it asked, bound to the operation, caller, and target (see the `api-context` skill). |
|
|
178
|
+
| `ctx.clientCapabilities` | What the client declared for this request, `undefined` when no view exists. Decides whether to ask for optional context (e.g. roots); never a reason to skip a consent prompt. |
|
|
179
|
+
| `ctx.enrich` | Success-path agent context (empty-result notices, query echo, pagination totals) — `ctx.enrich(...)` or `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches `structuredContent` and `content[]`; lands only when the definition declares an `enrichment` block (no-op otherwise). |
|
|
180
|
+
| `ctx.content` | Non-text content blocks — `.image(data, mimeType)`, `.audio(data, mimeType)`, or `ctx.content(block)` for a raw block. Prepended to `content[]` after `format()`; never enters `structuredContent`. |
|
|
179
181
|
| `ctx.signal` | `AbortSignal` for cancellation. |
|
|
180
182
|
| `ctx.requestId` | Request ID — the one every log record of the call carries and its error envelope returns as `data.requestId`. |
|
|
181
183
|
| `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
|
|
@@ -219,7 +221,7 @@ throw new Error('Invalid query format'); // → ValidationError
|
|
|
219
221
|
|
|
220
222
|
// McpError — when no factory exists for the code
|
|
221
223
|
import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
222
|
-
throw new McpError(JsonRpcErrorCode.
|
|
224
|
+
throw new McpError(JsonRpcErrorCode.InitializationFailed, 'Connection failed', { pool: 'primary' });
|
|
223
225
|
```
|
|
224
226
|
|
|
225
227
|
See framework CLAUDE.md and the `api-errors` skill for the full auto-classification table, all available factories, and the contract reference.
|
|
@@ -274,9 +276,18 @@ src/
|
|
|
274
276
|
|
|
275
277
|
## Skills
|
|
276
278
|
|
|
277
|
-
|
|
279
|
+
Two skill trees live at the project root, written for different readers:
|
|
280
|
+
|
|
281
|
+
| Tree | For | Written by |
|
|
282
|
+
|:-----|:----|:-----------|
|
|
283
|
+
| `framework-skills/` | The agent developing this server — framework skills (`metadata.audience: external`) plus any development-time skills of the server's own | `init` and the `maintenance` sync (framework skills); you (your own) |
|
|
284
|
+
| `skills/` | The agents that install the server and call its tools — tool chains, identifier sources, gates | You, deliberately, with the `add-skill` skill. Absent by default; usually none is needed |
|
|
285
|
+
|
|
286
|
+
Development skills are modular instructions. 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. Their 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 them to every agent that installs it.
|
|
278
287
|
|
|
279
|
-
|
|
288
|
+
A published skill in root `skills/` reaches plugin installs and the `.mcpb` bundle as-is, ships in the npm package and the Docker image, and reaches MCP clients when the server opts in with `createApp({ skills: 'skills' })`. `lint:packaging` validates the tree.
|
|
289
|
+
|
|
290
|
+
**Agent skill directory:** Copy `framework-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.
|
|
280
291
|
|
|
281
292
|
Available skills:
|
|
282
293
|
|
|
@@ -288,6 +299,7 @@ Available skills:
|
|
|
288
299
|
| `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
|
|
289
300
|
| `add-resource` | Scaffold a new resource definition |
|
|
290
301
|
| `add-prompt` | Scaffold a new prompt definition |
|
|
302
|
+
| `add-skill` | Author a skill the server publishes in root `skills/` for the agents that use it — when one earns its place, structure, validation, delivery |
|
|
291
303
|
| `add-service` | Scaffold a new service integration |
|
|
292
304
|
| `add-test` | Scaffold test file for a tool, resource, or service |
|
|
293
305
|
| `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
|
|
@@ -335,7 +347,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
335
347
|
| `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` |
|
|
336
348
|
| `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` |
|
|
337
349
|
| `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
|
|
338
|
-
| `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, README version-badge parity (run by devcheck) |
|
|
350
|
+
| `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, README version-badge parity, `.mcpbignore` coverage, plugin manifests, the published `skills/` tree (run by devcheck) |
|
|
339
351
|
| `bun run list-skills` | Print the skill registry |
|
|
340
352
|
| `bun run tree` | Generate directory structure doc |
|
|
341
353
|
| `bun run format` | Auto-fix formatting (safe fixes only) |
|
|
@@ -349,9 +361,11 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
349
361
|
|
|
350
362
|
---
|
|
351
363
|
|
|
364
|
+
**CI is one file.** `.github/workflows/codeql.yml` is the only GitHub Actions workflow: CodeQL runs only while the repo's CodeQL default setup is turned off. Verification — `devcheck`, tests, and release gates — runs locally.
|
|
365
|
+
|
|
352
366
|
## Bundling
|
|
353
367
|
|
|
354
|
-
`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
|
|
368
|
+
`bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. `mcpb pack` reads `.mcpbignore`, not `.gitignore`, so every root path the bundle must not ship — source, tests, docs, caches, logs, agent docs, plugin manifests, gitignored local files — needs a root-anchored line there (`/docs/`, never `docs/`, which also strips `node_modules/*/docs/`); `lint:packaging` names each missing or unanchored line. Root `skills/` ships on purpose. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips four classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/` at a package's root, and `SKILL.md` files — a `skills/` deeper in a package, such as the MCP SDK's `dist/ext/skills/`, is runtime code and stays); platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on; node-gyp build output beside a `binding.gyp` (`config.gypi`, Makefiles, `obj.target/`, `.deps/`, object files and debug symbols), which records the packing machine's paths — compiled `*.node` addons stay; and build-only package files — the framework's `changelog/`, `scripts/`, `templates/`, `CLAUDE.md`, `AGENTS.md`, and config bases (`biome.json`, `vitest.config.base.mjs`, `config/tsconfig.base.json`), and `better-sqlite3`'s `deps/` and `src/` C sources. `tests/scripts/bundle-agent-docs.test.ts` keeps the agent-doc matcher in `scripts/clean-mcpb.ts` and `scripts/lint-packaging.ts` identical; a framework script resync that widens it again breaks that test. 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.
|
|
355
369
|
|
|
356
370
|
**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": ""`.
|
|
357
371
|
|
|
@@ -418,7 +432,10 @@ import { getMyService } from '@/services/my-domain/my-service.js';
|
|
|
418
432
|
- [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
|
|
419
433
|
- [ ] Registered in `createApp()` arrays (directly or via barrel exports)
|
|
420
434
|
- [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
|
|
435
|
+
- [ ] `plugin.json` + `mcp.json` (Agent Plugins 1.0.0 — read by Codex, Cursor, GitHub Copilot, VS Code, Kiro) populated — `plugin.json` `name` = the unscoped repo name (lowercase `a-z`, `0-9`, `-`, `.` only); `version`, `description`, `author`, `homepage`, `repository`, `license`, `keywords` from `package.json`; no other top-level field, `extensions` included. `mcp.json` holds one `"type": "stdio"` entry keyed by the unscoped name, `npx -y <full package name>`, and the same fixed `env` as `.codex-plugin/mcp.json`. Neither file carries a user-supplied variable (no `""`, no `${…}` besides `${PLUGIN_ROOT}`/`${PLUGIN_DATA}`): keys reach Codex through `.codex-plugin/mcp.json` `env_vars`, which works only while `.codex-plugin/plugin.json` keeps `"mcpServers": "./.codex-plugin/mcp.json"`
|
|
421
436
|
- [ ] `.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
|
|
422
437
|
- [ ] `.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
|
|
423
438
|
- [ ] `.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`
|
|
439
|
+
- [ ] Any new root dev path has a root-anchored line in `.mcpbignore`; `bun run bundle` and `lint:packaging` pass
|
|
440
|
+
- [ ] If the server publishes a skill in root `skills/`: `lint:packaging` passes, and `field-test` verified every gate and identifier claim in it against live tool behavior
|
|
424
441
|
- [ ] `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.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.
|
|
4
|
+
**Version:** 0.7.0
|
|
5
|
+
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.18`
|
|
6
6
|
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
|
-
**MCP SDK:** `@modelcontextprotocol/server` ^2.2.0
|
|
8
|
-
**Zod:** ^4.6.
|
|
7
|
+
**MCP SDK:** `@modelcontextprotocol/server` ^2.4.0 (resolved 2.4.0; protocol revisions 2026-07-28 and 2025-*)
|
|
8
|
+
**Zod:** ^4.6.7
|
|
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
|
|
|
@@ -151,6 +151,8 @@ export function getServerConfig() {
|
|
|
151
151
|
|
|
152
152
|
`parseEnvConfig` maps Zod schema paths → env var names so errors name the variable (`NOAA_APPLICATION_ID`) not the path (`applicationId`). Throws `ConfigurationError`, which the framework prints as a clean startup banner.
|
|
153
153
|
|
|
154
|
+
For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag cannot be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else.
|
|
155
|
+
|
|
154
156
|
### Server identity and instructions
|
|
155
157
|
|
|
156
158
|
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.
|
|
@@ -159,7 +161,7 @@ This server's `createApp()` identity is the bare repository name in both `name`
|
|
|
159
161
|
|
|
160
162
|
`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.
|
|
161
163
|
|
|
162
|
-
`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.
|
|
164
|
+
`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 (0, or 1 if a step does not settle within the framework's 10-second ceiling).
|
|
163
165
|
|
|
164
166
|
---
|
|
165
167
|
|
|
@@ -169,13 +171,13 @@ Handlers receive a unified `ctx` object. Key properties:
|
|
|
169
171
|
|
|
170
172
|
| Property | Description |
|
|
171
173
|
|:---------|:------------|
|
|
172
|
-
| `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino and `notifications/message
|
|
174
|
+
| `ctx.log` | Request-scoped logger — `.debug()`, `.info()`, `.notice()`, `.warning()`, `.error()`. Auto-correlates requestId, traceId, tenantId. Dual-sink: Pino **and** `notifications/message` to the client, so treat it as client-visible. |
|
|
173
175
|
| `ctx.state` | Tenant-scoped KV — `.get(key)`, `.set(key, value, { ttl? })`, `.delete(key)`, `.getMany(keys)`, `.list(prefix, { cursor, limit })`. Accepts any JSON-serializable value; reads return its JSON form (a `Date` comes back as an ISO string). |
|
|
174
|
-
| `ctx.requestInput` | Suspend and ask the caller for more input.
|
|
175
|
-
| `ctx.inputs` | The request's responses, limited to what the client declared (`elicitation` and its form/url modes, `sampling`, `roots`). Client-supplied: a consent gate trusts only a `ctx.state` record it stored when it asked (see the `api-context` skill). |
|
|
176
|
-
| `ctx.clientCapabilities` | What the client declared for this request, `undefined` when no view exists.
|
|
177
|
-
| `ctx.enrich` | Success-path agent context
|
|
178
|
-
| `ctx.content` | Non-text image
|
|
176
|
+
| `ctx.requestInput` | Suspend and ask the caller for more input — `return ctx.requestInput({ inputRequests: { key: inputRequired.elicit({ message, requestedSchema }) } })`. Never returns; the handler is re-entered with the answers. Always present. |
|
|
177
|
+
| `ctx.inputs` | The request's responses — `.accepted(key, schema)`, `.view(key)`, `.state()`, `.dropped` — limited to what the client declared (`elicitation` and its form/url modes, `sampling`, `roots`). Client-supplied: a consent gate trusts only a `ctx.state` record it stored when it asked, bound to the operation, caller, and target (see the `api-context` skill). |
|
|
178
|
+
| `ctx.clientCapabilities` | What the client declared for this request, `undefined` when no view exists. Decides whether to ask for optional context (e.g. roots); never a reason to skip a consent prompt. |
|
|
179
|
+
| `ctx.enrich` | Success-path agent context (empty-result notices, query echo, pagination totals) — `ctx.enrich(...)` or `.notice()` / `.total()` / `.echo()` / `.truncated()`. Reaches `structuredContent` and `content[]`; lands only when the definition declares an `enrichment` block (no-op otherwise). |
|
|
180
|
+
| `ctx.content` | Non-text content blocks — `.image(data, mimeType)`, `.audio(data, mimeType)`, or `ctx.content(block)` for a raw block. Prepended to `content[]` after `format()`; never enters `structuredContent`. |
|
|
179
181
|
| `ctx.signal` | `AbortSignal` for cancellation. |
|
|
180
182
|
| `ctx.requestId` | Request ID — the one every log record of the call carries and its error envelope returns as `data.requestId`. |
|
|
181
183
|
| `ctx.tenantId` | Tenant ID from JWT; `'default'` for stdio or HTTP with auth off. |
|
|
@@ -219,7 +221,7 @@ throw new Error('Invalid query format'); // → ValidationError
|
|
|
219
221
|
|
|
220
222
|
// McpError — when no factory exists for the code
|
|
221
223
|
import { McpError, JsonRpcErrorCode } from '@cyanheads/mcp-ts-core/errors';
|
|
222
|
-
throw new McpError(JsonRpcErrorCode.
|
|
224
|
+
throw new McpError(JsonRpcErrorCode.InitializationFailed, 'Connection failed', { pool: 'primary' });
|
|
223
225
|
```
|
|
224
226
|
|
|
225
227
|
See framework CLAUDE.md and the `api-errors` skill for the full auto-classification table, all available factories, and the contract reference.
|
|
@@ -274,9 +276,18 @@ src/
|
|
|
274
276
|
|
|
275
277
|
## Skills
|
|
276
278
|
|
|
277
|
-
|
|
279
|
+
Two skill trees live at the project root, written for different readers:
|
|
280
|
+
|
|
281
|
+
| Tree | For | Written by |
|
|
282
|
+
|:-----|:----|:-----------|
|
|
283
|
+
| `framework-skills/` | The agent developing this server — framework skills (`metadata.audience: external`) plus any development-time skills of the server's own | `init` and the `maintenance` sync (framework skills); you (your own) |
|
|
284
|
+
| `skills/` | The agents that install the server and call its tools — tool chains, identifier sources, gates | You, deliberately, with the `add-skill` skill. Absent by default; usually none is needed |
|
|
285
|
+
|
|
286
|
+
Development skills are modular instructions. 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. Their 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 them to every agent that installs it.
|
|
278
287
|
|
|
279
|
-
|
|
288
|
+
A published skill in root `skills/` reaches plugin installs and the `.mcpb` bundle as-is, ships in the npm package and the Docker image, and reaches MCP clients when the server opts in with `createApp({ skills: 'skills' })`. `lint:packaging` validates the tree.
|
|
289
|
+
|
|
290
|
+
**Agent skill directory:** Copy `framework-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.
|
|
280
291
|
|
|
281
292
|
Available skills:
|
|
282
293
|
|
|
@@ -288,6 +299,7 @@ Available skills:
|
|
|
288
299
|
| `add-app-tool` | Scaffold an MCP App tool + paired UI resource |
|
|
289
300
|
| `add-resource` | Scaffold a new resource definition |
|
|
290
301
|
| `add-prompt` | Scaffold a new prompt definition |
|
|
302
|
+
| `add-skill` | Author a skill the server publishes in root `skills/` for the agents that use it — when one earns its place, structure, validation, delivery |
|
|
291
303
|
| `add-service` | Scaffold a new service integration |
|
|
292
304
|
| `add-test` | Scaffold test file for a tool, resource, or service |
|
|
293
305
|
| `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
|
|
@@ -335,7 +347,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
335
347
|
| `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` |
|
|
336
348
|
| `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` |
|
|
337
349
|
| `bun run lint:mcp` | Run the MCP definition linter standalone (rule catalog: `api-linter` skill) |
|
|
338
|
-
| `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, README version-badge parity (run by devcheck) |
|
|
350
|
+
| `bun run lint:packaging` | Packaging surface checks — `server.json`/`manifest.json` env-var parity, README version-badge parity, `.mcpbignore` coverage, plugin manifests, the published `skills/` tree (run by devcheck) |
|
|
339
351
|
| `bun run list-skills` | Print the skill registry |
|
|
340
352
|
| `bun run tree` | Generate directory structure doc |
|
|
341
353
|
| `bun run format` | Auto-fix formatting (safe fixes only) |
|
|
@@ -349,9 +361,11 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
349
361
|
|
|
350
362
|
---
|
|
351
363
|
|
|
364
|
+
**CI is one file.** `.github/workflows/codeql.yml` is the only GitHub Actions workflow: CodeQL runs only while the repo's CodeQL default setup is turned off. Verification — `devcheck`, tests, and release gates — runs locally.
|
|
365
|
+
|
|
352
366
|
## Bundling
|
|
353
367
|
|
|
354
|
-
`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
|
|
368
|
+
`bun run bundle` produces a `.mcpb` extension bundle for one-click install in Claude Desktop. `mcpb pack` reads `.mcpbignore`, not `.gitignore`, so every root path the bundle must not ship — source, tests, docs, caches, logs, agent docs, plugin manifests, gitignored local files — needs a root-anchored line there (`/docs/`, never `docs/`, which also strips `node_modules/*/docs/`); `lint:packaging` names each missing or unanchored line. Root `skills/` ships on purpose. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips four classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/` at a package's root, and `SKILL.md` files — a `skills/` deeper in a package, such as the MCP SDK's `dist/ext/skills/`, is runtime code and stays); platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on; node-gyp build output beside a `binding.gyp` (`config.gypi`, Makefiles, `obj.target/`, `.deps/`, object files and debug symbols), which records the packing machine's paths — compiled `*.node` addons stay; and build-only package files — the framework's `changelog/`, `scripts/`, `templates/`, `CLAUDE.md`, `AGENTS.md`, and config bases (`biome.json`, `vitest.config.base.mjs`, `config/tsconfig.base.json`), and `better-sqlite3`'s `deps/` and `src/` C sources. `tests/scripts/bundle-agent-docs.test.ts` keeps the agent-doc matcher in `scripts/clean-mcpb.ts` and `scripts/lint-packaging.ts` identical; a framework script resync that widens it again breaks that test. 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.
|
|
355
369
|
|
|
356
370
|
**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": ""`.
|
|
357
371
|
|
|
@@ -418,7 +432,10 @@ import { getMyService } from '@/services/my-domain/my-service.js';
|
|
|
418
432
|
- [ ] If wrapping external API: tests include at least one sparse payload case with omitted upstream fields
|
|
419
433
|
- [ ] Registered in `createApp()` arrays (directly or via barrel exports)
|
|
420
434
|
- [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
|
|
435
|
+
- [ ] `plugin.json` + `mcp.json` (Agent Plugins 1.0.0 — read by Codex, Cursor, GitHub Copilot, VS Code, Kiro) populated — `plugin.json` `name` = the unscoped repo name (lowercase `a-z`, `0-9`, `-`, `.` only); `version`, `description`, `author`, `homepage`, `repository`, `license`, `keywords` from `package.json`; no other top-level field, `extensions` included. `mcp.json` holds one `"type": "stdio"` entry keyed by the unscoped name, `npx -y <full package name>`, and the same fixed `env` as `.codex-plugin/mcp.json`. Neither file carries a user-supplied variable (no `""`, no `${…}` besides `${PLUGIN_ROOT}`/`${PLUGIN_DATA}`): keys reach Codex through `.codex-plugin/mcp.json` `env_vars`, which works only while `.codex-plugin/plugin.json` keeps `"mcpServers": "./.codex-plugin/mcp.json"`
|
|
421
436
|
- [ ] `.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
|
|
422
437
|
- [ ] `.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
|
|
423
438
|
- [ ] `.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`
|
|
439
|
+
- [ ] Any new root dev path has a root-anchored line in `.mcpbignore`; `bun run bundle` and `lint:packaging` pass
|
|
440
|
+
- [ ] If the server publishes a skill in root `skills/`: `lint:packaging` passes, and `field-test` verified every gate and identifier claim in it against live tool behavior
|
|
424
441
|
- [ ] `bun run devcheck` passes
|
package/Dockerfile
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
# output. A stage that compiles a native addon needs the target-arch toolchain
|
|
15
15
|
# and cannot cross-compile this way — drop the flag there.
|
|
16
16
|
# ==============================================================================
|
|
17
|
-
FROM --platform=$BUILDPLATFORM oven/bun:1.4.
|
|
17
|
+
FROM --platform=$BUILDPLATFORM oven/bun:1.4.4 AS build
|
|
18
18
|
|
|
19
19
|
WORKDIR /usr/src/app
|
|
20
20
|
|
|
@@ -29,6 +29,11 @@ RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
|
29
29
|
# Copy the rest of the source code
|
|
30
30
|
COPY . .
|
|
31
31
|
|
|
32
|
+
# The skills a server publishes (`createApp({ skills: 'skills' })`) are read at
|
|
33
|
+
# runtime from the application root. Created when the project has none, so the
|
|
34
|
+
# production stage's copy below holds with or without one.
|
|
35
|
+
RUN mkdir -p skills
|
|
36
|
+
|
|
32
37
|
# Build the application
|
|
33
38
|
RUN bun run build
|
|
34
39
|
|
|
@@ -46,7 +51,7 @@ RUN bun run build
|
|
|
46
51
|
# A clean image rather than `FROM build`: the build stage's node_modules holds
|
|
47
52
|
# devDependencies.
|
|
48
53
|
# ==============================================================================
|
|
49
|
-
FROM --platform=$BUILDPLATFORM oven/bun:1.4.
|
|
54
|
+
FROM --platform=$BUILDPLATFORM oven/bun:1.4.4 AS deps
|
|
50
55
|
|
|
51
56
|
WORKDIR /usr/src/app
|
|
52
57
|
|
|
@@ -114,7 +119,7 @@ RUN rm -rf node_modules/@socketsecurity/bun-security-scanner
|
|
|
114
119
|
# dependencies and build artifacts. Its only Bun invocations are HEALTHCHECK
|
|
115
120
|
# and CMD, which run on the real target at container start.
|
|
116
121
|
# ==============================================================================
|
|
117
|
-
FROM oven/bun:1.4.
|
|
122
|
+
FROM oven/bun:1.4.4-slim AS production
|
|
118
123
|
|
|
119
124
|
WORKDIR /usr/src/app
|
|
120
125
|
|
|
@@ -136,6 +141,7 @@ COPY --from=deps /usr/src/app/node_modules ./node_modules
|
|
|
136
141
|
|
|
137
142
|
# Copy the compiled application code from the build stage
|
|
138
143
|
COPY --from=build /usr/src/app/dist ./dist
|
|
144
|
+
COPY --from=build /usr/src/app/skills ./skills
|
|
139
145
|
|
|
140
146
|
# The 'oven/bun' image already provides a non-root user named 'bun'.
|
|
141
147
|
# We will use this existing user for enhanced security.
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/noaa-marine-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/noaa-marine-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
## Overview
|
|
31
31
|
|
|
32
|
-
US tides, currents, water levels, and buoy observations from NOAA CO-OPS and NDBC. Find coastal stations and buoys, then fetch tide and tidal-current predictions, observed and monthly-mean water levels, and live buoy conditions, current profiles, and water-column readings.
|
|
32
|
+
US tides, currents, water levels, and buoy observations from NOAA CO-OPS and NDBC. Find coastal stations and buoys, then fetch tide and tidal-current predictions, observed and monthly-mean water levels, and live buoy conditions, current profiles, and water-column readings.
|
|
33
33
|
|
|
34
34
|
### Tools
|
|
35
35
|
|
|
@@ -140,115 +140,32 @@ Agent-friendly output:
|
|
|
140
140
|
|
|
141
141
|
## Getting started
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
Connect to the hosted instance, no install:
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
```json
|
|
148
|
-
{
|
|
149
|
-
"mcpServers": {
|
|
150
|
-
"noaa-marine-mcp-server": {
|
|
151
|
-
"type": "streamable-http",
|
|
152
|
-
"url": "https://noaa-marine.caseyjhand.com/mcp"
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
145
|
+
```sh
|
|
146
|
+
claude mcp add --transport http noaa-marine-mcp-server https://noaa-marine.caseyjhand.com/mcp
|
|
156
147
|
```
|
|
157
148
|
|
|
158
|
-
|
|
149
|
+
Or run it locally:
|
|
159
150
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
```json
|
|
163
|
-
{
|
|
164
|
-
"mcpServers": {
|
|
165
|
-
"noaa-marine-mcp-server": {
|
|
166
|
-
"type": "stdio",
|
|
167
|
-
"command": "bunx",
|
|
168
|
-
"args": ["@cyanheads/noaa-marine-mcp-server@latest"],
|
|
169
|
-
"env": {
|
|
170
|
-
"MCP_TRANSPORT_TYPE": "stdio",
|
|
171
|
-
"MCP_LOG_LEVEL": "info"
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
151
|
+
```sh
|
|
152
|
+
claude mcp add noaa-marine-mcp-server -- npx -y @cyanheads/noaa-marine-mcp-server
|
|
176
153
|
```
|
|
177
154
|
|
|
178
|
-
|
|
155
|
+
For other clients, add this to the MCP config file:
|
|
179
156
|
|
|
180
157
|
```json
|
|
181
158
|
{
|
|
182
159
|
"mcpServers": {
|
|
183
160
|
"noaa-marine-mcp-server": {
|
|
184
|
-
"type": "stdio",
|
|
185
161
|
"command": "npx",
|
|
186
|
-
"args": ["-y", "@cyanheads/noaa-marine-mcp-server
|
|
187
|
-
"env": {
|
|
188
|
-
"MCP_TRANSPORT_TYPE": "stdio",
|
|
189
|
-
"MCP_LOG_LEVEL": "info"
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
```
|
|
195
|
-
|
|
196
|
-
Or with Docker:
|
|
197
|
-
|
|
198
|
-
```json
|
|
199
|
-
{
|
|
200
|
-
"mcpServers": {
|
|
201
|
-
"noaa-marine-mcp-server": {
|
|
202
|
-
"type": "stdio",
|
|
203
|
-
"command": "docker",
|
|
204
|
-
"args": [
|
|
205
|
-
"run", "-i", "--rm",
|
|
206
|
-
"-e", "MCP_TRANSPORT_TYPE=stdio",
|
|
207
|
-
"ghcr.io/cyanheads/noaa-marine-mcp-server:latest"
|
|
208
|
-
]
|
|
162
|
+
"args": ["-y", "@cyanheads/noaa-marine-mcp-server"]
|
|
209
163
|
}
|
|
210
164
|
}
|
|
211
165
|
}
|
|
212
166
|
```
|
|
213
167
|
|
|
214
|
-
For
|
|
215
|
-
|
|
216
|
-
```sh
|
|
217
|
-
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
|
|
218
|
-
# Server listens at http://localhost:3010/mcp
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Prerequisites
|
|
222
|
-
|
|
223
|
-
- [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
|
|
224
|
-
- No API key: NOAA CO-OPS and NDBC are open data sources.
|
|
225
|
-
|
|
226
|
-
### Installation
|
|
227
|
-
|
|
228
|
-
1. **Clone the repository:**
|
|
229
|
-
|
|
230
|
-
```sh
|
|
231
|
-
git clone https://github.com/cyanheads/noaa-marine-mcp-server.git
|
|
232
|
-
```
|
|
233
|
-
|
|
234
|
-
2. **Navigate into the directory:**
|
|
235
|
-
|
|
236
|
-
```sh
|
|
237
|
-
cd noaa-marine-mcp-server
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
3. **Install dependencies:**
|
|
241
|
-
|
|
242
|
-
```sh
|
|
243
|
-
bun install
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
4. **Configure environment:**
|
|
247
|
-
|
|
248
|
-
```sh
|
|
249
|
-
cp .env.example .env
|
|
250
|
-
# edit .env if needed (all vars optional)
|
|
251
|
-
```
|
|
168
|
+
No API key: NOAA CO-OPS and NDBC are open data sources. For Docker or a source build, see [Running the server](#running-the-server).
|
|
252
169
|
|
|
253
170
|
## Configuration
|
|
254
171
|
|
|
@@ -273,6 +190,16 @@ See [`.env.example`](./.env.example) for the full list of optional overrides.
|
|
|
273
190
|
|
|
274
191
|
### Local development
|
|
275
192
|
|
|
193
|
+
Requires [Bun v1.4.0](https://bun.sh/) or higher (or Node.js v24+).
|
|
194
|
+
|
|
195
|
+
- **Clone and install:**
|
|
196
|
+
|
|
197
|
+
```sh
|
|
198
|
+
git clone https://github.com/cyanheads/noaa-marine-mcp-server.git
|
|
199
|
+
cd noaa-marine-mcp-server && bun install
|
|
200
|
+
cp .env.example .env # all vars optional
|
|
201
|
+
```
|
|
202
|
+
|
|
276
203
|
- **Build and run:**
|
|
277
204
|
|
|
278
205
|
```sh
|
|
@@ -294,11 +221,10 @@ See [`.env.example`](./.env.example) for the full list of optional overrides.
|
|
|
294
221
|
### Docker
|
|
295
222
|
|
|
296
223
|
```sh
|
|
297
|
-
docker
|
|
298
|
-
docker run --rm -p 3010:3010 noaa-marine-mcp-server
|
|
224
|
+
docker run --rm -p 3010:3010 ghcr.io/cyanheads/noaa-marine-mcp-server:latest
|
|
299
225
|
```
|
|
300
226
|
|
|
301
|
-
The
|
|
227
|
+
The image defaults to HTTP transport, stateless session mode, and logs to `/var/log/noaa-marine-mcp-server`. To run it over stdio from an MCP client, use `"command": "docker"` with `"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/noaa-marine-mcp-server:latest"]`. To build locally, `docker build -t noaa-marine-mcp-server .`; OpenTelemetry peer dependencies are installed by default — build with `--build-arg OTEL_ENABLED=false` to omit them.
|
|
302
228
|
|
|
303
229
|
## Project structure
|
|
304
230
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Marine tool responses preserve literal station names, metadata, queries, and notices in Markdown, with framework 0.13.16 and refreshed dependencies."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.6.2 — 2026-10-25
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **Framework error and enrichment text** now escapes Markdown syntax in `content[]`, preserving literal messages, recovery hints, and trailer values.
|
|
12
|
+
- **Maintenance tooling and guidance** refresh audit attribution, bundle exclusions, configuration references, and hosted-client connection examples.
|
|
13
|
+
|
|
14
|
+
## Fixed
|
|
15
|
+
|
|
16
|
+
- **Marine tool Markdown** preserves literal upstream station names and metadata across all eight tools, including caller queries and discovery/current notices.
|
|
17
|
+
|
|
18
|
+
## Dependencies
|
|
19
|
+
|
|
20
|
+
- `@cyanheads/mcp-ts-core` ^0.13.14 → ^0.13.16.
|
|
21
|
+
- `zod` ^4.6.5 → ^4.6.7 (GHSA-mrhx-c7xc-m3w5).
|
|
22
|
+
- `@biomejs/biome` ^2.5.15 → ^2.6.3.
|
|
23
|
+
- `@types/node` ^26.6.4 → ^26.6.5.
|
|
24
|
+
- `ignore` ^7.0.12 → ^7.0.14.
|
|
25
|
+
- `tsc-alias` ^1.9.7 → ^1.9.9.
|
|
26
|
+
- `vitest` ^5.0.3 → ^5.0.5.
|
|
27
|
+
- **Bun** 1.4.2 → 1.4.4 (`packageManager` and Docker images).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Publishes the noaa-marine-guide skill over MCP, fixes the per-request HTTP memory retention that framework 0.13.16 introduced, and moves to framework 0.13.18."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.7.0 — 2026-10-28
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`noaa-marine-guide` skill** is served over MCP through `createApp({ skills })` (framework 0.13.18). `resources/list` gains `skill://noaa-marine-guide/SKILL.md`, `resources/templates/list` gains `skill://{+path}`, and `initialize` capabilities advertise the `io.modelcontextprotocol/skills` extension. The skill also ships in the npm package, the Docker image, and the `.mcpb`.
|
|
12
|
+
- **Agent Plugins** root `plugin.json` and `mcp.json`.
|
|
13
|
+
|
|
14
|
+
## Changed
|
|
15
|
+
|
|
16
|
+
- **`noaa_marine_find_stations` description and the server `instructions`** now say `query` matches station names and IDs, not places, so a town or marina needs geocoding to coordinates first. They previously said the tool resolves "a place name".
|
|
17
|
+
- **Enrichment trailer string values** render in `content[]` as code literals (framework 0.13.18), e.g. ``**predictions_status:** `empty` `` from `noaa_marine_get_water_level` at a station with no tide predictions. `structuredContent` is unchanged.
|
|
18
|
+
- **The `.mcpb`** no longer carries the framework's build-only files (framework 0.13.18), and keeps the MCP SDK's `dist/ext/skills/` runtime that serving skills needs, via a local fix to the copied bundle cleaner and packaging check ([cyanheads/mcp-ts-core#794](https://github.com/cyanheads/mcp-ts-core/issues/794)).
|
|
19
|
+
- **README Getting started** is restructured: hosted `claude mcp add`, local `npx`, one stdio JSON block; Docker uses the published image.
|
|
20
|
+
- **Maintenance:** `docs/design.md` corrections; framework skills re-synced; `bunfig.toml` release-age guard set to 1 day; the bug-report form uses one Environment table.
|
|
21
|
+
|
|
22
|
+
## Fixed
|
|
23
|
+
|
|
24
|
+
- **HTTP transport no longer retains memory per request.** Over Streamable HTTP, framework 0.13.16 (shipped in 0.6.2) held about 71 KiB per request through its shutdown signal, so heap grew with sustained traffic. Framework 0.13.17 releases each request's signal when its response ends ([cyanheads/mcp-ts-core#786](https://github.com/cyanheads/mcp-ts-core/issues/786)).
|
|
25
|
+
|
|
26
|
+
## Dependencies
|
|
27
|
+
|
|
28
|
+
- `@cyanheads/mcp-ts-core` ^0.13.16 → ^0.13.18.
|
package/dist/index.js
CHANGED
|
@@ -32,12 +32,13 @@ await createApp({
|
|
|
32
32
|
],
|
|
33
33
|
resources: [noaaMarineStationResource],
|
|
34
34
|
prompts: [],
|
|
35
|
+
skills: 'skills',
|
|
35
36
|
cacheHints: {
|
|
36
37
|
'tools/list': { ttlMs: 86_400_000, cacheScope: 'public' },
|
|
37
38
|
'resources/list': { ttlMs: 86_400_000, cacheScope: 'public' },
|
|
38
39
|
'resources/templates/list': { ttlMs: 86_400_000, cacheScope: 'public' },
|
|
39
40
|
},
|
|
40
|
-
instructions: `US marine conditions from NOAA CO-OPS and NDBC: CO-OPS is coastal-gauge tide and tidal-current predictions plus observed water levels, NDBC is live buoy observation, and every water height is referenced to MLLW, the US nautical chart datum, unless another datum is requested. Start with noaa_marine_find_stations to resolve a
|
|
41
|
+
instructions: `US marine conditions from NOAA CO-OPS and NDBC: CO-OPS is coastal-gauge tide and tidal-current predictions plus observed water levels, NDBC is live buoy observation, and every water height is referenced to MLLW, the US nautical chart datum, unless another datum is requested. Start with noaa_marine_find_stations to resolve a coordinate pair, a station name, or a bare station number to a station ID (it matches station names, not places, so geocode a town or marina to coordinates first) — its types filter names what each station can serve, and an ID from one source never works on a tool that reads the other. That source split is also what separates the two current tools: noaa_marine_get_currents returns CO-OPS tidal-current predictions, noaa_marine_get_current_profile an observed NDBC ADCP measurement.`,
|
|
41
42
|
setup(core) {
|
|
42
43
|
const serverConfig = getServerConfig();
|
|
43
44
|
initCoopsService(core.config, core.storage, serverConfig);
|
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,yBAAyB,EAAE,MAAM,sEAAsE,CAAC;AACjH,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,yBAAyB;QACzB,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,EAAE,
|
|
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,yBAAyB,EAAE,MAAM,sEAAsE,CAAC;AACjH,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,yBAAyB;QACzB,qBAAqB;QACrB,uBAAuB;QACvB,2BAA2B;QAC3B,8BAA8B;KAC/B;IACD,SAAS,EAAE,CAAC,yBAAyB,CAAC;IACtC,OAAO,EAAE,EAAE;IACX,MAAM,EAAE,QAAQ;IAChB,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,EAAE,4yBAA4yB;IAE1zB,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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"noaa-marine-find-stations.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/noaa-marine-find-stations.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"noaa-marine-find-stations.tool.d.ts","sourceRoot":"","sources":["../../../../src/mcp-server/tools/definitions/noaa-marine-find-stations.tool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAQ,CAAC,EAAE,MAAM,wBAAwB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AAkLjE,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqkBjC,CAAC"}
|