@cyanheads/mcp-ts-core 0.8.17 → 0.8.19
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/CLAUDE.md +47 -46
- package/README.md +36 -38
- package/changelog/0.8.x/0.8.18.md +17 -0
- package/changelog/0.8.x/0.8.19.md +33 -0
- package/changelog/template.md +71 -44
- package/dist/cli/init.js +12 -5
- package/dist/cli/init.js.map +1 -1
- package/dist/logs/combined.log +6 -6
- package/dist/logs/error.log +4 -4
- package/dist/utils/internal/requestContext.d.ts +7 -0
- package/dist/utils/internal/requestContext.d.ts.map +1 -1
- package/dist/utils/internal/requestContext.js +1 -0
- package/dist/utils/internal/requestContext.js.map +1 -1
- package/package.json +20 -18
- package/scripts/build-changelog.ts +27 -18
- package/skills/api-telemetry/SKILL.md +222 -0
- package/skills/api-utils/SKILL.md +3 -1
- package/skills/maintenance/SKILL.md +16 -6
- package/skills/polish-docs-meta/references/package-meta.md +1 -1
- package/skills/report-issue-framework/SKILL.md +5 -3
- package/skills/report-issue-local/SKILL.md +5 -3
- package/skills/setup/SKILL.md +10 -9
- package/templates/AGENTS.md +20 -10
- package/templates/CLAUDE.md +20 -10
- package/templates/Dockerfile +2 -2
- package/templates/changelog/template.md +71 -44
- package/templates/package.json +2 -2
package/CLAUDE.md
CHANGED
|
@@ -1,9 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Developer Protocol
|
|
2
2
|
|
|
3
|
-
**Package:** `@cyanheads/mcp-ts-core`
|
|
4
|
-
**
|
|
3
|
+
**Package:** `@cyanheads/mcp-ts-core`
|
|
4
|
+
**Version:** 0.8.19
|
|
5
|
+
**Engines:** Bun ≥1.3.0, Node ≥24.0.0
|
|
6
|
+
**MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
|
|
7
|
+
**Zod:** ^4.4.3
|
|
8
|
+
**GitHub:** [cyanheads/mcp-ts-core](https://github.com/cyanheads/mcp-ts-core)
|
|
9
|
+
**npm:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)
|
|
10
|
+
**Docker:** [ghcr.io/cyanheads/mcp-ts-core](https://ghcr.io/cyanheads/mcp-ts-core)
|
|
5
11
|
|
|
6
|
-
> **Developer note:** Never assume. Read related files and docs before making changes. Read full file content for context. Never edit a file before reading it.
|
|
12
|
+
> **Developer note:** Never assume. Read related files and docs before making changes. Read full file content for context. Never try to edit a file before reading it.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Consumers
|
|
17
|
+
|
|
18
|
+
This package serves two consumer paths. When making changes, know which audience your change affects:
|
|
19
|
+
|
|
20
|
+
| Path | On-ramp | Affected by changes to |
|
|
21
|
+
|:--|:--|:--|
|
|
22
|
+
| **Direct package import** — existing project pulls in the package | `bun add @cyanheads/mcp-ts-core` → `import { createApp, tool, z } from '@cyanheads/mcp-ts-core'` | Public API surface (`src/`) — existing consumers feel changes immediately on upgrade |
|
|
23
|
+
| **Init-scaffolded server** — fresh project bootstrapped from this repo's templates | `bunx @cyanheads/mcp-ts-core init [name]` copies `templates/` into the new directory | `templates/` — only affects newly scaffolded servers, not existing ones |
|
|
24
|
+
|
|
25
|
+
Both paths end up consuming the same public API. The init script bootstraps the project structure: starter `package.json`, `tsconfig`, `biome.json`, `vitest.config.ts`, `.env.example`, `Dockerfile`, `CLAUDE.md`/`AGENTS.md`, and example tool/resource/prompt definitions. Files in `templates/` prefixed with `_` (e.g. `_.gitignore`, `_tsconfig.json`) have the prefix stripped on copy. After init, agents should consult the `setup` skill.
|
|
7
26
|
|
|
8
27
|
---
|
|
9
28
|
|
|
@@ -13,7 +32,7 @@
|
|
|
13
32
|
- **Full-stack observability.** The framework automatically instruments every tool/resource call — OTel span, duration/payload/memory metrics, structured completion log. Use `ctx.log` for additional domain-specific logging within handlers (external API calls, multi-step operations, business events). `requestId`, `traceId`, `tenantId` auto-correlated. No `console` calls.
|
|
14
33
|
- **Unified Context.** Handlers receive `ctx` with logging (`ctx.log`), tenant-scoped storage (`ctx.state`), optional protocol capabilities (`ctx.elicit`, `ctx.sample`), and cancellation (`ctx.signal`).
|
|
15
34
|
- **Decoupled storage.** `ctx.state` for tenant-scoped KV. Never access persistence backends directly.
|
|
16
|
-
- **Canvas tokens are capabilities, not tenant-scoped state.** A `canvasId` is a 10-char URL-safe token; possession grants full read/write/drop on that canvas. Tokens are designed to be shareable — between agents in one session, and across users in single-tenant deployments (
|
|
35
|
+
- **Canvas tokens are capabilities, not tenant-scoped state.** A `canvasId` is a 10-char URL-safe token; possession grants full read/write/drop on that canvas. Tokens are designed to be shareable — between agents in one session, and across users in single-tenant deployments (see tenant resolution table under `ctx.state`). Tools should accept the token in `input` (or omit to create fresh) and return it in `output`; collaboration is opt-in via explicit token exchange.
|
|
17
36
|
- **Runtime parity.** All features work with `stdio`/`http` and Worker bundle. Guard non-portable deps via `runtimeCaps` from `@cyanheads/mcp-ts-core/utils` — a frozen capability object (`isNode`, `isBun`, `isWorkerLike`, `hasBuffer`, `hasProcess`, etc.) computed once at module load. Prefer runtime-agnostic abstractions (Hono + `@hono/mcp`, Fetch APIs).
|
|
18
37
|
- **Startup validation.** `createApp()` runs the definition linter before proceeding — errors (spec violations) throw `ConfigurationError` and block startup; warnings are logged. Also available standalone via `bun run lint:mcp` and as a devcheck step. Every diagnostic links to the rule reference in `api-linter` skill; see that skill for the full rule catalog.
|
|
19
38
|
- **Elicitation for missing input.** Use `ctx.elicit` when the client supports it.
|
|
@@ -151,10 +170,6 @@ src/
|
|
|
151
170
|
|
|
152
171
|
**File suffixes:** `.tool.ts` (standard or task), `.resource.ts`, `.prompt.ts`, `.app-tool.ts` (UI-enabled), `.app-resource.ts` (UI resource linked to app tool).
|
|
153
172
|
|
|
154
|
-
**Scaffold a new server:** `npx @cyanheads/mcp-ts-core init [name]` copies `templates/` into a new project. After running, consult the `setup` skill.
|
|
155
|
-
|
|
156
|
-
**`templates/` directory:** Scaffolding source for the init CLI. Contents are copied into new consumer servers — includes starter `package.json`, `tsconfig`, `biome.json`, `vitest.config.ts`, `.env.example`, `Dockerfile`, `CLAUDE.md`/`AGENTS.md`, and example tool/resource/prompt definitions. Files prefixed with `_` (e.g. `_.gitignore`, `_tsconfig.json`) are renamed on copy (strip `_` prefix). Changes here affect every newly scaffolded server.
|
|
157
|
-
|
|
158
173
|
---
|
|
159
174
|
|
|
160
175
|
## Adding a Tool
|
|
@@ -199,9 +214,14 @@ export const myTool = tool('my_tool', {
|
|
|
199
214
|
|
|
200
215
|
**Schema constraint:** Input/output schemas must use JSON-Schema-serializable Zod types only. The MCP SDK converts schemas to JSON Schema for `tools/list` — non-serializable types (`z.custom()`, `z.date()`, `z.transform()`, `z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`, `z.function()`, `z.nan()`) cause a hard runtime failure. Use structural equivalents instead (e.g., `z.string()` with `.describe('ISO 8601 date')` instead of `z.date()`). The linter validates this at startup.
|
|
201
216
|
|
|
202
|
-
**Form-client safety:** Form-based
|
|
217
|
+
**Form-client safety:** Form-based clients (MCP Inspector, web UIs) send optional fields as empty strings, not `undefined`. Don't reject with `.min(1)` on optional fields — guard for meaningful values in the handler (`if (input.dateRange?.minDate && input.dateRange?.maxDate)`). Test with both omitted and empty-value payloads. When schema-level constraints (regex/length) need to surface in the JSON Schema, wrap in a union with a `z.literal('')` sentinel: `z.union([z.literal(''), z.string().regex(...).describe(...)])` — the linter exempts the literal variant from `describe-on-fields`.
|
|
218
|
+
|
|
219
|
+
**`format`**: Maps output to MCP `content[]`. Different clients forward different surfaces to the agent — some (Claude Code) read `structuredContent` from `output`, others (Claude Desktop) read `content[]` from `format()`. `format()` is the markdown twin of `structuredContent`, not a reduced summary.
|
|
203
220
|
|
|
204
|
-
|
|
221
|
+
- **Parity is enforced.** Every terminal field in `output` must appear in `format()`'s rendered text (via sentinel injection), or startup fails with a `format-parity` lint error.
|
|
222
|
+
- **Primary fix:** render the missing field in `format()`. Use `z.discriminatedUnion` for list/detail variants — each branch is validated separately.
|
|
223
|
+
- **Escape hatch:** if the schema was over-typed for a genuinely dynamic upstream API, relax it (`z.object({}).passthrough()`) — passthrough still flows data to `structuredContent`.
|
|
224
|
+
- **Fallback:** omit `format` for JSON stringify. Additional formatters in `/utils`: `markdown()` (builder), `diffFormatter` (async), `tableFormatter`, `treeFormatter`.
|
|
205
225
|
|
|
206
226
|
**Task tools:** Add `task: true` for long-running async operations. Framework manages lifecycle: creates task → returns ID immediately → runs handler in background with `ctx.progress` → stores result/error → `ctx.signal` for cancellation. See `add-tool` skill for full example.
|
|
207
227
|
|
|
@@ -396,11 +416,7 @@ For HTTP responses from upstream APIs, use `httpErrorFromResponse(response, { se
|
|
|
396
416
|
|
|
397
417
|
**Auto-classification.** Plain `Error`, `ZodError`, and any other thrown value are caught and classified automatically. Resolution order: `McpError` code (preserved as-is) → JS constructor name (`TypeError` → `ValidationError`) → provider patterns (HTTP status codes, AWS errors, DB errors) → common message patterns → `AbortError` name → `InternalError` fallback.
|
|
398
418
|
|
|
399
|
-
**Error-path parity.** Tool errors
|
|
400
|
-
- `content[]` (read by clients like Claude Desktop) — markdown rendering of the error, with `data.recovery.hint` mirrored into the text when present
|
|
401
|
-
- `structuredContent.error` (read by clients like Claude Code) — JSON `{ code, message, data? }` carrying the error code, message, and any structured data from the thrown `McpError` or `ZodError`
|
|
402
|
-
|
|
403
|
-
`_meta.error` is **not** emitted on tool errors — error data lives on `structuredContent.error`. Resources re-throw to the SDK, which routes them through the JSON-RPC error envelope (no parity wiring needed there).
|
|
419
|
+
**Error-path parity.** Tool errors apply the same client-surface parity as success: `content[]` carries a markdown rendering with `data.recovery.hint` mirrored in, `structuredContent.error` carries `{ code, message, data? }`. `_meta.error` is not emitted. Resources re-throw to the SDK and route through the JSON-RPC error envelope (no parity wiring there).
|
|
404
420
|
|
|
405
421
|
The startup linter checks handler bodies for `prefer-mcp-error-in-handler`, `prefer-error-factory`, `preserve-cause-on-rethrow`, `no-stringify-upstream-error`, plus contract conformance (`error-contract-conformance` for undeclared non-baseline codes, `error-contract-prefer-fail` for declared codes thrown directly instead of via `ctx.fail`) — all warnings, surfaced in `bun run devcheck`.
|
|
406
422
|
|
|
@@ -494,7 +510,8 @@ Each `skills/<name>/SKILL.md` carries a `metadata.version` string in its frontma
|
|
|
494
510
|
|
|
495
511
|
| Skill | Path | Covers |
|
|
496
512
|
|:------|:-----|:-------|
|
|
497
|
-
| `api-utils` | `skills/api-utils/SKILL.md` | formatting, parsing, security, network, pagination, runtime, scheduling, types, logger, requestContext, errorHandler, telemetry |
|
|
513
|
+
| `api-utils` | `skills/api-utils/SKILL.md` | formatting, parsing, security, network, pagination, runtime, scheduling, types, logger, requestContext, errorHandler, telemetry helpers (`withSpan`, `createCounter`, …) |
|
|
514
|
+
| `api-telemetry` | `skills/api-telemetry/SKILL.md` | OTel catalog: span names, metric names + attributes, completion log fields, env config, runtime support, cardinality rules |
|
|
498
515
|
| `api-services` | `skills/api-services/SKILL.md` | LLM (OpenRouter), Speech (ElevenLabs TTS, Whisper STT), Graph (CRUD, traversal, pathfinding) |
|
|
499
516
|
| `api-context` | `skills/api-context/SKILL.md` | Context interface, createContext, ContextLogger/State/Progress |
|
|
500
517
|
| `api-errors` | `skills/api-errors/SKILL.md` | McpError, JsonRpcErrorCode, error handling patterns |
|
|
@@ -535,7 +552,7 @@ Each `skills/<name>/SKILL.md` carries a `metadata.version` string in its frontma
|
|
|
535
552
|
- **JSDoc:** `@fileoverview` + `@module` required on every file
|
|
536
553
|
- **No fabricated signal:** Don't invent synthetic scores or arbitrary "confidence percentages." Surface real signal.
|
|
537
554
|
- **Builders:** `tool()`/`resource()`/`prompt()` with correct fields (`handler`, `input`, `output`, `format`, `auth`, `args`)
|
|
538
|
-
- **`format()` completeness:**
|
|
555
|
+
- **`format()` completeness:** must carry the same data as `output` (parity is lint-enforced — see Adding a Tool)
|
|
539
556
|
- **Auth:** via `auth: ['scope']` on definitions (not HOF wrapper)
|
|
540
557
|
- **Presence checks:** `ctx.elicit`/`ctx.sample` checked before use
|
|
541
558
|
- **Task tools:** use `task: true` flag
|
|
@@ -547,14 +564,6 @@ Each `skills/<name>/SKILL.md` carries a `metadata.version` string in its frontma
|
|
|
547
564
|
|
|
548
565
|
---
|
|
549
566
|
|
|
550
|
-
## Git
|
|
551
|
-
|
|
552
|
-
**Safety:** NEVER `git stash`. NEVER destructive commands (`reset --hard`, `checkout -- .`, `restore .`, `clean -f`) unless user explicitly requests. Read-only is always safe.
|
|
553
|
-
|
|
554
|
-
**Commits:** Plain `-m` strings, no heredoc/command substitution. [Conventional Commits](https://www.conventionalcommits.org/): `feat|fix|refactor|chore|docs|test|build(scope): message`. Group related changes atomically.
|
|
555
|
-
|
|
556
|
-
---
|
|
557
|
-
|
|
558
567
|
## Commands
|
|
559
568
|
|
|
560
569
|
| Command | Purpose |
|
|
@@ -576,7 +585,9 @@ After `bun update --latest`, run the `maintenance` skill to investigate changelo
|
|
|
576
585
|
|
|
577
586
|
## Changelog
|
|
578
587
|
|
|
579
|
-
Directory-based, grouped by minor series
|
|
588
|
+
Directory-based, grouped by minor series via the `.x` semver-wildcard convention. Source of truth is `changelog/<major.minor>.x/<version>.md` — one standalone file per release (e.g. `changelog/0.5.x/0.5.4.md`). Per-version files ship in the npm package so agents can read a specific version directly from `node_modules` without parsing a monolithic file.
|
|
589
|
+
|
|
590
|
+
At release time, author the per-version file with a concrete version and date, then run `bun run changelog:build` to regenerate the rollup. `changelog/template.md` is a format reference — never edited; consult it for frontmatter and section layout when scaffolding a new file. Be concise and accurate.
|
|
580
591
|
|
|
581
592
|
`CHANGELOG.md` is a **navigation index**, not a copy of bodies — each entry is a clickable header + one-line summary pulled from the per-version file's frontmatter. Regenerated by `bun run changelog:build`. Devcheck runs `changelog:check` and hard-fails on drift. Never hand-edit `CHANGELOG.md` — edit the per-version file and rerun the build.
|
|
582
593
|
|
|
@@ -584,14 +595,13 @@ Directory-based, grouped by minor series using the `.x` semver-wildcard conventi
|
|
|
584
595
|
|
|
585
596
|
```markdown
|
|
586
597
|
---
|
|
587
|
-
summary: One-line headline, ≤250 chars, no markdown # required
|
|
588
|
-
breaking: false
|
|
598
|
+
summary: "One-line headline, ≤250 chars, no markdown" # required
|
|
599
|
+
breaking: false # optional, default false
|
|
600
|
+
security: false # optional, default false
|
|
589
601
|
---
|
|
590
602
|
|
|
591
603
|
# 0.5.4 — 2026-04-20
|
|
592
604
|
|
|
593
|
-
Optional narrative intro (1-3 sentences).
|
|
594
|
-
|
|
595
605
|
## Added
|
|
596
606
|
|
|
597
607
|
- ...
|
|
@@ -601,10 +611,13 @@ Optional narrative intro (1-3 sentences).
|
|
|
601
611
|
|
|
602
612
|
| Field | Required | Purpose |
|
|
603
613
|
|:------|:---------|:--------|
|
|
604
|
-
| `summary` | yes | Rollup index line.
|
|
614
|
+
| `summary` | yes | Rollup index line. ≤250 chars, no markdown, single line. Write like a GitHub Release title. |
|
|
605
615
|
| `breaking` | no (default `false`) | Flags releases with breaking changes. Renders as `· ⚠️ Breaking` badge in the rollup. Agents running the `maintenance` skill read this to prioritize review. |
|
|
616
|
+
| `security` | no (default `false`) | Flags releases with security fixes. Renders as `· 🛡️ Security` badge in the rollup so users can triage upgrade urgency. Pairs with the `## Security` body section. |
|
|
617
|
+
|
|
618
|
+
When both flags are set, badges render in fixed order: `· ⚠️ Breaking · 🛡️ Security`. Summary > 250 chars or a malformed boolean fails `changelog:check`. Missing `summary` emits a warning and renders the rollup entry as header-only.
|
|
606
619
|
|
|
607
|
-
|
|
620
|
+
**Section order** (Keep a Changelog): Added, Changed, Deprecated, Removed, Fixed, Security. Include only sections with entries — don't ship empty headers.
|
|
608
621
|
|
|
609
622
|
Pre-release versions (`0.6.0-beta.1`, `0.6.0-rc.1`, etc.) are consolidated as `##`/`###` sub-headers inside the final version's per-version file (`changelog/0.6.x/0.6.0.md`) when the final ships — they share the final version's frontmatter, no separate files per pre-release.
|
|
610
623
|
|
|
@@ -612,16 +625,4 @@ Pre-release versions (`0.6.0-beta.1`, `0.6.0-rc.1`, etc.) are consolidated as `#
|
|
|
612
625
|
|
|
613
626
|
## Publishing
|
|
614
627
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
```bash
|
|
618
|
-
bun publish --access public
|
|
619
|
-
|
|
620
|
-
docker buildx build --platform linux/amd64,linux/arm64 \
|
|
621
|
-
-t ghcr.io/cyanheads/mcp-ts-core:<version> \
|
|
622
|
-
-t ghcr.io/cyanheads/mcp-ts-core:latest \
|
|
623
|
-
--push .
|
|
624
|
-
|
|
625
|
-
mcp-publisher publish
|
|
626
|
-
```
|
|
627
|
-
|
|
628
|
+
If the user requests it, run the `release-and-publish` skill — it runs the verification gate (`devcheck`, `rebuild`, `test:all`), pushes commits and tags, and publishes to every applicable destination. The full reference:
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
<div align="center">
|
|
7
7
|
|
|
8
|
-
[](./CHANGELOG.md) [](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [](https://modelcontextprotocol.io/) [](./LICENSE)
|
|
9
9
|
|
|
10
10
|
[](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
## What is this?
|
|
17
17
|
|
|
18
|
-
`@cyanheads/mcp-ts-core` is the infrastructure layer for TypeScript MCP servers. Install it as a dependency — don't fork it.
|
|
18
|
+
`@cyanheads/mcp-ts-core` is the infrastructure layer for TypeScript MCP servers. Install it as a dependency — don't fork it. Your agent collaborates with you to design and build the tools, resources, and prompts for your server.
|
|
19
|
+
|
|
20
|
+
The framework handles the plumbing: transports, auth, config, logging, telemetry, & more. Define your domain logic with the builders and let the framework take care of the rest.
|
|
19
21
|
|
|
20
22
|
```ts
|
|
21
23
|
import { createApp, tool, z } from '@cyanheads/mcp-ts-core';
|
|
@@ -23,15 +25,21 @@ import { createApp, tool, z } from '@cyanheads/mcp-ts-core';
|
|
|
23
25
|
const greet = tool('greet', {
|
|
24
26
|
description: 'Greet someone by name and return a personalized message.',
|
|
25
27
|
annotations: { readOnlyHint: true },
|
|
26
|
-
input: z.object({
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
input: z.object({
|
|
29
|
+
name: z.string().describe('Name of the person to greet'),
|
|
30
|
+
}),
|
|
31
|
+
output: z.object({
|
|
32
|
+
message: z.string().describe('The greeting message'),
|
|
33
|
+
}),
|
|
34
|
+
handler: async (input) => ({
|
|
35
|
+
message: `Hello, ${input.name}!`,
|
|
36
|
+
}),
|
|
29
37
|
});
|
|
30
38
|
|
|
31
39
|
await createApp({ tools: [greet] });
|
|
32
40
|
```
|
|
33
41
|
|
|
34
|
-
That's a complete MCP server. Every tool call is automatically logged with duration, payload sizes,
|
|
42
|
+
That's a complete MCP server. Every tool call is automatically logged with duration, payload sizes, and request correlation — no instrumentation code needed. `createApp()` handles config parsing, logger init, transport startup, signal handlers, and graceful shutdown.
|
|
35
43
|
|
|
36
44
|
## Quick start
|
|
37
45
|
|
|
@@ -43,7 +51,7 @@ bun install
|
|
|
43
51
|
|
|
44
52
|
You get a scaffolded project with `CLAUDE.md`, Agent Skills, and a `src/` tree ready for your tools. Infrastructure — transports, auth, storage, telemetry, lifecycle, linting — lives in `node_modules`. What's left is domain: which APIs to wrap, which workflows to expose.
|
|
45
53
|
|
|
46
|
-
Start your coding agent (Claude Code, Codex
|
|
54
|
+
Start your coding agent (i.e. Claude Code, Codex) and describe what you want. The agent knows what to do from there. The included Agent Skills cover the full cycle: `setup`, `design-mcp-server`, scaffolding, testing, `security-pass`, `release-and-publish`, `maintenance`, & more.
|
|
47
55
|
|
|
48
56
|
### What you get
|
|
49
57
|
|
|
@@ -58,7 +66,9 @@ export const search = tool('search', {
|
|
|
58
66
|
query: z.string().describe('Search query'),
|
|
59
67
|
limit: z.number().default(10).describe('Max results'),
|
|
60
68
|
}),
|
|
61
|
-
output: z.object({
|
|
69
|
+
output: z.object({
|
|
70
|
+
items: z.array(z.string()).describe('Search results'),
|
|
71
|
+
}),
|
|
62
72
|
async handler(input) {
|
|
63
73
|
const results = await doSearch(input.query, input.limit);
|
|
64
74
|
return { items: results };
|
|
@@ -73,7 +83,9 @@ import { resource, z } from '@cyanheads/mcp-ts-core';
|
|
|
73
83
|
|
|
74
84
|
export const itemData = resource('items://{itemId}', {
|
|
75
85
|
description: 'Retrieve item data by ID.',
|
|
76
|
-
params: z.object({
|
|
86
|
+
params: z.object({
|
|
87
|
+
itemId: z.string().describe('Item ID'),
|
|
88
|
+
}),
|
|
77
89
|
async handler(params, ctx) {
|
|
78
90
|
return await getItem(params.itemId);
|
|
79
91
|
},
|
|
@@ -96,32 +108,17 @@ It also works on Cloudflare Workers with `createWorkerHandler()` — same defini
|
|
|
96
108
|
|
|
97
109
|
## Features
|
|
98
110
|
|
|
99
|
-
- **Declarative definitions** — `tool()`, `resource()`, `prompt()` builders with Zod schemas
|
|
111
|
+
- **Declarative definitions** — `tool()`, `resource()`, `prompt()` builders with Zod schemas; `appTool()`/`appResource()` add interactive HTML UIs.
|
|
100
112
|
- **Unified Context** — one `ctx` for logging, tenant-scoped storage, elicitation, sampling, cancellation, and task progress.
|
|
101
|
-
- **
|
|
113
|
+
- **Auth** — `auth: ['scope']` on definitions, checked before dispatch (no wrapper code). Modes: `none`, `jwt`, or `oauth` (local secret or JWKS).
|
|
102
114
|
- **Task tools** — `task: true` for long-running ops; framework manages create/poll/progress/complete/cancel.
|
|
103
|
-
- **Definition linter** — validates names, schemas, auth scopes,
|
|
104
|
-
- **Typed error contracts** — declare `errors: [{ reason, code, when, retryable? }]`
|
|
105
|
-
- **Multi-backend storage** — `in-memory`, filesystem, Supabase, Cloudflare D1/KV/R2. Swap
|
|
115
|
+
- **Definition linter** — validates names, schemas, auth scopes, annotations, and format-parity at startup. Standalone via `lint:mcp` or devcheck.
|
|
116
|
+
- **Typed error contracts** — declare `errors: [{ reason, code, when, retryable? }]` and handlers get a typed `ctx.fail(reason, …)`. Contracts publish in `tools/list` so clients preview failure modes; the linter cross-checks the handler. Factories (`notFound()`, `httpErrorFromResponse()`, …) cover ad-hoc throws; plain `Error` auto-classifies.
|
|
117
|
+
- **Multi-backend storage** — `in-memory`, filesystem, Supabase, Cloudflare D1/KV/R2. Swap via env var; handlers don't change.
|
|
106
118
|
- **DataCanvas (optional)** — Tier 3 SQL/analytical workspace backed by DuckDB. Register tabular data from upstream APIs, run SQL across registered tables, export CSV/Parquet/JSON. Token-sharing model (opaque `canvas_id`) for multi-agent collaboration; sliding TTL + per-tenant scoping. Opt-in via `CANVAS_PROVIDER_TYPE=duckdb`; fails closed on Workers.
|
|
107
|
-
- **
|
|
108
|
-
- **
|
|
109
|
-
- **
|
|
110
|
-
- **Tiered dependencies** — parsers, OTEL SDK, Supabase, and OpenAI are optional peers. Install what you use.
|
|
111
|
-
- **Agent-first DX** — ships `CLAUDE.md` with the full exports catalog so AI agents ramp up without prompting.
|
|
112
|
-
|
|
113
|
-
### Storage Behavior Snapshot
|
|
114
|
-
|
|
115
|
-
Provider behavior is intentionally normalized at the interface, but backend limits still matter:
|
|
116
|
-
|
|
117
|
-
| Provider | Delete count accuracy | List TTL filtering | Notes |
|
|
118
|
-
|:---------|:----------------------|:-------------------|:------|
|
|
119
|
-
| `in-memory` | Exact | Exact | Volatile process memory |
|
|
120
|
-
| `filesystem` | Exact | Exact | Node/Bun only |
|
|
121
|
-
| `supabase` | Exact | Exact | Requires configured Supabase client |
|
|
122
|
-
| `cloudflare-d1` | Exact | Exact | Workers D1 binding |
|
|
123
|
-
| `cloudflare-kv` | Idempotent API success | Native/eventual | Delete cannot prove prior existence |
|
|
124
|
-
| `cloudflare-r2` | Idempotent API success | Not applied during list | Expired envelopes are removed on read |
|
|
119
|
+
- **Observability** — Pino logging + optional OpenTelemetry traces/metrics. Request correlation and tool metrics automatic.
|
|
120
|
+
- **Tiered dependencies** — parsers, OTEL SDK, Supabase, OpenAI as optional peers. Install what you use.
|
|
121
|
+
- **Agent-first DX** — ships `CLAUDE.md` / `AGENTS.md` with the codebase documented throughout Agent Skills.
|
|
125
122
|
|
|
126
123
|
## Server structure
|
|
127
124
|
|
|
@@ -181,7 +178,6 @@ See [CLAUDE.md](CLAUDE.md) for the full configuration reference.
|
|
|
181
178
|
| `prompt(name, options)` | Define a prompt with `generate(args)` |
|
|
182
179
|
| `appTool(name, options)` | Define an MCP Apps tool with auto-populated `_meta.ui` |
|
|
183
180
|
| `appResource(uriTemplate, options)` | Define an MCP Apps HTML resource with the correct MIME type and `_meta.ui` mirroring for read content |
|
|
184
|
-
| `disabledTool(def, meta)` | Mark a tool present-in-manifest but skipped at registration — clients can't invoke; landing page renders it muted with the operator-facing reason and optional hint. Compose with feature-flag conditionals at definition time. |
|
|
185
181
|
|
|
186
182
|
### Context
|
|
187
183
|
|
|
@@ -215,7 +211,7 @@ import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
|
|
|
215
211
|
import { fuzzTool, fuzzResource, fuzzPrompt } from '@cyanheads/mcp-ts-core/testing/fuzz';
|
|
216
212
|
```
|
|
217
213
|
|
|
218
|
-
See [CLAUDE.md](CLAUDE.md) for the complete exports reference.
|
|
214
|
+
See [CLAUDE.md/AGENTS.md](CLAUDE.md) for the complete exports reference.
|
|
219
215
|
|
|
220
216
|
## Examples
|
|
221
217
|
|
|
@@ -261,16 +257,18 @@ Also exports `fuzzResource`, `fuzzPrompt`, `zodToArbitrary`, and `ADVERSARIAL_ST
|
|
|
261
257
|
|
|
262
258
|
## Documentation
|
|
263
259
|
|
|
264
|
-
- **[CLAUDE.md](CLAUDE.md)** — Framework reference: exports catalog, patterns, Context interface, error codes, auth, config, testing. Ships in the npm package.
|
|
265
|
-
- **[
|
|
260
|
+
- **[CLAUDE.md/AGENTS.md](CLAUDE.md)** — Framework reference: exports catalog, patterns, Context interface, error codes, auth, config, testing. Ships in the npm package.
|
|
261
|
+
- **[docs/telemetry/observability.md](docs/telemetry/observability.md)** — OpenTelemetry catalog: every span, metric, and attribute the framework emits, plus the env vars to wire export.
|
|
262
|
+
- **[docs/telemetry/dashboards.md](docs/telemetry/dashboards.md)** — Example Grafana dashboard JSON and vendor-agnostic query recipes (Datadog, New Relic, Honeycomb).
|
|
263
|
+
- **[CHANGELOG.md](CHANGELOG.md)** — Version history - Directory based for easier parsing by agents. Each entry includes a summary, migration notes, and links to commits/issues.
|
|
266
264
|
|
|
267
265
|
## Development
|
|
268
266
|
|
|
269
267
|
```bash
|
|
270
268
|
bun run rebuild # clean + build (scripts/clean.ts + scripts/build.ts)
|
|
271
|
-
bun run devcheck # lint
|
|
269
|
+
bun run devcheck # full gate: lint/format, typecheck, MCP defs, framework antipatterns, docs/skills/changelog sync, tests, audit, outdated, secrets/TODO scan
|
|
272
270
|
bun run lint:mcp # validate MCP definitions against spec
|
|
273
|
-
bun run test:all # vitest
|
|
271
|
+
bun run test:all # vitest: unit + Workers pool + integration
|
|
274
272
|
```
|
|
275
273
|
|
|
276
274
|
## Contributing
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Fix `ctx.auth.token` strip in `toAuthContext` ([#121](https://github.com/cyanheads/mcp-ts-core/issues/121)) — typed `token?: string` on `AuthContext`, forwarded by `withAuthInfo` and the ALS bridge so handlers can relay the bearer upstream."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 0.8.18 — 2026-05-06
|
|
7
|
+
|
|
8
|
+
`toAuthContext` mapped `AuthInfo` → public `AuthContext` but silently dropped `info.token`, so `ctx.auth.token` was always `undefined` even though `claimParser.buildAuthInfoFromClaims()` set it from the verified JWT and the jwt/oauth strategies populated it. Tool handlers needing PAT pass-through / on-behalf-of forwarding had no public API path to the validated bearer (`authContext` ALS isn't re-exported), and the `[key: string]: unknown` index signature on `AuthContext` made `ctx.auth.token` compile cleanly — hiding the bug from typecheck and `createMockContext({ auth: { token } })` unit tests.
|
|
9
|
+
|
|
10
|
+
## Fixed
|
|
11
|
+
|
|
12
|
+
- **`ctx.auth.token` reaches handlers** ([#121](https://github.com/cyanheads/mcp-ts-core/issues/121)) — `toAuthContext` in `src/utils/internal/requestContext.ts` now spreads `info.token` when present. Covers both call sites: `withAuthInfo` (initial request context, including auto-task `callerAuth` capture) and the ALS bridge in `createRequestContext` (handler-created child contexts). Logger pino-redact paths (`token`, `*.token`, `*.*.token`) already mask the field, so naive `ctx.log.info({ auth: ctx.auth })` doesn't leak the bearer.
|
|
13
|
+
|
|
14
|
+
## Changed
|
|
15
|
+
|
|
16
|
+
- **`AuthContext.token`** — typed `token?: string` on the public interface. Consumers no longer need the `(ctx.auth as { token?: string }).token` cast.
|
|
17
|
+
- **Dependency bumps:** `hono` `^4.12.17` → `^4.12.18` (security release: GHSA-p77w-8qqv-26rm cache leakage, GHSA-qp7p-654g-cw7p `hono/jsx` CSS injection, GHSA-hm8q-7f3q-5f36 JWT `NumericDate`; framework doesn't use the affected surfaces, but the bumped peer floor protects downstream consumers); `chrono-node` `^2.9.0` → `^2.9.1`; `@opentelemetry/sdk-node` / `exporter-trace-otlp-http` / `exporter-metrics-otlp-http` / `instrumentation-http` `^0.216.0` → `^0.217.0`; `@opentelemetry/instrumentation-pino` `^0.62.0` → `^0.63.0`.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Telemetry visualization docs ([#125](https://github.com/cyanheads/mcp-ts-core/issues/125)) — example Grafana dashboard JSON, vendor-agnostic query recipes, new `api-telemetry` skill. Engines bumped to Bun ≥1.3.0 / Node ≥24.0.0."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 0.8.19 — 2026-05-08
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`docs/telemetry/dashboards.md` + `mcp-ts-core-dashboard.json`** ([#125](https://github.com/cyanheads/mcp-ts-core/issues/125)) — example Grafana dashboard (54 panels, schemaVersion 39) plus an import quickstart and equivalent query recipes for Datadog / NRQL / Honeycomb. Single `$service` template variable, `(?:@[^/]+/)?` regex strips any npm-org prefix, no publisher-specific names. `docs/telemetry/observability.md` cross-links it. Framework-source-only — `docs/` is intentionally not shipped in the npm `dist`.
|
|
12
|
+
- **`api-telemetry` skill** (v1.0) — catalog of every span, metric, attribute, completion-log field, env var, and runtime caveat the framework emits. Cross-linked from `api-utils` (which now scopes to the helper API only) and from CLAUDE.md/AGENTS.md skill index.
|
|
13
|
+
- **Changelog frontmatter `security: boolean`** — pairs with the `## Security` section to render a `🛡️ Security` badge in the rollup. Both flags render in fixed order (`· ⚠️ Breaking · 🛡️ Security`) when set. `scripts/build-changelog.ts` parses and validates `security` like `breaking` (must be literal `true`/`false`).
|
|
14
|
+
- **`init` template substitutions** — `{{MCP_SDK_VERSION}}` and `{{ZOD_VERSION}}` now resolve from the framework `package.json` `dependencies` map. Joins existing `{{PACKAGE_NAME}}` and `{{FRAMEWORK_VERSION}}`.
|
|
15
|
+
|
|
16
|
+
## Changed
|
|
17
|
+
|
|
18
|
+
- **Engines bumped:** Bun `>=1.2.0` → `>=1.3.0`, Node `>=22.0.0` → `>=24.0.0`. Mirrored in `templates/package.json` and `skills/polish-docs-meta/references/package-meta.md`.
|
|
19
|
+
- **Docker base image:** `oven/bun:1` → `oven/bun:1.3` (build + runtime stages, root and template Dockerfiles).
|
|
20
|
+
- **CLAUDE.md / AGENTS.md** — restructured: new "Consumers" section explains the package-import vs init-scaffolded paths up front; tool/format guidance condensed; standalone Git section dropped (the global protocol covers it). Now reflects 0.8.19 + Bun ≥1.3.0 / Node ≥24.0.0 in the header.
|
|
21
|
+
- **`changelog/template.md` / `templates/changelog/template.md`** — rewritten authoring guide: bold-the-symbol bullet style, Keep-a-Changelog section order, scaffolded `Deprecated` / `Removed` / `Security` sections (commented out by default).
|
|
22
|
+
- **`README.md`** — leaner feature list, reordered storage detail behind a single `node_modules` link, added cross-links to `docs/telemetry/observability.md` and `dashboards.md`.
|
|
23
|
+
- **Skill bumps:** `setup` 1.6 → 1.7 (rebrands `npx` examples to `bunx`, replaces `{{PACKAGE_NAME}}` placeholder guidance with substituted-name verification, adds `release-and-publish` to the rough progression). `maintenance` 2.0 → 2.1 — Phase C now also resyncs pristine reference files (`templates/changelog/template.md` → consumer `changelog/template.md`) on content-hash mismatch. `report-issue-framework` 1.5 → 1.6, `report-issue-local` 1.4 → 1.5 — terser issue-writing guidance, "cite cross-references once," Bun `1.3.x` examples. `api-utils` 2.1 → 2.2 — telemetry section header points readers to the new `api-telemetry` skill for the catalog.
|
|
24
|
+
- **Keywords:** added `bun`, `mcp-framework`; removed `edge`.
|
|
25
|
+
- **Dependency refresh:**
|
|
26
|
+
- `@hono/node-server` `^2.0.1` → `^2.0.2`
|
|
27
|
+
- `@cloudflare/vitest-pool-workers` `^0.16.0` → `^0.16.3`
|
|
28
|
+
- `@cloudflare/workers-types` `^4.20260506.1` → `^4.20260508.1`
|
|
29
|
+
- `@hono/otel` `^1.1.1` → `^1.1.2`
|
|
30
|
+
- `@types/node` `^25.6.0` → `^25.6.2`
|
|
31
|
+
- `openai` `^6.36.0` → `^6.37.0`
|
|
32
|
+
- `vite` `8.0.10` → `8.0.11`
|
|
33
|
+
- `fast-xml-parser` `^5.7.3` (new dev dep)
|
package/changelog/template.md
CHANGED
|
@@ -1,56 +1,71 @@
|
|
|
1
1
|
---
|
|
2
|
-
# FORMAT REFERENCE —
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# Keep the double quotes around the value — unquoted YAML treats `:` (colon-space)
|
|
12
|
-
# inside the string as a key separator, which fails GitHub's strict YAML parser.
|
|
2
|
+
# FORMAT REFERENCE — do not edit. Copy this file to
|
|
3
|
+
# `changelog/<major.minor>.x/<version>.md` (e.g. `changelog/0.8.x/0.8.6.md`)
|
|
4
|
+
# to author a new release. Set that file's H1 to `# <version> — YYYY-MM-DD`
|
|
5
|
+
# with a concrete date.
|
|
6
|
+
|
|
7
|
+
# Required. One-line GitHub Release-style headline. ~250 character soft cap.
|
|
8
|
+
# Default short and scannable. Don't pad, don't stitch unrelated changes with
|
|
9
|
+
# semicolons — pick the headline. Quotes required: unquoted YAML treats `: `
|
|
10
|
+
# inside the value as a key separator and fails GitHub's strict parser.
|
|
13
11
|
summary: ""
|
|
14
12
|
|
|
15
|
-
# Set
|
|
16
|
-
# changes, config renames,
|
|
17
|
-
# Flagged as
|
|
13
|
+
# Set `true` when consumers must change code to upgrade: API removals,
|
|
14
|
+
# signature changes, config renames, behavior changes that break existing
|
|
15
|
+
# usage. Flagged as `Breaking` in the rollup.
|
|
18
16
|
breaking: false
|
|
17
|
+
|
|
18
|
+
# Set `true` if this release contains any security fix. Pairs with the
|
|
19
|
+
# `## Security` section below. Flagged as `Security` in the rollup so
|
|
20
|
+
# users can triage upgrade urgency at a glance.
|
|
21
|
+
security: false
|
|
19
22
|
---
|
|
20
23
|
|
|
21
24
|
# <version> — YYYY-MM-DD
|
|
22
25
|
|
|
23
26
|
<!--
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
and
|
|
27
|
+
AUTHORING GUIDE — applies to the new per-version file you create from this
|
|
28
|
+
template.
|
|
29
|
+
|
|
30
|
+
Audience: someone scanning release notes to decide what affects them. Lead
|
|
31
|
+
each bullet with the symbol or concept name in **bold** so they can skip
|
|
32
|
+
what's irrelevant and zoom in on what's not.
|
|
33
|
+
|
|
34
|
+
Tone: terse, fact-dense, not verbose. Default to one sentence per bullet —
|
|
35
|
+
name the symbol, state what changed, stop. Use a second sentence only when
|
|
36
|
+
it carries weight. If a bullet feels long, it is.
|
|
37
|
+
|
|
38
|
+
Cut: mechanism walkthroughs (those belong in JSDoc, AGENTS.md, or the
|
|
39
|
+
relevant skill), ceremonial framings ("This release introduces…",
|
|
40
|
+
backwards-compat paragraphs), file-by-file test enumerations, internal
|
|
41
|
+
implementation notes. Prefer code/symbol names over English re-explanations.
|
|
42
|
+
|
|
43
|
+
Narrative intro: skip by default. Add one short sentence only when the
|
|
44
|
+
release theme genuinely needs framing the bullets can't carry.
|
|
45
|
+
|
|
46
|
+
Sections: Keep a Changelog order — Added, Changed, Deprecated, Removed,
|
|
47
|
+
Fixed, Security. Include only sections with entries; delete the rest
|
|
48
|
+
(including the commented-out scaffolding below). Don't ship empty headers.
|
|
49
|
+
|
|
50
|
+
Include: every distinct fact a reader needs to adopt or audit the release —
|
|
51
|
+
new exports, signatures, lint rule IDs, env vars, breaking changes, version
|
|
52
|
+
bumps on shipped skills. Nothing more.
|
|
53
|
+
|
|
54
|
+
Links: link issues, PRs, docs, or skills where they help a reader jump to
|
|
55
|
+
context. Once per item per entry — don't re-link the same issue in summary,
|
|
56
|
+
narrative, and bullet. Skip links for inline symbol names; code spans speak
|
|
57
|
+
for themselves.
|
|
58
|
+
|
|
59
|
+
Issue/PR URLs: use full URLs. GitHub's bare `#NN` auto-link only resolves
|
|
60
|
+
inside its own UI, not in npm reads or local editors.
|
|
61
|
+
|
|
62
|
+
[#38](https://github.com/cyanheads/mcp-ts-core/issues/38) ← issue
|
|
63
|
+
[#42](https://github.com/cyanheads/mcp-ts-core/pull/42) ← PR
|
|
64
|
+
|
|
65
|
+
Verify numbers exist before linking (`gh issue view NN`, `gh pr view NN`).
|
|
66
|
+
Never speculate on a future number — `#42` for an upcoming PR silently
|
|
67
|
+
resolves to whatever real item already owns 42, and timeline previews pull
|
|
68
|
+
in that unrelated item's metadata.
|
|
54
69
|
-->
|
|
55
70
|
|
|
56
71
|
## Added
|
|
@@ -61,6 +76,18 @@ breaking: false
|
|
|
61
76
|
|
|
62
77
|
-
|
|
63
78
|
|
|
79
|
+
<!-- ## Deprecated
|
|
80
|
+
|
|
81
|
+
- -->
|
|
82
|
+
|
|
83
|
+
<!-- ## Removed
|
|
84
|
+
|
|
85
|
+
- -->
|
|
86
|
+
|
|
64
87
|
## Fixed
|
|
65
88
|
|
|
66
89
|
-
|
|
90
|
+
|
|
91
|
+
<!-- ## Security
|
|
92
|
+
|
|
93
|
+
- -->
|
package/dist/cli/init.js
CHANGED
|
@@ -66,10 +66,16 @@ function init() {
|
|
|
66
66
|
mkdirSync(dest, { recursive: true });
|
|
67
67
|
}
|
|
68
68
|
console.log(`\n Scaffolding${name ? ` ${name}` : ''} in ${dest}\n`);
|
|
69
|
+
const substitutions = {
|
|
70
|
+
PACKAGE_NAME: packageName,
|
|
71
|
+
FRAMEWORK_VERSION: PACKAGE_JSON.version,
|
|
72
|
+
MCP_SDK_VERSION: PACKAGE_JSON.dependencies?.['@modelcontextprotocol/sdk'] ?? '',
|
|
73
|
+
ZOD_VERSION: PACKAGE_JSON.dependencies?.zod ?? '',
|
|
74
|
+
};
|
|
69
75
|
const created = [];
|
|
70
76
|
const skipped = [];
|
|
71
77
|
// Step 1: Copy templates
|
|
72
|
-
copyTemplates(dest,
|
|
78
|
+
copyTemplates(dest, substitutions, created, skipped);
|
|
73
79
|
// Step 2: Copy scripts
|
|
74
80
|
copyScripts(dest, created, skipped);
|
|
75
81
|
// Step 3: Copy external skills
|
|
@@ -78,7 +84,7 @@ function init() {
|
|
|
78
84
|
printSummary(created, skipped, name);
|
|
79
85
|
}
|
|
80
86
|
// ── Template copying ──────────────────────────────────────────────────
|
|
81
|
-
function copyTemplates(dest,
|
|
87
|
+
function copyTemplates(dest, substitutions, created, skipped) {
|
|
82
88
|
const entries = walkDir(TEMPLATES_DIR);
|
|
83
89
|
for (const srcPath of entries) {
|
|
84
90
|
let relPath = relative(TEMPLATES_DIR, srcPath);
|
|
@@ -94,9 +100,10 @@ function copyTemplates(dest, name, frameworkVersion, created, skipped) {
|
|
|
94
100
|
continue;
|
|
95
101
|
}
|
|
96
102
|
mkdirSync(dirname(destPath), { recursive: true });
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.
|
|
103
|
+
let content = readFileSync(srcPath, 'utf-8');
|
|
104
|
+
for (const [key, value] of Object.entries(substitutions)) {
|
|
105
|
+
content = content.replaceAll(`{{${key}}}`, value);
|
|
106
|
+
}
|
|
100
107
|
writeFileSync(destPath, content);
|
|
101
108
|
created.push(relPath);
|
|
102
109
|
}
|