@cyanheads/mcp-ts-core 0.13.7 → 0.13.8
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 +3 -3
- package/CLAUDE.md +3 -3
- package/README.md +3 -1
- package/changelog/0.13.x/0.13.8.md +101 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +19 -0
- package/dist/config/index.js.map +1 -1
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +14 -2
- package/dist/core/app.js.map +1 -1
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +13 -3
- package/dist/core/context.js.map +1 -1
- package/dist/core/worker.d.ts +2 -0
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/core/worker.js +2 -0
- package/dist/core/worker.js.map +1 -1
- package/dist/linter/rules/enrichment-rules.d.ts +3 -2
- package/dist/linter/rules/enrichment-rules.d.ts.map +1 -1
- package/dist/linter/rules/enrichment-rules.js +9 -2
- package/dist/linter/rules/enrichment-rules.js.map +1 -1
- package/dist/linter/rules/handler-body-rules.d.ts.map +1 -1
- package/dist/linter/rules/handler-body-rules.js +10 -4
- package/dist/linter/rules/handler-body-rules.js.map +1 -1
- package/dist/linter/rules/schema-rules.d.ts +5 -0
- package/dist/linter/rules/schema-rules.d.ts.map +1 -1
- package/dist/linter/rules/schema-rules.js +44 -17
- package/dist/linter/rules/schema-rules.js.map +1 -1
- package/dist/mcp-server/outputContract.d.ts +33 -0
- package/dist/mcp-server/outputContract.d.ts.map +1 -0
- package/dist/mcp-server/outputContract.js +43 -0
- package/dist/mcp-server/outputContract.js.map +1 -0
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +10 -2
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +16 -5
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +65 -13
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.js +4 -1
- package/dist/mcp-server/transports/auth/lib/authUtils.js.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js +1 -1
- package/dist/mcp-server/transports/auth/strategies/jwtStrategy.js.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.d.ts.map +1 -1
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js +2 -5
- package/dist/mcp-server/transports/auth/strategies/oauthStrategy.js.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.js +2 -2
- package/dist/mcp-server/transports/http/sessionStore.js.map +1 -1
- package/dist/services/canvas/core/CanvasRegistry.js +1 -1
- package/dist/services/canvas/core/CanvasRegistry.js.map +1 -1
- package/dist/services/canvas/core/DataCanvas.d.ts.map +1 -1
- package/dist/services/canvas/core/DataCanvas.js +7 -5
- package/dist/services/canvas/core/DataCanvas.js.map +1 -1
- package/dist/services/canvas/core/canvasFactory.d.ts.map +1 -1
- package/dist/services/canvas/core/canvasFactory.js +2 -2
- package/dist/services/canvas/core/canvasFactory.js.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.d.ts.map +1 -1
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js +25 -16
- package/dist/services/canvas/providers/duckdb/DuckdbProvider.js.map +1 -1
- package/dist/services/llm/providers/openrouter.provider.js +1 -1
- package/dist/services/llm/providers/openrouter.provider.js.map +1 -1
- package/dist/services/speech/providers/elevenlabs.provider.js +3 -3
- package/dist/services/speech/providers/elevenlabs.provider.js.map +1 -1
- package/dist/services/speech/providers/whisper.provider.d.ts.map +1 -1
- package/dist/services/speech/providers/whisper.provider.js +5 -5
- package/dist/services/speech/providers/whisper.provider.js.map +1 -1
- package/dist/storage/core/StorageService.d.ts.map +1 -1
- package/dist/storage/core/StorageService.js +3 -6
- package/dist/storage/core/StorageService.js.map +1 -1
- package/dist/storage/core/storageFactory.d.ts.map +1 -1
- package/dist/storage/core/storageFactory.js +12 -15
- package/dist/storage/core/storageFactory.js.map +1 -1
- package/dist/storage/core/storageValidation.d.ts +13 -13
- package/dist/storage/core/storageValidation.d.ts.map +1 -1
- package/dist/storage/core/storageValidation.js +49 -125
- package/dist/storage/core/storageValidation.js.map +1 -1
- package/dist/storage/providers/cloudflare/d1Provider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/d1Provider.js +5 -3
- package/dist/storage/providers/cloudflare/d1Provider.js.map +1 -1
- package/dist/storage/providers/cloudflare/kvProvider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/kvProvider.js +1 -1
- package/dist/storage/providers/cloudflare/kvProvider.js.map +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.d.ts.map +1 -1
- package/dist/storage/providers/cloudflare/r2Provider.js +3 -3
- package/dist/storage/providers/cloudflare/r2Provider.js.map +1 -1
- package/dist/storage/providers/fileSystem/fileSystemProvider.js +4 -4
- package/dist/storage/providers/fileSystem/fileSystemProvider.js.map +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.d.ts +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.d.ts.map +1 -1
- package/dist/storage/providers/inMemory/inMemoryProvider.js +6 -5
- package/dist/storage/providers/inMemory/inMemoryProvider.js.map +1 -1
- package/dist/testing/fuzz.d.ts.map +1 -1
- package/dist/testing/fuzz.js +7 -1
- package/dist/testing/fuzz.js.map +1 -1
- package/dist/testing/index.d.ts +15 -2
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +51 -6
- package/dist/testing/index.js.map +1 -1
- package/dist/types-global/errors.d.ts +7 -4
- package/dist/types-global/errors.d.ts.map +1 -1
- package/dist/types-global/errors.js.map +1 -1
- package/dist/utils/formatting/codeSpan.d.ts +27 -0
- package/dist/utils/formatting/codeSpan.d.ts.map +1 -0
- package/dist/utils/formatting/codeSpan.js +42 -0
- package/dist/utils/formatting/codeSpan.js.map +1 -0
- package/dist/utils/formatting/diffFormatter.d.ts.map +1 -1
- package/dist/utils/formatting/diffFormatter.js +7 -15
- package/dist/utils/formatting/diffFormatter.js.map +1 -1
- package/dist/utils/formatting/markdownBuilder.d.ts +12 -5
- package/dist/utils/formatting/markdownBuilder.d.ts.map +1 -1
- package/dist/utils/formatting/markdownBuilder.js +14 -2
- package/dist/utils/formatting/markdownBuilder.js.map +1 -1
- package/dist/utils/formatting/tableFormatter.d.ts.map +1 -1
- package/dist/utils/formatting/tableFormatter.js +5 -9
- package/dist/utils/formatting/tableFormatter.js.map +1 -1
- package/dist/utils/formatting/treeFormatter.d.ts.map +1 -1
- package/dist/utils/formatting/treeFormatter.js +5 -9
- package/dist/utils/formatting/treeFormatter.js.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.d.ts +17 -10
- package/dist/utils/internal/error-handler/errorHandler.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/errorHandler.js +47 -26
- package/dist/utils/internal/error-handler/errorHandler.js.map +1 -1
- package/dist/utils/internal/error-handler/mappings.d.ts +17 -1
- package/dist/utils/internal/error-handler/mappings.d.ts.map +1 -1
- package/dist/utils/internal/error-handler/mappings.js +22 -1
- package/dist/utils/internal/error-handler/mappings.js.map +1 -1
- package/dist/utils/internal/error-handler/types.d.ts +2 -0
- package/dist/utils/internal/error-handler/types.d.ts.map +1 -1
- package/dist/utils/internal/logger.d.ts +75 -3
- package/dist/utils/internal/logger.d.ts.map +1 -1
- package/dist/utils/internal/logger.js +181 -52
- package/dist/utils/internal/logger.js.map +1 -1
- package/dist/utils/internal/performance.d.ts +11 -0
- package/dist/utils/internal/performance.d.ts.map +1 -1
- package/dist/utils/internal/performance.js +46 -12
- package/dist/utils/internal/performance.js.map +1 -1
- package/dist/utils/network/fetchWithTimeout.d.ts +11 -5
- package/dist/utils/network/fetchWithTimeout.d.ts.map +1 -1
- package/dist/utils/network/fetchWithTimeout.js +50 -23
- package/dist/utils/network/fetchWithTimeout.js.map +1 -1
- package/dist/utils/network/retry.d.ts +16 -8
- package/dist/utils/network/retry.d.ts.map +1 -1
- package/dist/utils/network/retry.js +19 -8
- package/dist/utils/network/retry.js.map +1 -1
- package/dist/utils/overflow/outlineOnOverflow.d.ts +18 -2
- package/dist/utils/overflow/outlineOnOverflow.d.ts.map +1 -1
- package/dist/utils/overflow/outlineOnOverflow.js +28 -3
- package/dist/utils/overflow/outlineOnOverflow.js.map +1 -1
- package/dist/utils/pagination/pagination.d.ts +3 -1
- package/dist/utils/pagination/pagination.d.ts.map +1 -1
- package/dist/utils/pagination/pagination.js +10 -2
- package/dist/utils/pagination/pagination.js.map +1 -1
- package/dist/utils/parsing/csvParser.d.ts.map +1 -1
- package/dist/utils/parsing/csvParser.js +4 -2
- package/dist/utils/parsing/csvParser.js.map +1 -1
- package/dist/utils/parsing/htmlExtractor.js +1 -1
- package/dist/utils/parsing/htmlExtractor.js.map +1 -1
- package/dist/utils/parsing/jsonParser.d.ts.map +1 -1
- package/dist/utils/parsing/jsonParser.js +3 -1
- package/dist/utils/parsing/jsonParser.js.map +1 -1
- package/dist/utils/parsing/xmlParser.d.ts.map +1 -1
- package/dist/utils/parsing/xmlParser.js +3 -1
- package/dist/utils/parsing/xmlParser.js.map +1 -1
- package/dist/utils/parsing/yamlParser.d.ts.map +1 -1
- package/dist/utils/parsing/yamlParser.js +3 -1
- package/dist/utils/parsing/yamlParser.js.map +1 -1
- package/dist/utils/security/idGenerator.d.ts.map +1 -1
- package/dist/utils/security/idGenerator.js +20 -4
- package/dist/utils/security/idGenerator.js.map +1 -1
- package/dist/utils/security/sanitization.d.ts +31 -0
- package/dist/utils/security/sanitization.d.ts.map +1 -1
- package/dist/utils/security/sanitization.js +98 -11
- package/dist/utils/security/sanitization.js.map +1 -1
- package/dist/utils/telemetry/attributes.d.ts +16 -1
- package/dist/utils/telemetry/attributes.d.ts.map +1 -1
- package/dist/utils/telemetry/attributes.js +16 -1
- package/dist/utils/telemetry/attributes.js.map +1 -1
- package/dist/utils/telemetry/instrumentation.d.ts +9 -3
- package/dist/utils/telemetry/instrumentation.d.ts.map +1 -1
- package/dist/utils/telemetry/instrumentation.js +85 -13
- package/dist/utils/telemetry/instrumentation.js.map +1 -1
- package/framework-skills/api-auth/SKILL.md +3 -1
- package/framework-skills/api-canvas/SKILL.md +3 -3
- package/framework-skills/api-config/SKILL.md +6 -3
- package/framework-skills/api-context/SKILL.md +3 -3
- package/framework-skills/api-errors/SKILL.md +16 -11
- package/framework-skills/api-linter/SKILL.md +7 -3
- package/framework-skills/api-telemetry/SKILL.md +31 -11
- package/framework-skills/api-testing/SKILL.md +5 -3
- package/framework-skills/api-utils/SKILL.md +9 -9
- package/framework-skills/api-utils/references/formatting.md +1 -1
- package/framework-skills/api-utils/references/parsing.md +2 -2
- package/framework-skills/api-utils/references/security.md +6 -4
- package/framework-skills/git-wrapup/SKILL.md +5 -2
- package/framework-skills/maintenance/SKILL.md +2 -2
- package/framework-skills/polish-docs-meta/SKILL.md +1 -1
- package/framework-skills/polish-docs-meta/references/readme.md +1 -0
- package/framework-skills/release-and-publish/SKILL.md +2 -2
- package/framework-skills/techniques/SKILL.md +1 -1
- package/framework-skills/techniques/references/outline-on-overflow.md +12 -7
- package/package.json +18 -3
- package/scripts/check-skill-versions.ts +103 -22
- package/scripts/devcheck.ts +4 -3
- package/scripts/lint-packaging.ts +38 -1
- package/templates/.env.example +4 -0
- package/templates/Dockerfile +26 -6
- package/templates/package.json +1 -0
|
@@ -21,7 +21,7 @@ Pre-constructed singleton of `Sanitization`. Tier 3 peer: `sanitize-html` (HTML
|
|
|
21
21
|
| `sanitizePath` | **no** | Node.js only | `(input, options?) -> SanitizedPathInfo` |
|
|
22
22
|
| `sanitizeJson` | **no** | none | `<T>(input, maxSize?) -> T` |
|
|
23
23
|
| `sanitizeForLogging` | **no** | none | `(input) -> unknown` |
|
|
24
|
-
| `
|
|
24
|
+
| `serializeForLogging` | **no** | none | `(value, maxBytes) -> { text: string; truncated: boolean }` |
|
|
25
25
|
| `getSensitivePinoFields` | **no** | none | `() -> string[]` |
|
|
26
26
|
|
|
27
27
|
### Option types
|
|
@@ -65,6 +65,8 @@ interface SanitizedPathInfo {
|
|
|
65
65
|
- `sanitizeJson`: `maxSize` is bytes (UTF-8); uses `Buffer.byteLength` / `TextEncoder` / `string.length` fallback chain
|
|
66
66
|
- `sanitizeNumber`: `NaN`/`Infinity` always rejected; out-of-range values silently clamped with debug log
|
|
67
67
|
- `sanitizeForLogging`: deep clones via `structuredClone`; returns `'[Log Sanitization Failed]'` on clone error
|
|
68
|
+
- **Rejection reasons.** Every `ValidationError` carries `data.reason`, and a `data.recovery.hint` wherever the caller can change the input: `invalid_url` (`sanitizeUrl`; the hint names the allowed schemes), `invalid_path` / `path_traversal` / `absolute_path_disallowed` (`sanitizePath`), `invalid_json` / `json_too_large` (`sanitizeJson`; the latter names the byte cap), `invalid_number` (`sanitizeNumber`), and `unsupported_sanitize_context` (`sanitizeString`'s `'javascript'` context, which has no hint — it is a server-code choice)
|
|
69
|
+
- `serializeForLogging`: `sanitizeForLogging`, then `JSON.stringify`, then a cut to at most `maxBytes` UTF-8 bytes on a character boundary — redaction first, so a cut never keeps part of a secret. A truncated `text` is a prefix of the whole serialization and no longer valid JSON; `truncated` says so. Returns a string so a deep payload survives the logger's four-level field depth. A value `JSON.stringify` rejects (a `bigint`) yields `'[Log Serialization Failed]'`. Backs the failed-call payload record (`LOG_TOOL_FAILURE_PAYLOADS`)
|
|
68
70
|
|
|
69
71
|
### Sensitive fields
|
|
70
72
|
|
|
@@ -191,10 +193,10 @@ interface IdGenerationOptions {
|
|
|
191
193
|
| Method | Signature | Notes |
|
|
192
194
|
|:-------|:----------|:------|
|
|
193
195
|
| `generate` | `(prefix?, options?) -> string` | `PREFIX_XXXXXX` or just `XXXXXX` if no prefix |
|
|
194
|
-
| `generateForEntity` | `(entityType, options?) -> string` | Uses registered prefix; throws `McpError(ValidationError)` if type unknown |
|
|
195
|
-
| `generateRandomString` | `(length?, charset?) -> string` | Raw random string; defaults: length 6, charset `A-Z0-9` |
|
|
196
|
+
| `generateForEntity` | `(entityType, options?) -> string` | Uses registered prefix; throws `McpError(ValidationError)` with `data.reason: 'unknown_entity_type'` if type unknown |
|
|
197
|
+
| `generateRandomString` | `(length?, charset?) -> string` | Raw random string; defaults: length 6, charset `A-Z0-9`. A charset outside 1–256 characters throws `ValidationError` with `data.reason: 'invalid_charset'` |
|
|
196
198
|
| `isValid` | `(id, entityType, options?) -> boolean` | Regex-validates format against prefix + separator + charset{length} |
|
|
197
|
-
| `getEntityType` | `(id, separator?) -> string` | Resolves entity type from prefix; throws `McpError(ValidationError)`
|
|
199
|
+
| `getEntityType` | `(id, separator?) -> string` | Resolves entity type from prefix; throws `McpError(ValidationError)` — `data.reason: 'invalid_id_format'` when the ID has no `PREFIX<sep>` part, `'unknown_entity_type'` when the prefix is unregistered, each with a `recovery.hint` (the latter lists the registered prefixes) |
|
|
198
200
|
| `normalize` | `(id, separator?) -> string` | Canonical prefix casing + uppercase random part |
|
|
199
201
|
| `stripPrefix` | `(id, separator?) -> string` | Returns random part; returns original if separator not found |
|
|
200
202
|
| `setEntityPrefixes` | `(config) -> void` | Replaces all prefixes and rebuilds reverse lookup |
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Land working-tree changes as logical commits — the work grouped by concern, topped by a release commit (version bump, changelog, regenerated artifacts). The work commits land first, then the version bump, verification, and the release commit on top. Stops at "committed locally on main" — or, when the project releases through a release PR, at "release branch pushed, PR open". No tag, no push to main, no publish: the release-and-publish skill merges, tags, and ships from here. Distilled from the git_wrapup_instructions protocol.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.26"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -105,7 +105,9 @@ git commit --only <paths-for-this-concern> -m "<subject>" -m "<body>"
|
|
|
105
105
|
|
|
106
106
|
**The file is the atomic boundary:** NEVER split a single file's working-tree changes across commits, regardless of mechanism — not `git add -p`, not an index-only patch (`git apply --cached`), not editing the file between commits to remove-then-re-add a hunk. When one file serves two concerns, it ships whole in the commit of its dominant concern; a later commit may touch the file again only for changes made AFTER the first commit (the version bump applied in step 4).
|
|
107
107
|
|
|
108
|
-
**Every commit builds on its own.** When a concern changes an exported contract — a service method's return type, a shared helper's signature — the files that consume it ride in the same commit, even when they also carry other concerns. Grouping the contract change into one commit and each consumer into its own later commit leaves pushed commits that fail typecheck alone, and pushed history is never rewritten to repair them.
|
|
108
|
+
**Every commit builds and passes its tests on its own.** When a concern changes an exported contract — a service method's return type, a shared helper's signature, a renamed export, a changed query or behavior a consumer's tests assert — the files that consume it AND their tests ride in the same commit, even when they also carry other concerns. Grouping the contract change into one commit and each consumer into its own later commit leaves pushed commits that fail typecheck or the suite alone, and pushed history is never rewritten to repair them. A snapshot can typecheck and still be red: before pushing, check out each work commit's tree (`git stash` is not the tool — extract it with `git archive <sha> | tar -x -C <scratch>`, symlink the project's `node_modules` into it) and run the test script there; merge groups whose snapshot fails.
|
|
109
|
+
|
|
110
|
+
**A dependency bump lands before the commits that use it.** When any later commit in the stack uses something the new versions introduce — a new framework export, a new `tool()` option, a changed signature — `chore(deps)` is the first work commit. It builds on its own: `package.json`, the lockfile, and any source change the upgrade itself forces (a renamed import, a removed option) ride in it, so the commits above it compile against the versions they were written for. Ordered the other way, the adopting commit and every commit up to the bump fail typecheck at their own SHA.
|
|
109
111
|
|
|
110
112
|
**Subject format:** Conventional Commits, no version in the subject — `feat: hosted server endpoint`, `fix: handle empty SPARQL result sets`, `feat(linter): enrichment contract rules`, `docs: document the enrichment block`, `chore(deps): refresh dev dependencies`.
|
|
111
113
|
|
|
@@ -307,6 +309,7 @@ If the working tree isn't clean or the release commit isn't at HEAD, something w
|
|
|
307
309
|
- [ ] `bun run test:package` passes, when the project defines it — it guards the public-export manifest and `test:all` does not run it
|
|
308
310
|
- [ ] Release PR mode: stack committed on `release/<version>`, never on `main`
|
|
309
311
|
- [ ] Work grouped into logical commits (large features split by layer); release artifacts (version + changelog + tree) committed separately on top, subject leading with the version
|
|
312
|
+
- [ ] `chore(deps)` is the first work commit whenever a later commit uses what the new versions introduce, and it builds on its own — carrying `package.json`, the lockfile, and any source change the upgrade forces
|
|
310
313
|
- [ ] A gate failure after the work is committed landed as a new commit on the stack — nothing amended, rebased, or otherwise rewritten
|
|
311
314
|
- [ ] Every commit carries a body, and every body is one or two lines — none subject-only, none a paragraph
|
|
312
315
|
- [ ] Release PR mode: branch pushed, PR open — title = release commit subject; body = theme line, `## Changes` in tag rules, `## Gates`, changelog link last (via `--body-file`, no closing keywords)
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Investigate, adopt, and verify dependency updates — with special handling for `@cyanheads/mcp-ts-core`. Captures what changed, understands why, cross-references against the codebase, adopts framework improvements, syncs project skills, and runs final checks. Supports two entry modes: run the full flow end-to-end, or review updates you already applied.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "2.
|
|
7
|
+
version: "2.10"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -177,7 +177,7 @@ The consumer opted into the framework; its templates, skills, scripts, linter ru
|
|
|
177
177
|
- **Deprecations** — migrate now, while context is fresh.
|
|
178
178
|
- **New linter rules** — if the rule now flags existing code, fix the code; don't silence the rule.
|
|
179
179
|
- **New utilities that supersede local code** — swap them in. The point of the framework is to centralize. This applies even when the local helper has richer messages or branch handling — port the domain detail onto the framework path; don't leave the local helper as-is. (E.g., `httpErrorFromResponse` replacing a project-local `throwForStatus`: keep the per-route message map, but route it through the framework utility.)
|
|
180
|
-
- **New conventions** (template changes, new config keys, renamed env vars) — adopt and update `.env.example`, server config schema, `server.json`, and README if user-facing.
|
|
180
|
+
- **New conventions** (template changes, new config keys, renamed env vars) — adopt and update `.env.example`, server config schema, `server.json`, and README if user-facing. A new Bun pin (the template's `packageManager`) moves three places together: `package.json` `packageManager`, the Dockerfile `oven/bun` base tags, and the README Bun badge.
|
|
181
181
|
- **New patterns that match existing surfaces** — refactor *every* matching site in this pass. Examples: typed error contracts (`errors[]` + `ctx.fail`) on tools that already throw domain-specific failures; factory adoption (`notFound()`, `validationError()`, …) replacing ad-hoc `new McpError(...)`; new logging/observability hooks supplanting bespoke logging. If the framework added a pattern that fits N tools/services, do all N — partial adoption fragments the surface and rots faster.
|
|
182
182
|
- **New framework features that don't match existing use cases** — skip. These are for future features, not retroactive refactors. "Don't match" means *the surface doesn't exist in this server* (e.g., a new Speech API in a non-speech server) — not "I'd have to touch a few files."
|
|
183
183
|
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Finalize documentation and project metadata for a ship-ready MCP server. Use after implementation is complete, tests pass, and devcheck is clean. Safe to run at any stage — each step checks current state and only acts on what still needs work.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "2.
|
|
7
|
+
version: "2.19"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -390,6 +390,7 @@ Table of environment variables. Include framework vars only if the server uses n
|
|
|
390
390
|
| `MCP_AUTH_MODE` | Auth mode: `none`, `jwt`, or `oauth`. | `none` |
|
|
391
391
|
| `MCP_LOG_LEVEL` | Log level (RFC 5424). | `info` |
|
|
392
392
|
| `LOGS_DIR` | Directory for log files (Node.js only). | `<project-root>/logs` |
|
|
393
|
+
| `LOG_TOOL_FAILURE_PAYLOADS` | Log each failed tool call's arguments and result, redacted by key name and capped at `LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES` (default `16384`). A secret inside a free-form value is not redacted. | `false` |
|
|
393
394
|
| `STORAGE_PROVIDER_TYPE` | Storage backend. | `in-memory` |
|
|
394
395
|
| `OTEL_ENABLED` | Enable [OpenTelemetry instrumentation](https://github.com/cyanheads/mcp-ts-core/tree/main/docs/telemetry) (spans, metrics, completion logs). | `false` |
|
|
395
396
|
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Ship a release end-to-end across every registry the project targets (npm, MCP Registry, GitHub Releases for `.mcpb` bundles, GHCR). Runs the final verification gate, fast-forwards `main` when the release rode a release PR, creates the annotated tag on the commit `main` now points at, pushes commits and tags, then publishes to each applicable destination. Assumes git wrapup (version bumps, changelog, commit stack — and in release PR mode, the pushed branch and open PR) is already complete — this skill is the post-wrapup merge + tag + publish workflow. Retries transient network failures on publish steps; halts with a partial-state report when retries are exhausted or the failure is terminal.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "2.
|
|
7
|
+
version: "2.21"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -175,7 +175,7 @@ Push `main` first, then the tag. If the remote rejects either push, halt.
|
|
|
175
175
|
|
|
176
176
|
### 6. Publish to npm
|
|
177
177
|
|
|
178
|
-
|
|
178
|
+
A `dist/*.mcpb` already built for step 8 stays out of the tarball: the `files` allowlist carries `"!dist/*.mcpb"`, and `lint:packaging` fails a project with `manifest.json` that lacks it.
|
|
179
179
|
|
|
180
180
|
```bash
|
|
181
181
|
bun publish --access public
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Catalog of reusable response- and data-shaping techniques for MCP servers built on `@cyanheads/mcp-ts-core` — overflow handling, payload shaping, retrieval patterns. Use when a tool's payload is too large, awkwardly shaped, or expensive to retrieve and you want a proven pattern instead of inventing one. Each technique has a self-contained reference under `references/`.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "0.
|
|
7
|
+
version: "0.4"
|
|
8
8
|
audience: external
|
|
9
9
|
type: reference
|
|
10
10
|
---
|
|
@@ -45,9 +45,12 @@ export const getLabel = tool('get_label', {
|
|
|
45
45
|
.describe('Sections to return. Omit for the full label (or an outline if it overflows).'),
|
|
46
46
|
}),
|
|
47
47
|
output: z.object({
|
|
48
|
-
kind: z.enum(['full', 'outline']),
|
|
48
|
+
kind: z.enum(['full', 'outline']).describe('Whether the full label or a section outline was returned'),
|
|
49
49
|
...FullLabel.partial().shape, // full arm — every field optional
|
|
50
|
-
sections:
|
|
50
|
+
sections: z // outline arm
|
|
51
|
+
.array(OUTLINE_VARIANT.shape.sections.element.describe('One section of the label'))
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Available sections, largest first'),
|
|
51
54
|
notice: OUTLINE_VARIANT.shape.notice.optional(),
|
|
52
55
|
}),
|
|
53
56
|
// Render each arm on field presence, independently — never branch on `kind` (see below).
|
|
@@ -66,6 +69,8 @@ export const getLabel = tool('get_label', {
|
|
|
66
69
|
});
|
|
67
70
|
```
|
|
68
71
|
|
|
72
|
+
The shape lints clean as written. The section item is described in place (`.element.describe(…)`, then the array re-described) because `describe-on-fields` asks every array-of-object element for a description and `OUTLINE_VARIANT`'s item carries none; folding `OUTLINE_VARIANT.shape.sections` in directly warns on `output.sections[]`. The `notice` beside a `sections` array is exempt from `enrichment-prefer-block` — it is the re-call instruction, main-body payload by design.
|
|
73
|
+
|
|
69
74
|
`format()`-parity holds because every terminal field in `output` must appear in the rendered text. With a flat object the linter builds **one** synthetic sample with every optional field populated at once, so render each arm on field presence, independently — a mutually-exclusive `if (kind === 'outline') … else …` renders only one arm against that all-fields sample and fails parity for the other. `formatOutline` is the shipped renderer for the `outline` arm; you supply the `full` renderer. That keeps the two client surfaces in lockstep.
|
|
70
75
|
|
|
71
76
|
## The helper
|
|
@@ -75,9 +80,9 @@ export const getLabel = tool('get_label', {
|
|
|
75
80
|
| Export | Purpose |
|
|
76
81
|
|:--|:--|
|
|
77
82
|
| `outlineOnOverflow(doc, options?)` | Returns `{ kind: 'full', ...doc }` under budget (or with `< 2` sections), else `{ kind: 'outline', sections, notice }`. |
|
|
78
|
-
| `OUTLINE_VARIANT` | The reusable `outline`-arm Zod schema; fold `.shape.sections` / `.shape.notice` into your flat `output` object as optional arms. |
|
|
79
|
-
| `selectSections(doc, want, { alwaysKeep })` | Projects the document to requested keys plus always-kept metadata. The selection-path counterpart. |
|
|
80
|
-
| `formatOutline(outline)` | Renders the outline to `content[]` for `format()`. |
|
|
83
|
+
| `OUTLINE_VARIANT` | The reusable `outline`-arm Zod schema; fold `.shape.sections` (item described in place, as above) / `.shape.notice` into your flat `output` object as optional arms. |
|
|
84
|
+
| `selectSections(doc, want, { alwaysKeep })` | Projects the document to requested keys plus always-kept metadata. The selection-path counterpart. A requested name that is not a key of `doc` throws `InvalidParams`; the message names the unmatched names and the available keys, and `data` carries both (`unmatched`, `available`). An `alwaysKeep` key absent from `doc` is ignored. |
|
|
85
|
+
| `formatOutline(outline)` | Renders the outline to `content[]` for `format()`. Each section name is a code span sized past any backtick in it, so the name reads back exactly as the caller must pass it in `sections`. |
|
|
81
86
|
| `DEFAULT_OUTLINE_BUDGET_BYTES` | The default budget (`24_000`) when `options.budget` is omitted. |
|
|
82
87
|
|
|
83
88
|
`outlineOnOverflow` options:
|
|
@@ -93,14 +98,14 @@ The flow:
|
|
|
93
98
|
3. **Over budget, ≥ 2 sections** → the outline (sections sorted largest-first). The agent re-calls with `sections: [...]`.
|
|
94
99
|
4. **Over budget, < 2 sections** → `full` anyway (nothing to pick between). A single section that *alone* exceeds budget is a known limitation — sub-section outlining is out of scope.
|
|
95
100
|
|
|
96
|
-
The budget bounds the **disclosure**, not the selection. `selectSections` returns whatever the agent named, so a selection over several sections — or one section larger than the budget — comes back whole. That is deliberate: the agent asked for those sections by name, and truncating the answer is the thing this technique exists to avoid. The default notice reports each example's size so the selection can be sized before it is made.
|
|
101
|
+
The budget bounds the **disclosure**, not the selection. `selectSections` returns whatever the agent named, so a selection over several sections — or one section larger than the budget — comes back whole. That is deliberate: the agent asked for those sections by name, and truncating the answer is the thing this technique exists to avoid. The default notice reports each example's size so the selection can be sized before it is made. The selection is not lenient about names, though: a name the document does not carry is rejected with the valid names, not dropped, so a stale or mistyped section never comes back as a quietly smaller answer.
|
|
97
102
|
|
|
98
103
|
## Re-retrieval — why the selection call is stateless
|
|
99
104
|
|
|
100
105
|
The re-call is **self-contained**, so nothing is stored between the outline call and the selection call:
|
|
101
106
|
|
|
102
107
|
- The selection call sends the **same input** as the outline call, plus `sections: [...]`.
|
|
103
|
-
- The handler **re-fetches** the document — input-minus-`sections` is identical and the upstream query is deterministic, so it reproduces the exact same record — then applies `selectSections` (a pure projection: requested keys + `alwaysKeep` metadata).
|
|
108
|
+
- The handler **re-fetches** the document — input-minus-`sections` is identical and the upstream query is deterministic, so it reproduces the exact same record — then applies `selectSections` (a pure projection: requested keys + `alwaysKeep` metadata; an unknown requested name throws `InvalidParams` naming the available keys).
|
|
104
109
|
- You **reconstruct rather than remember**. The agent holds the continuity (it passes `sections`); the upstream holds the document.
|
|
105
110
|
|
|
106
111
|
The only cost is the redundant fetch. For a **rate-limited or expensive upstream**, trade it for an optional cache:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/mcp-ts-core",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.8",
|
|
4
4
|
"mcpName": "io.github.cyanheads/mcp-ts-core",
|
|
5
5
|
"description": "Agent-native TypeScript framework for MCP servers. Includes runtime infrastructure and agent skills for building, testing, and shipping servers.",
|
|
6
6
|
"files": [
|
|
@@ -202,17 +202,20 @@
|
|
|
202
202
|
"@duckdb/node-api": "^1.5.5-r.5",
|
|
203
203
|
"@hono/otel": "^1.1.2",
|
|
204
204
|
"@modelcontextprotocol/client": "^2.0.0",
|
|
205
|
+
"@opentelemetry/api-logs": "^0.222.0",
|
|
206
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.222.0",
|
|
205
207
|
"@opentelemetry/exporter-metrics-otlp-http": "^0.222.0",
|
|
206
208
|
"@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
|
|
207
209
|
"@opentelemetry/instrumentation-http": "^0.222.0",
|
|
208
210
|
"@opentelemetry/instrumentation-pino": "^0.68.0",
|
|
209
211
|
"@opentelemetry/resources": "^2.11.0",
|
|
212
|
+
"@opentelemetry/sdk-logs": "^0.222.0",
|
|
210
213
|
"@opentelemetry/sdk-metrics": "^2.11.0",
|
|
211
214
|
"@opentelemetry/sdk-node": "^0.222.0",
|
|
212
215
|
"@opentelemetry/sdk-trace-node": "^2.11.0",
|
|
213
216
|
"@opentelemetry/semantic-conventions": "^1.43.0",
|
|
214
217
|
"@socketsecurity/bun-security-scanner": "^1.1.3",
|
|
215
|
-
"@supabase/supabase-js": "^2.117.
|
|
218
|
+
"@supabase/supabase-js": "^2.117.1",
|
|
216
219
|
"@types/bun": "^1.4.2",
|
|
217
220
|
"@types/node": "26.6.2",
|
|
218
221
|
"@types/papaparse": "^5.5.2",
|
|
@@ -232,7 +235,7 @@
|
|
|
232
235
|
"js-yaml": "^5.4.2",
|
|
233
236
|
"linkedom": "^0.18.13",
|
|
234
237
|
"node-cron": "^4.6.0",
|
|
235
|
-
"openai": "^7.
|
|
238
|
+
"openai": "^7.23.0",
|
|
236
239
|
"papaparse": "^5.7.0",
|
|
237
240
|
"partial-json": "^0.1.7",
|
|
238
241
|
"pdf-lib": "^1.17.1",
|
|
@@ -297,11 +300,14 @@
|
|
|
297
300
|
"peerDependencies": {
|
|
298
301
|
"@duckdb/node-api": "^1.5.5-r.1",
|
|
299
302
|
"@hono/otel": "^1.1.2",
|
|
303
|
+
"@opentelemetry/api-logs": "^0.222.0",
|
|
304
|
+
"@opentelemetry/exporter-logs-otlp-http": "^0.222.0",
|
|
300
305
|
"@opentelemetry/exporter-metrics-otlp-http": "^0.222.0",
|
|
301
306
|
"@opentelemetry/exporter-trace-otlp-http": "^0.222.0",
|
|
302
307
|
"@opentelemetry/instrumentation-http": "^0.222.0",
|
|
303
308
|
"@opentelemetry/instrumentation-pino": "^0.68.0",
|
|
304
309
|
"@opentelemetry/resources": "^2.10.0",
|
|
310
|
+
"@opentelemetry/sdk-logs": "^0.222.0",
|
|
305
311
|
"@opentelemetry/sdk-metrics": "^2.10.0",
|
|
306
312
|
"@opentelemetry/sdk-node": "^0.222.0",
|
|
307
313
|
"@opentelemetry/sdk-trace-node": "^2.10.0",
|
|
@@ -332,6 +338,12 @@
|
|
|
332
338
|
"@hono/otel": {
|
|
333
339
|
"optional": true
|
|
334
340
|
},
|
|
341
|
+
"@opentelemetry/api-logs": {
|
|
342
|
+
"optional": true
|
|
343
|
+
},
|
|
344
|
+
"@opentelemetry/exporter-logs-otlp-http": {
|
|
345
|
+
"optional": true
|
|
346
|
+
},
|
|
335
347
|
"@opentelemetry/instrumentation-http": {
|
|
336
348
|
"optional": true
|
|
337
349
|
},
|
|
@@ -347,6 +359,9 @@
|
|
|
347
359
|
"@opentelemetry/resources": {
|
|
348
360
|
"optional": true
|
|
349
361
|
},
|
|
362
|
+
"@opentelemetry/sdk-logs": {
|
|
363
|
+
"optional": true
|
|
364
|
+
},
|
|
350
365
|
"@opentelemetry/sdk-metrics": {
|
|
351
366
|
"optional": true
|
|
352
367
|
},
|
|
@@ -20,10 +20,19 @@
|
|
|
20
20
|
*
|
|
21
21
|
* A bare name (`add-tool`) and the file path (`add-tool/SKILL.md`) both match.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* In the framework repo, a skill whose version already moved since the last `v*`
|
|
24
|
+
* release tag — or that is new since it — is covered: a later body edit in the
|
|
25
|
+
* same cycle shares that bump instead of needing another.
|
|
26
|
+
*
|
|
27
|
+
* The inverse also holds: a skill moves at most one step per release. In the
|
|
28
|
+
* framework repo itself, each skill's version is compared with its version at the
|
|
29
|
+
* last `v*` release tag, and anything past the next minor (`1.4` → `1.5`) or the
|
|
30
|
+
* next major (`1.4` → `2.0`) is a violation — repeated edits in one cycle share a
|
|
31
|
+
* single bump. Consumer repos skip this: a skill sync can legitimately jump
|
|
32
|
+
* several framework releases at once.
|
|
33
|
+
*
|
|
25
34
|
* Severity mirrors `check-skills-sync.ts` — exits 1, demoted to a warning by
|
|
26
|
-
* devcheck. New skills (no
|
|
35
|
+
* devcheck. New skills (no prior version) and non-git trees are skipped.
|
|
27
36
|
*
|
|
28
37
|
* Runs standalone (`bun run scripts/check-skill-versions.ts`) and as a devcheck step.
|
|
29
38
|
*
|
|
@@ -36,6 +45,7 @@ import process from 'node:process';
|
|
|
36
45
|
|
|
37
46
|
const ROOT = resolve('.');
|
|
38
47
|
const SKILL_MD_RE = /^framework-skills\/[^/]+\/SKILL\.md$/;
|
|
48
|
+
const FRAMEWORK_PACKAGE = '@cyanheads/mcp-ts-core';
|
|
39
49
|
|
|
40
50
|
interface DevcheckConfig {
|
|
41
51
|
skillVersions?: { ignore?: string[] };
|
|
@@ -60,10 +70,10 @@ function isIgnored(relPath: string, patterns: string[]): boolean {
|
|
|
60
70
|
);
|
|
61
71
|
}
|
|
62
72
|
|
|
63
|
-
/** Skill `SKILL.md` files that differ from `
|
|
64
|
-
function changedSkillFiles(): string[] {
|
|
65
|
-
const result = spawnSync('git', ['diff', '--name-only',
|
|
66
|
-
if (result.status !== 0) return []; // not a git repo / no
|
|
73
|
+
/** Skill `SKILL.md` files that differ from `ref` in the working tree (staged + unstaged). */
|
|
74
|
+
function changedSkillFiles(ref: string): string[] {
|
|
75
|
+
const result = spawnSync('git', ['diff', '--name-only', ref, '--'], { encoding: 'utf-8' });
|
|
76
|
+
if (result.status !== 0) return []; // not a git repo / no such ref
|
|
67
77
|
return result.stdout
|
|
68
78
|
.trim()
|
|
69
79
|
.split('\n')
|
|
@@ -71,18 +81,50 @@ function changedSkillFiles(): string[] {
|
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
/**
|
|
74
|
-
* Content of a path at `
|
|
75
|
-
* A tree renamed from the pre-0.13 `skills/` reads its
|
|
84
|
+
* Content of a path at `ref`, or null when it didn't exist there (new file).
|
|
85
|
+
* A tree renamed from the pre-0.13 `skills/` reads its old copy from the old
|
|
76
86
|
* path, so the release that carries the rename still checks every body edit.
|
|
77
87
|
*/
|
|
78
|
-
function
|
|
79
|
-
const show = (p: string) => spawnSync('git', ['show',
|
|
88
|
+
function contentAt(ref: string, relPath: string): string | null {
|
|
89
|
+
const show = (p: string) => spawnSync('git', ['show', `${ref}:${p}`], { encoding: 'utf-8' });
|
|
80
90
|
const result = show(relPath);
|
|
81
91
|
if (result.status === 0) return result.stdout;
|
|
82
92
|
const legacy = show(relPath.replace(/^framework-skills\//, 'skills/'));
|
|
83
93
|
return legacy.status === 0 ? legacy.stdout : null;
|
|
84
94
|
}
|
|
85
95
|
|
|
96
|
+
/** True when this tree is the framework itself, where skill versions are authored. */
|
|
97
|
+
function isFrameworkRepo(): boolean {
|
|
98
|
+
try {
|
|
99
|
+
const pkg = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')) as {
|
|
100
|
+
name?: string;
|
|
101
|
+
};
|
|
102
|
+
return pkg.name === FRAMEWORK_PACKAGE;
|
|
103
|
+
} catch {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The latest `v*` release tag reachable from `HEAD`, or null when there is none. */
|
|
109
|
+
function lastReleaseTag(): string | null {
|
|
110
|
+
const result = spawnSync('git', ['describe', '--tags', '--abbrev=0', '--match', 'v*'], {
|
|
111
|
+
encoding: 'utf-8',
|
|
112
|
+
});
|
|
113
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** True when `to` is `from`, its next minor, or its next major at `.0`. */
|
|
117
|
+
function withinOneStep(from: string, to: string): boolean {
|
|
118
|
+
const [fromMajor, fromMinor] = from.split('.').map(Number);
|
|
119
|
+
const [toMajor, toMinor] = to.split('.').map(Number);
|
|
120
|
+
if ([fromMajor, fromMinor, toMajor, toMinor].some((n) => n === undefined || Number.isNaN(n))) {
|
|
121
|
+
return true; // not `X.Y` — out of this check's scope
|
|
122
|
+
}
|
|
123
|
+
if (from === to) return true;
|
|
124
|
+
if (toMajor === fromMajor) return toMinor === (fromMinor as number) + 1;
|
|
125
|
+
return toMajor === (fromMajor as number) + 1 && toMinor === 0;
|
|
126
|
+
}
|
|
127
|
+
|
|
86
128
|
/** `metadata.version` from skill frontmatter, or null when absent/unparseable. */
|
|
87
129
|
function extractVersion(content: string): string | null {
|
|
88
130
|
const block = content.match(/^---\n([\s\S]*?)\n---/)?.[1];
|
|
@@ -108,11 +150,24 @@ if (!existsSync(resolve(ROOT, 'framework-skills'))) {
|
|
|
108
150
|
}
|
|
109
151
|
|
|
110
152
|
const ignore = loadIgnorePatterns();
|
|
111
|
-
const
|
|
153
|
+
const tag = isFrameworkRepo() ? lastReleaseTag() : null;
|
|
112
154
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
155
|
+
/**
|
|
156
|
+
* True when the skill's current version already differs from its version at the
|
|
157
|
+
* release tag — or the skill is new since it — so this cycle's one step is taken
|
|
158
|
+
* and a further body edit shares it rather than needing another bump.
|
|
159
|
+
*/
|
|
160
|
+
function bumpedThisRelease(file: string, version: string | null): boolean {
|
|
161
|
+
if (tag === null) return false;
|
|
162
|
+
const released = contentAt(tag, file);
|
|
163
|
+
if (released === null) return true;
|
|
164
|
+
const releasedVersion = extractVersion(released);
|
|
165
|
+
return releasedVersion !== null && releasedVersion !== version;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const missing: { file: string; version: string }[] = [];
|
|
169
|
+
for (const file of changedSkillFiles('HEAD').filter((f) => !isIgnored(f, ignore))) {
|
|
170
|
+
const oldContent = contentAt('HEAD', file);
|
|
116
171
|
if (oldContent === null) continue; // new skill — no prior version to compare
|
|
117
172
|
if (!existsSync(resolve(ROOT, file))) continue; // deleted in worktree — no body to compare, can't violate
|
|
118
173
|
const newContent = readFileSync(resolve(ROOT, file), 'utf-8');
|
|
@@ -121,25 +176,51 @@ for (const file of changed) {
|
|
|
121
176
|
|
|
122
177
|
const oldVersion = extractVersion(oldContent);
|
|
123
178
|
const newVersion = extractVersion(newContent);
|
|
124
|
-
if (oldVersion !== null && oldVersion === newVersion) {
|
|
125
|
-
|
|
179
|
+
if (oldVersion !== null && oldVersion === newVersion && !bumpedThisRelease(file, newVersion)) {
|
|
180
|
+
missing.push({ file, version: oldVersion });
|
|
126
181
|
}
|
|
127
182
|
}
|
|
128
183
|
|
|
129
|
-
|
|
184
|
+
const overshot: { file: string; tag: string; released: string; version: string }[] = [];
|
|
185
|
+
if (tag !== null) {
|
|
186
|
+
for (const file of changedSkillFiles(tag)) {
|
|
187
|
+
const released = contentAt(tag, file);
|
|
188
|
+
if (released === null || !existsSync(resolve(ROOT, file))) continue;
|
|
189
|
+
const releasedVersion = extractVersion(released);
|
|
190
|
+
const version = extractVersion(readFileSync(resolve(ROOT, file), 'utf-8'));
|
|
191
|
+
if (releasedVersion === null || version === null) continue;
|
|
192
|
+
if (!withinOneStep(releasedVersion, version)) {
|
|
193
|
+
overshot.push({ file, tag, released: releasedVersion, version });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const count = missing.length + overshot.length;
|
|
199
|
+
if (count === 0) {
|
|
130
200
|
console.log('Skill versions are in step with body changes.');
|
|
131
201
|
process.exit(0);
|
|
132
202
|
}
|
|
133
203
|
|
|
134
204
|
const lines = [
|
|
135
|
-
`${
|
|
205
|
+
`${count} skill${count === 1 ? '' : 's'} out of step with the versioning policy:`,
|
|
136
206
|
'',
|
|
137
207
|
];
|
|
138
|
-
for (const v of
|
|
208
|
+
for (const v of missing) {
|
|
139
209
|
lines.push(` - ${v.file} body changed but metadata.version is still "${v.version}"`);
|
|
140
210
|
}
|
|
211
|
+
for (const v of overshot) {
|
|
212
|
+
lines.push(
|
|
213
|
+
` - ${v.file} is "${v.version}", more than one step past "${v.released}" at ${v.tag}`,
|
|
214
|
+
);
|
|
215
|
+
}
|
|
141
216
|
lines.push('');
|
|
142
|
-
|
|
143
|
-
lines.push('
|
|
217
|
+
if (missing.length > 0) {
|
|
218
|
+
lines.push('Fix: bump metadata.version in the SKILL.md frontmatter, or add the skill to');
|
|
219
|
+
lines.push(' devcheck.config.json `skillVersions.ignore` for the typo/whitespace carve-out.');
|
|
220
|
+
}
|
|
221
|
+
if (overshot.length > 0) {
|
|
222
|
+
lines.push('Fix: set metadata.version to one step past the release tag — edits in one');
|
|
223
|
+
lines.push(' release cycle share a single bump.');
|
|
224
|
+
}
|
|
144
225
|
console.log(lines.join('\n'));
|
|
145
226
|
process.exit(1);
|
package/scripts/devcheck.ts
CHANGED
|
@@ -811,7 +811,8 @@ const ALL_CHECKS: Check[] = [
|
|
|
811
811
|
flag: '--no-skill-versions',
|
|
812
812
|
canFix: false,
|
|
813
813
|
// Flags framework-skills/<name>/SKILL.md body changes (vs HEAD) that lack a metadata.version
|
|
814
|
-
// bump (#99)
|
|
814
|
+
// bump (#99), and, in the framework repo, a skill bumped more than one step past the last
|
|
815
|
+
// release tag. Skipped when framework-skills/ is absent. Drift is demoted to a warning via
|
|
815
816
|
// isSuccess — the typo/whitespace carve-out lives in devcheck.config.json
|
|
816
817
|
// `skillVersions.ignore`.
|
|
817
818
|
getCommand: () => {
|
|
@@ -821,11 +822,11 @@ const ALL_CHECKS: Check[] = [
|
|
|
821
822
|
isSuccess: (result) => {
|
|
822
823
|
if (result.exitCode === 0) return true;
|
|
823
824
|
const firstLine =
|
|
824
|
-
result.stdout.split('\n')[0]?.trim() || 'Skill
|
|
825
|
+
result.stdout.split('\n')[0]?.trim() || 'Skill versions are out of step with the policy.';
|
|
825
826
|
return { success: true, warning: firstLine };
|
|
826
827
|
},
|
|
827
828
|
tip: (c) =>
|
|
828
|
-
`Bump ${c.bold('metadata.version')} in the changed ${c.bold('SKILL.md')}, or add it to ${c.bold('devcheck.config.json')} ${c.bold('skillVersions.ignore')}.`,
|
|
829
|
+
`Bump ${c.bold('metadata.version')} once per release in the changed ${c.bold('SKILL.md')}, or add it to ${c.bold('devcheck.config.json')} ${c.bold('skillVersions.ignore')}.`,
|
|
829
830
|
},
|
|
830
831
|
{
|
|
831
832
|
name: 'Changelog Sync',
|
|
@@ -59,6 +59,12 @@
|
|
|
59
59
|
* package's headline version on GitHub and npmjs.com and ships in the
|
|
60
60
|
* tarball, so a half-finished bump is publicly visible. Skipped when the
|
|
61
61
|
* README, the badge, or the package version is absent (issue #418).
|
|
62
|
+
* 13. npm `files` excludes the built bundle: when `manifest.json` exists and
|
|
63
|
+
* `package.json` `files` covers `dist/` wholesale, it must also carry
|
|
64
|
+
* `"!dist/*.mcpb"`. The `bundle` script writes the `.mcpb` into `dist/`,
|
|
65
|
+
* so without the entry a release that bundles before publishing ships the
|
|
66
|
+
* server and its production dependencies inside the npm tarball
|
|
67
|
+
* (issue #469). Skipped when `manifest.json` or `files` is absent.
|
|
62
68
|
*
|
|
63
69
|
* Every check skips cleanly when its input is absent — consumers who deleted
|
|
64
70
|
* `manifest.json` for an HTTP-only deploy, or who haven't built a bundle,
|
|
@@ -742,6 +748,33 @@ export function checkReadmeVersionBadge(readme: string, packageVersion?: string)
|
|
|
742
748
|
];
|
|
743
749
|
}
|
|
744
750
|
|
|
751
|
+
/** The `files` entry that keeps the `bundle` script's `dist/<name>.mcpb` out of the npm tarball. */
|
|
752
|
+
const BUNDLE_EXCLUSION = '!dist/*.mcpb';
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Check 13: a `files` allowlist that covers `dist/` wholesale must exclude the
|
|
756
|
+
* `.mcpb` the `bundle` script writes there. npm and Bun both honor `!` entries
|
|
757
|
+
* in `files`, and both ignore `.npmignore` once `files` is set, so the entry is
|
|
758
|
+
* the only place the exclusion can live. The caller runs this only when
|
|
759
|
+
* `manifest.json` exists — a project without one never builds a bundle.
|
|
760
|
+
*/
|
|
761
|
+
export function checkBundleExcludedFromFiles(files: unknown): string[] {
|
|
762
|
+
if (!Array.isArray(files)) return [];
|
|
763
|
+
const entries = files.filter((entry): entry is string => typeof entry === 'string');
|
|
764
|
+
|
|
765
|
+
const coversDist = entries.some(
|
|
766
|
+
(entry) => entry.replace(/^\.\//, '').replace(/\/(?:\*\*(?:\/\*)?|\*)?$/, '') === 'dist',
|
|
767
|
+
);
|
|
768
|
+
if (!coversDist) return [];
|
|
769
|
+
if (entries.includes(BUNDLE_EXCLUSION) || entries.includes('!dist/**/*.mcpb')) return [];
|
|
770
|
+
|
|
771
|
+
return [
|
|
772
|
+
`package.json "files" covers dist/ without "${BUNDLE_EXCLUSION}" — the bundle script writes ` +
|
|
773
|
+
`dist/<name>.mcpb, so a release that bundles before publishing ships it in the npm tarball; ` +
|
|
774
|
+
`add "${BUNDLE_EXCLUSION}" to "files"`,
|
|
775
|
+
];
|
|
776
|
+
}
|
|
777
|
+
|
|
745
778
|
/** Read `packaging.pluginManifests` from devcheck.config.json; default on. */
|
|
746
779
|
function pluginManifestsEnabled(): boolean {
|
|
747
780
|
const cfg = tryReadJson<{ packaging?: { pluginManifests?: boolean } }>(
|
|
@@ -755,7 +788,9 @@ async function main(): Promise<void> {
|
|
|
755
788
|
const warnings: string[] = [];
|
|
756
789
|
const notes: string[] = [];
|
|
757
790
|
|
|
758
|
-
const pkg = tryReadJson<{ name?: string; version?: string }>(
|
|
791
|
+
const pkg = tryReadJson<{ files?: unknown; name?: string; version?: string }>(
|
|
792
|
+
resolve('package.json'),
|
|
793
|
+
);
|
|
759
794
|
const unscopedName = pkg?.name?.split('/').pop();
|
|
760
795
|
|
|
761
796
|
// ── Manifest-dependent checks (1–4 + manifest identity) ──
|
|
@@ -826,6 +861,8 @@ async function main(): Promise<void> {
|
|
|
826
861
|
if (unscopedName) {
|
|
827
862
|
errors.push(...checkManifestIdentity(manifest, unscopedName));
|
|
828
863
|
}
|
|
864
|
+
|
|
865
|
+
errors.push(...checkBundleExcludedFromFiles(pkg?.files));
|
|
829
866
|
} else {
|
|
830
867
|
notes.push('No manifest.json — skipping manifest/server.json alignment checks.');
|
|
831
868
|
}
|
package/templates/.env.example
CHANGED
|
@@ -31,12 +31,16 @@ MCP_SESSION_MODE=stateless # stateful | stateless | auto. Set here, not
|
|
|
31
31
|
# MCP_LOG_LEVEL=info # debug | info | notice | warning | error
|
|
32
32
|
# MCP_LOG_RATE_LIMIT_THRESHOLD=10 # Max emissions per level+message per window; 0 disables
|
|
33
33
|
# MCP_LOG_RATE_LIMIT_WINDOW_MS=60000
|
|
34
|
+
# LOG_TOOL_FAILURE_PAYLOADS=false # Log failed tool calls' arguments + result (key-name redaction only;
|
|
35
|
+
# secrets in free-form values are kept). Reaches stderr, files, and OTLP
|
|
36
|
+
# LOG_TOOL_FAILURE_PAYLOAD_MAX_BYTES=16384 # Per-payload cap, UTF-8 bytes
|
|
34
37
|
|
|
35
38
|
# ── Telemetry ─────────────────────────────────────────────────────────
|
|
36
39
|
# OTEL_ENABLED=false # Enable OpenTelemetry (default: false)
|
|
37
40
|
# OTEL_EXPORTER_OTLP_ENDPOINT= # OTLP base URL (e.g., http://localhost:4318); traces → /v1/traces, metrics → /v1/metrics
|
|
38
41
|
# OTEL_EXPORTER_OTLP_TRACES_ENDPOINT= # Overrides the base for traces; used as-is
|
|
39
42
|
# OTEL_EXPORTER_OTLP_METRICS_ENDPOINT= # Overrides the base for metrics; used as-is
|
|
43
|
+
# OTEL_EXPORTER_OTLP_LOGS_ENDPOINT= # Opt-in log export (e.g., http://localhost:4318/v1/logs); the base never enables it
|
|
40
44
|
|
|
41
45
|
# ── Server-specific ──────────────────────────────────────────────────
|
|
42
46
|
# Add your server's environment variables below
|
package/templates/Dockerfile
CHANGED
|
@@ -56,8 +56,15 @@ LABEL org.opencontainers.image.licenses="Apache-2.0"
|
|
|
56
56
|
LABEL org.opencontainers.image.version="${APP_VERSION}"
|
|
57
57
|
LABEL org.opencontainers.image.source=""
|
|
58
58
|
|
|
59
|
-
# Copy dependency manifests
|
|
60
|
-
|
|
59
|
+
# Copy dependency manifests. `bunfig.toml` rides along so every install below
|
|
60
|
+
# passes its release-age gate and security scanner, as a local install does.
|
|
61
|
+
COPY package.json bun.lock bunfig.toml ./
|
|
62
|
+
|
|
63
|
+
# The scanner bunfig.toml names is a devDependency, and Bun installs a missing
|
|
64
|
+
# scanner through the same production-filtered install, which omits it and
|
|
65
|
+
# aborts. Seed it from the build stage's full install instead. Remove this line
|
|
66
|
+
# together with the scanner if bunfig.toml stops naming one.
|
|
67
|
+
COPY --from=build /usr/src/app/node_modules/@socketsecurity/bun-security-scanner ./node_modules/@socketsecurity/bun-security-scanner
|
|
61
68
|
|
|
62
69
|
# Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
|
|
63
70
|
# that are not needed in the final production image.
|
|
@@ -72,19 +79,33 @@ RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
|
72
79
|
# Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
|
|
73
80
|
# Installed by default. Omit them for a leaner image at build time
|
|
74
81
|
# with: docker build --build-arg OTEL_ENABLED=false
|
|
82
|
+
# Each package is requested at the range the installed framework declares in
|
|
83
|
+
# `peerDependencies`, so the resolution stays inside the framework's tested
|
|
84
|
+
# peer range; a name with no declared range fails the build.
|
|
75
85
|
ARG OTEL_ENABLED=true
|
|
76
86
|
RUN --mount=type=cache,target=/root/.bun/install/cache \
|
|
77
87
|
if [ "$OTEL_ENABLED" = "true" ]; then \
|
|
78
|
-
bun
|
|
79
|
-
|
|
88
|
+
specs=$(bun -e ' \
|
|
89
|
+
const { peerDependencies: peers } = await Bun.file("node_modules/@cyanheads/mcp-ts-core/package.json").json(); \
|
|
90
|
+
const names = process.argv.slice(1); \
|
|
91
|
+
const missing = names.filter((name) => !peers?.[name]); \
|
|
92
|
+
if (missing.length > 0) throw new Error(`no peerDependencies range for ${missing.join(", ")}`); \
|
|
93
|
+
console.log(names.map((name) => `${name}@${peers[name]}`).join(" ")); \
|
|
94
|
+
' \
|
|
95
|
+
@hono/otel \
|
|
96
|
+
@opentelemetry/api-logs \
|
|
97
|
+
@opentelemetry/exporter-logs-otlp-http \
|
|
80
98
|
@opentelemetry/exporter-metrics-otlp-http \
|
|
81
99
|
@opentelemetry/exporter-trace-otlp-http \
|
|
100
|
+
@opentelemetry/instrumentation-http \
|
|
82
101
|
@opentelemetry/instrumentation-pino \
|
|
83
102
|
@opentelemetry/resources \
|
|
103
|
+
@opentelemetry/sdk-logs \
|
|
84
104
|
@opentelemetry/sdk-metrics \
|
|
85
105
|
@opentelemetry/sdk-node \
|
|
86
106
|
@opentelemetry/sdk-trace-node \
|
|
87
|
-
@opentelemetry/semantic-conventions
|
|
107
|
+
@opentelemetry/semantic-conventions) \
|
|
108
|
+
&& bun add --omit=dev --omit=peer --ignore-scripts $specs; \
|
|
88
109
|
fi
|
|
89
110
|
|
|
90
111
|
# Copy the compiled application code from the build stage
|
|
@@ -128,7 +149,6 @@ ENV MCP_TRANSPORT_TYPE="http"
|
|
|
128
149
|
ENV MCP_SESSION_MODE="stateless"
|
|
129
150
|
ENV MCP_LOG_LEVEL="info"
|
|
130
151
|
ENV LOGS_DIR="/var/log/{{PACKAGE_NAME}}"
|
|
131
|
-
ENV MCP_FORCE_CONSOLE_LOGGING="true"
|
|
132
152
|
|
|
133
153
|
# Expose the port the server listens on
|
|
134
154
|
EXPOSE ${MCP_HTTP_PORT}
|