@cyanheads/mcp-ts-core 0.13.0 → 0.13.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +14 -4
- package/CLAUDE.md +14 -4
- package/README.md +2 -2
- package/changelog/0.12.x/0.12.2.md +1 -1
- package/changelog/0.13.x/0.13.1.md +56 -0
- package/changelog/0.13.x/0.13.2.md +43 -0
- package/changelog/0.8.x/0.8.11.md +2 -2
- package/{tsconfig.base.json → config/tsconfig.base.json} +2 -2
- package/dist/config/index.d.ts +8 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +8 -0
- package/dist/config/index.js.map +1 -1
- package/dist/core/app.d.ts +82 -2
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +129 -6
- package/dist/core/app.js.map +1 -1
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/worker.d.ts +6 -1
- package/dist/core/worker.d.ts.map +1 -1
- package/dist/core/worker.js.map +1 -1
- package/dist/linter/rules/resource-rules.js +9 -2
- package/dist/linter/rules/resource-rules.js.map +1 -1
- package/dist/linter/rules/tool-rules.js +4 -1
- package/dist/linter/rules/tool-rules.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +15 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +21 -5
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/http/serverCard.d.ts +23 -0
- package/dist/mcp-server/transports/http/serverCard.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/serverCard.js +7 -0
- package/dist/mcp-server/transports/http/serverCard.js.map +1 -1
- package/dist/mcp-server/types.d.ts +10 -3
- package/dist/mcp-server/types.d.ts.map +1 -1
- package/dist/mcp-server/types.js +4 -3
- package/dist/mcp-server/types.js.map +1 -1
- package/dist/services/canvas/core/sqlGate.d.ts.map +1 -1
- package/dist/services/canvas/core/sqlGate.js +27 -2
- package/dist/services/canvas/core/sqlGate.js.map +1 -1
- package/dist/testing/index.d.ts +5 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +7 -2
- package/dist/testing/index.js.map +1 -1
- package/dist/utils/pagination/pagination.d.ts.map +1 -1
- package/dist/utils/pagination/pagination.js +4 -1
- package/dist/utils/pagination/pagination.js.map +1 -1
- package/dist/utils/parsing/frontmatterParser.d.ts +8 -7
- package/dist/utils/parsing/frontmatterParser.d.ts.map +1 -1
- package/dist/utils/parsing/frontmatterParser.js +91 -16
- package/dist/utils/parsing/frontmatterParser.js.map +1 -1
- package/framework-skills/add-tool/SKILL.md +4 -4
- package/framework-skills/api-config/SKILL.md +19 -3
- package/framework-skills/api-context/SKILL.md +3 -1
- package/framework-skills/api-telemetry/SKILL.md +13 -10
- package/framework-skills/api-testing/SKILL.md +3 -1
- package/framework-skills/code-simplifier/SKILL.md +12 -6
- package/framework-skills/design-mcp-server/SKILL.md +2 -2
- package/framework-skills/git-wrapup/SKILL.md +3 -3
- package/framework-skills/maintenance/SKILL.md +2 -2
- package/framework-skills/polish-docs-meta/SKILL.md +4 -3
- package/framework-skills/polish-docs-meta/references/readme.md +12 -8
- package/framework-skills/release-and-publish/SKILL.md +12 -3
- package/framework-skills/report-issue-framework/SKILL.md +2 -1
- package/framework-skills/report-issue-local/SKILL.md +7 -1
- package/package.json +7 -7
- package/scripts/build.ts +28 -6
- package/scripts/clean-mcpb.ts +8 -2
- package/scripts/clean.ts +40 -4
- package/scripts/devcheck.ts +46 -15
- package/scripts/lint-packaging.ts +79 -4
- package/templates/.env.example +5 -2
- package/templates/AGENTS.md +16 -0
- package/templates/CLAUDE.md +16 -0
- package/templates/src/index.ts +10 -0
|
@@ -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.17"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -111,12 +111,15 @@ If `--ff-only` refuses, `main` moved underneath the release branch. Halt and rep
|
|
|
111
111
|
The tag goes on HEAD. In release PR mode that is `main`'s tip after step 3 — the commit the PR's `headRefOid` names — so the tag is created on the branch it stays reachable from.
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
|
-
|
|
114
|
+
cat > /tmp/tag-v<version>.md <<'TAG'
|
|
115
|
+
<tag message>
|
|
116
|
+
TAG
|
|
117
|
+
git tag -a v<version> --cleanup=whitespace -F /tmp/tag-v<version>.md
|
|
115
118
|
```
|
|
116
119
|
|
|
117
120
|
If `v<version>` already exists and points at HEAD, a prior run created it — proceed. If it exists and points anywhere else, **halt and report the conflict** with the version string, the existing tag SHA, and HEAD. Never delete or move a tag without explicit authorization.
|
|
118
121
|
|
|
119
|
-
|
|
122
|
+
Write the message to a file through a quoted-delimiter heredoc and pass it with `-F`, never inline with `-m`: the body carries backticks, which a double-quoted string runs as command substitution and silently deletes, and apostrophes, which end a single-quoted string. The tag message renders as the GitHub Release body via `--notes-from-tag`. It must be structured markdown, not a flat string.
|
|
120
123
|
|
|
121
124
|
**Release PR mode: the tag body is the PR body's `## Changes` bullets plus its final changelog link, verbatim** — `gh pr view <N> --json body -q .body` (`<N>` from step 1 — on `main` there is no branch for `gh` to infer it from), take the theme line as the subject, the bullets under `## Changes`, and the last line; drop `## Gates` and the headers. That digest was authored at wrapup and reviewed on the PR; re-authoring it here would publish unreviewed words. The one addition: append ` · release PR #<N>` to that final line, so the GitHub Release points at its audit trail (GitHub autolinks the bare `#<N>`). Without a PR, author it from the changelog entry at `changelog/<major.minor>.x/<version>.md` — every claim in the tag must appear in that file, and the file's `summary:` line is the tag's theme.
|
|
122
125
|
|
|
@@ -193,10 +196,16 @@ Halt on publish error other than "version already exists" (which means this step
|
|
|
193
196
|
|
|
194
197
|
Only if `server.json` exists at the repo root (otherwise skip). Note: `server.json` (MCP Registry metadata) and `manifest.json` (MCPB bundle manifest, step 8) are independent — a project may have either, both, or neither.
|
|
195
198
|
|
|
199
|
+
The registry checks that the npm version exists before it registers, and npm's read endpoint can lag `bun publish` by several minutes. Wait for the version to be served before publishing; a publisher error saying the npm version was not found is this lag, not a terminal failure:
|
|
200
|
+
|
|
196
201
|
```bash
|
|
202
|
+
curl -sf --retry 30 --retry-delay 30 --retry-all-errors -o /dev/null \
|
|
203
|
+
"https://registry.npmjs.org/<package-name>/<version>"
|
|
197
204
|
bun run publish-mcp
|
|
198
205
|
```
|
|
199
206
|
|
|
207
|
+
Step 8 depends only on the pushed tag, so it may run while this wait is in progress.
|
|
208
|
+
|
|
200
209
|
If `publish-mcp` isn't defined in `package.json`, add it permanently (one-time setup, macOS):
|
|
201
210
|
|
|
202
211
|
```json
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
File a bug or feature request against @cyanheads/mcp-ts-core when you hit a framework issue. Use when a builder, utility, context method, or config behaves contrary to the documented API — not for server-specific application bugs.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.11"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -181,6 +181,7 @@ Every issue needs exactly one primary label. Stack secondary labels on top when
|
|
|
181
181
|
| `performance` | Memory, CPU, latency, or resource usage |
|
|
182
182
|
| `security` | Vulnerability, CVE, or hardening work |
|
|
183
183
|
| `breaking-change` | Fix/feature will break public API; requires a major bump |
|
|
184
|
+
| `blocked-by-sdk` | Fix requires changes in `@modelcontextprotocol/sdk` |
|
|
184
185
|
| `surplus-token-idea` | Worth exploring when token budget allows |
|
|
185
186
|
|
|
186
187
|
Combine labels: `--label "bug" --label "regression"`.
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
File a bug or feature request against this MCP server's own repo. Use for server-specific issues — tool logic, service integrations, config problems, or domain bugs that aren't caused by the framework.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.9"
|
|
8
8
|
audience: external
|
|
9
9
|
type: workflow
|
|
10
10
|
---
|
|
@@ -165,8 +165,12 @@ Every issue needs exactly one primary label. Stack secondary labels on top when
|
|
|
165
165
|
| `performance` | Memory, CPU, latency, or resource usage |
|
|
166
166
|
| `security` | Vulnerability, CVE, or hardening work |
|
|
167
167
|
| `breaking-change` | Change will break public API; requires a major bump |
|
|
168
|
+
| `blocked-by-framework` | Fix requires a released change in `@cyanheads/mcp-ts-core`; pairs with a `Depends on: cyanheads/mcp-ts-core#N` line in the body |
|
|
169
|
+
| `blocked-by-sdk` | Fix requires changes in `@modelcontextprotocol/sdk` |
|
|
168
170
|
| `surplus-token-idea` | Worth exploring when token budget allows |
|
|
169
171
|
|
|
172
|
+
`blocked-by-framework` comes off when this server adopts the release that ships the fix. An issue blocked on the SDK *through* the framework takes `blocked-by-framework`, not `blocked-by-sdk` — the server's own unblock is still a framework release.
|
|
173
|
+
|
|
170
174
|
Combine labels: `--label "bug" --label "regression"`.
|
|
171
175
|
|
|
172
176
|
Secondary labels are not GitHub defaults — if `gh issue create --label "regression"` fails with `label not found`, create it once:
|
|
@@ -176,6 +180,8 @@ gh label create regression --color e99695 --description "Worked before, broken a
|
|
|
176
180
|
gh label create performance --color 5319e7 --description "Memory, CPU, latency, or resource usage"
|
|
177
181
|
gh label create security --color b60205 --description "Vulnerability, CVE, or hardening work"
|
|
178
182
|
gh label create breaking-change --color d93f0b --description "Change will break public API; requires a major bump"
|
|
183
|
+
gh label create blocked-by-framework --color fbca04 --description "Fix requires a released change in @cyanheads/mcp-ts-core"
|
|
184
|
+
gh label create blocked-by-sdk --color c5def5 --description "Fix requires changes in @modelcontextprotocol/sdk"
|
|
179
185
|
gh label create surplus-token-idea --color FF10F0 --description "Worth exploring when token budget allows"
|
|
180
186
|
```
|
|
181
187
|
|
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.2",
|
|
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": [
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"templates/",
|
|
26
26
|
"AGENTS.md",
|
|
27
27
|
"CLAUDE.md",
|
|
28
|
-
"tsconfig.base.json",
|
|
28
|
+
"config/tsconfig.base.json",
|
|
29
29
|
"vitest.config.base.mjs",
|
|
30
30
|
"biome.json"
|
|
31
31
|
],
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
"import": "./dist/testing/vitest.js",
|
|
124
124
|
"default": "./dist/testing/vitest.js"
|
|
125
125
|
},
|
|
126
|
-
"./tsconfig.base.json": "./tsconfig.base.json",
|
|
126
|
+
"./tsconfig.base.json": "./config/tsconfig.base.json",
|
|
127
127
|
"./vitest.config": "./vitest.config.base.mjs",
|
|
128
128
|
"./biome": "./biome.json",
|
|
129
129
|
"./package.json": "./package.json"
|
|
@@ -163,8 +163,8 @@
|
|
|
163
163
|
"format": "biome check --write .",
|
|
164
164
|
"format:unsafe": "biome check --write --unsafe .",
|
|
165
165
|
"typecheck": "bunx tsc --noEmit",
|
|
166
|
-
"typecheck:scripts": "bunx tsc --project tsconfig.scripts.json --noEmit",
|
|
167
|
-
"typecheck:worker": "bunx tsc --project tsconfig.worker.json --noEmit",
|
|
166
|
+
"typecheck:scripts": "bunx tsc --project config/tsconfig.scripts.json --noEmit",
|
|
167
|
+
"typecheck:worker": "bunx tsc --project config/tsconfig.worker.json --noEmit",
|
|
168
168
|
"list-skills": "bun run scripts/list-skills.ts",
|
|
169
169
|
"tree": "bun run scripts/tree.ts",
|
|
170
170
|
"fetch-spec": "bun run scripts/fetch-openapi-spec.ts",
|
|
@@ -234,7 +234,7 @@
|
|
|
234
234
|
"js-yaml": "^5.4.1",
|
|
235
235
|
"linkedom": "^0.18.13",
|
|
236
236
|
"node-cron": "^4.6.0",
|
|
237
|
-
"openai": "^7.
|
|
237
|
+
"openai": "^7.15.0",
|
|
238
238
|
"papaparse": "^5.7.0",
|
|
239
239
|
"partial-json": "^0.1.7",
|
|
240
240
|
"pdf-lib": "^1.17.1",
|
|
@@ -309,7 +309,7 @@
|
|
|
309
309
|
"hono": "^4.13.7",
|
|
310
310
|
"jose": "^6.2.12",
|
|
311
311
|
"pino": "^10.3.1",
|
|
312
|
-
"zod": "^4.6.
|
|
312
|
+
"zod": "^4.6.2"
|
|
313
313
|
},
|
|
314
314
|
"peerDependencies": {
|
|
315
315
|
"@duckdb/node-api": "^1.5.5-r.1",
|
package/scripts/build.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { execFile } from 'node:child_process';
|
|
17
|
-
import { readFileSync } from 'node:fs';
|
|
17
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
18
18
|
import { readdir, stat } from 'node:fs/promises';
|
|
19
19
|
import { dirname, join } from 'node:path';
|
|
20
20
|
import { fileURLToPath } from 'node:url';
|
|
@@ -22,6 +22,32 @@ import { fileURLToPath } from 'node:url';
|
|
|
22
22
|
const ROOT_DIR = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
23
23
|
const DIST_DIR = join(ROOT_DIR, 'dist');
|
|
24
24
|
|
|
25
|
+
/** Where `bunx @cyanheads/mcp-ts-core init` writes the build tsconfig. */
|
|
26
|
+
const SCAFFOLD_BUILD_PROJECT = 'tsconfig.build.json';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Build tsconfig locations, in precedence order. A project may keep its project
|
|
30
|
+
* tsconfigs in `config/` or at the root, and this script ships to both
|
|
31
|
+
* verbatim, so the default is probed rather than hardcoded.
|
|
32
|
+
*/
|
|
33
|
+
const BUILD_PROJECT_CANDIDATES = ['config/tsconfig.build.json', SCAFFOLD_BUILD_PROJECT];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The tsconfig to build: an explicit `--project <path>` verbatim — a missing
|
|
37
|
+
* one still reaches the compiler and fails there — otherwise the first
|
|
38
|
+
* candidate location present. With neither present the scaffold's layout is
|
|
39
|
+
* named, so the compiler error points at the file the project should have.
|
|
40
|
+
*/
|
|
41
|
+
function resolveProject(argv: string[]): string {
|
|
42
|
+
const flagIndex = argv.indexOf('--project');
|
|
43
|
+
const explicit = flagIndex === -1 ? undefined : argv[flagIndex + 1];
|
|
44
|
+
if (explicit !== undefined) return explicit;
|
|
45
|
+
return (
|
|
46
|
+
BUILD_PROJECT_CANDIDATES.find((candidate) => existsSync(join(ROOT_DIR, candidate))) ??
|
|
47
|
+
SCAFFOLD_BUILD_PROJECT
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
25
51
|
async function exec(
|
|
26
52
|
cmd: string[],
|
|
27
53
|
label: string,
|
|
@@ -92,11 +118,7 @@ function formatBytes(bytes: number): string {
|
|
|
92
118
|
async function main() {
|
|
93
119
|
// Read package info
|
|
94
120
|
const pkg = JSON.parse(readFileSync(join(ROOT_DIR, 'package.json'), 'utf-8'));
|
|
95
|
-
const
|
|
96
|
-
const project =
|
|
97
|
-
projectIdx !== -1
|
|
98
|
-
? (process.argv[projectIdx + 1] ?? 'tsconfig.build.json')
|
|
99
|
-
: 'tsconfig.build.json';
|
|
121
|
+
const project = resolveProject(process.argv);
|
|
100
122
|
|
|
101
123
|
console.log(`\x1b[1mBuilding ${pkg.name}@${pkg.version}\x1b[0m`);
|
|
102
124
|
console.log(`\x1b[2m tsconfig: ${project}\x1b[0m`);
|
package/scripts/clean-mcpb.ts
CHANGED
|
@@ -33,7 +33,10 @@ import { fileURLToPath } from 'node:url';
|
|
|
33
33
|
/**
|
|
34
34
|
* Agent-doc entries under `node_modules/` that must not ship in a bundle.
|
|
35
35
|
* KEEP IN SYNC with `AGENT_DOC_ENTRY` in `scripts/lint-packaging.ts`
|
|
36
|
-
* (post-bundle content check) —
|
|
36
|
+
* (post-bundle content check) — edit both literals together. The assertion that
|
|
37
|
+
* they match lives in the mcp-ts-core repository's own test suite; `tests/` is
|
|
38
|
+
* not part of the published package, so nothing enforces the pair in a server
|
|
39
|
+
* these scripts were copied into.
|
|
37
40
|
*/
|
|
38
41
|
export const AGENT_DOC_ENTRY =
|
|
39
42
|
/^node_modules\/.*(?:\/framework-skills\/|\/skills\/|\/\.claude\/|\/\.agents\/|\/SKILL\.md$)/;
|
|
@@ -41,7 +44,10 @@ export const AGENT_DOC_ENTRY =
|
|
|
41
44
|
/**
|
|
42
45
|
* Platform-specific native binding packages, which must not ship in a bundle.
|
|
43
46
|
* KEEP IN SYNC with `NATIVE_BINDING_ENTRY` in `scripts/lint-packaging.ts`
|
|
44
|
-
* (post-bundle content check) —
|
|
47
|
+
* (post-bundle content check) — edit both literals together. The assertion that
|
|
48
|
+
* they match lives in the mcp-ts-core repository's own test suite; `tests/` is
|
|
49
|
+
* not part of the published package, so nothing enforces the pair in a server
|
|
50
|
+
* these scripts were copied into.
|
|
45
51
|
*
|
|
46
52
|
* `mcpb pack` archives the whole project directory, so a native dependency
|
|
47
53
|
* contributes the build host's platform slice and nothing else — for
|
package/scripts/clean.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Utility script to clean build artifacts and temporary directories.
|
|
3
3
|
* @module scripts/clean
|
|
4
|
-
* By default, it removes the 'dist' and 'logs' directories
|
|
5
|
-
*
|
|
4
|
+
* By default, it removes the 'dist' and 'logs' directories plus every
|
|
5
|
+
* TypeScript build-info file the project's tsconfigs write — in the root and
|
|
6
|
+
* in 'config/', where a tsconfig kept there resolves its relative
|
|
7
|
+
* `tsBuildInfoFile` against itself.
|
|
8
|
+
* Custom directories can be specified as command-line arguments, which
|
|
9
|
+
* replace the default set entirely.
|
|
6
10
|
* Works on all platforms using Node.js path normalization.
|
|
7
11
|
*
|
|
8
12
|
* @example
|
|
9
|
-
* // Default
|
|
13
|
+
* // Default targets (dist, logs, build info):
|
|
10
14
|
* // bun run scripts/clean.ts
|
|
11
15
|
*
|
|
12
16
|
* // Custom directories:
|
|
@@ -21,6 +25,38 @@ interface CleanResult {
|
|
|
21
25
|
status: 'cleaned' | 'skipped' | 'error';
|
|
22
26
|
}
|
|
23
27
|
|
|
28
|
+
/**
|
|
29
|
+
* A TypeScript build-info file: the plain `.tsbuildinfo`, a `<name>.tsbuildinfo`,
|
|
30
|
+
* and the lane-suffixed `.tsbuildinfo.<lane>` forms a multi-tsconfig project
|
|
31
|
+
* writes (`tsBuildInfoFile: ".tsbuildinfo.worker"`).
|
|
32
|
+
*/
|
|
33
|
+
const BUILD_INFO_FILE = /\.tsbuildinfo(\.[^.]*)?$/;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Directories a tsconfig may write its build info into. A `tsBuildInfoFile` is
|
|
37
|
+
* resolved against the tsconfig that declares it, so a project keeping its
|
|
38
|
+
* tsconfigs in `config/` leaves build info there rather than at the root.
|
|
39
|
+
*/
|
|
40
|
+
const BUILD_INFO_DIRS = ['.', 'config'];
|
|
41
|
+
|
|
42
|
+
/** Every build-info file under the scanned directories, as root-relative paths. */
|
|
43
|
+
async function findBuildInfoFiles(root: string): Promise<string[]> {
|
|
44
|
+
const found = await Promise.all(
|
|
45
|
+
BUILD_INFO_DIRS.map(async (dir) => {
|
|
46
|
+
let entries: string[];
|
|
47
|
+
try {
|
|
48
|
+
entries = await readdir(resolve(root, dir));
|
|
49
|
+
} catch {
|
|
50
|
+
return []; // directory absent — nothing to clean there
|
|
51
|
+
}
|
|
52
|
+
return entries
|
|
53
|
+
.filter((entry) => BUILD_INFO_FILE.test(entry))
|
|
54
|
+
.map((entry) => (dir === '.' ? entry : `${dir}/${entry}`));
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
return found.flat();
|
|
58
|
+
}
|
|
59
|
+
|
|
24
60
|
/**
|
|
25
61
|
* Validates that a resolved path stays within the project root.
|
|
26
62
|
* Rejects absolute paths, '..' traversal, and paths that escape cwd.
|
|
@@ -46,7 +82,7 @@ const clean = async (): Promise<void> => {
|
|
|
46
82
|
try {
|
|
47
83
|
const root = process.cwd();
|
|
48
84
|
const args = process.argv.slice(2);
|
|
49
|
-
const buildInfoFiles =
|
|
85
|
+
const buildInfoFiles = await findBuildInfoFiles(root);
|
|
50
86
|
const dirsToClean = [...new Set(args.length > 0 ? args : ['dist', 'logs', ...buildInfoFiles])];
|
|
51
87
|
|
|
52
88
|
console.log(`Cleaning directories: ${dirsToClean.join(', ')}`);
|
package/scripts/devcheck.ts
CHANGED
|
@@ -624,6 +624,13 @@ function classifyAuditVulns(output: string): { direct: string[]; upstream: strin
|
|
|
624
624
|
// Define file extensions for linting and formatting
|
|
625
625
|
const LINT_EXTS = ['.ts', '.tsx', '.js', '.jsx'];
|
|
626
626
|
|
|
627
|
+
/**
|
|
628
|
+
* Worker tsconfig locations, in precedence order. A project keeps its project
|
|
629
|
+
* tsconfigs in `config/` or at the root — the `init` scaffold writes the root
|
|
630
|
+
* form — and this script ships to both verbatim.
|
|
631
|
+
*/
|
|
632
|
+
const WORKER_PROJECT_CANDIDATES = ['config/tsconfig.worker.json', 'tsconfig.worker.json'];
|
|
633
|
+
|
|
627
634
|
const ALL_CHECKS: Check[] = [
|
|
628
635
|
// Fast checks first (local operations, no network)
|
|
629
636
|
{
|
|
@@ -703,18 +710,23 @@ const ALL_CHECKS: Check[] = [
|
|
|
703
710
|
flag: '--no-packaging',
|
|
704
711
|
canFix: false,
|
|
705
712
|
// Validates env var alignment between manifest.json (MCPB bundle) and
|
|
706
|
-
// server.json (MCP Registry), plus plugin marketplace manifests (#240),
|
|
707
|
-
//
|
|
708
|
-
// inputs is present; skipped cleanly when
|
|
709
|
-
// HTTP-only deploy are unaffected.
|
|
713
|
+
// server.json (MCP Registry), plus plugin marketplace manifests (#240), the
|
|
714
|
+
// bundle-content guards on .mcpbignore (#343), and the README version badge
|
|
715
|
+
// (#418). Runs when any of those inputs is present; skipped cleanly when
|
|
716
|
+
// none exist — consumers on an HTTP-only deploy are unaffected. README.md is
|
|
717
|
+
// a trigger in its own right: the badge check must gate a project that
|
|
718
|
+
// carries no bundle or plugin metadata at all, which the other three inputs
|
|
719
|
+
// only covered incidentally.
|
|
710
720
|
getCommand: () => {
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
721
|
+
const inputs = [
|
|
722
|
+
'manifest.json',
|
|
723
|
+
'.claude-plugin/plugin.json',
|
|
724
|
+
'.codex-plugin/plugin.json',
|
|
725
|
+
'.codex-plugin/mcp.json',
|
|
726
|
+
'.mcpbignore',
|
|
727
|
+
'README.md',
|
|
728
|
+
];
|
|
729
|
+
if (!inputs.some((input) => existsSync(path.join(ROOT_DIR, input)))) return null;
|
|
718
730
|
return ['bun', 'run', 'scripts/lint-packaging.ts'];
|
|
719
731
|
},
|
|
720
732
|
tip: (c) =>
|
|
@@ -864,14 +876,20 @@ const ALL_CHECKS: Check[] = [
|
|
|
864
876
|
canFix: false,
|
|
865
877
|
// The workerd type environment is its own program: Cloudflare's ambient
|
|
866
878
|
// globals cannot share one with @types/node's (#397). It reads the built
|
|
867
|
-
// declarations, so it only has something to check after a build.
|
|
879
|
+
// declarations, so it only has something to check after a build. The
|
|
880
|
+
// tsconfig is looked for in both supported layouts — `config/` and the
|
|
881
|
+
// project root, which is where the `init` scaffold writes its tsconfigs
|
|
882
|
+
// (#440) — and the step skips only when neither carries one.
|
|
868
883
|
getCommand: (ctx) => {
|
|
869
|
-
|
|
884
|
+
const project = WORKER_PROJECT_CANDIDATES.find((candidate) =>
|
|
885
|
+
existsSync(path.join(ctx.rootDir, candidate)),
|
|
886
|
+
);
|
|
887
|
+
if (!project) return null;
|
|
870
888
|
if (!existsSync(path.join(ctx.rootDir, 'dist'))) return null;
|
|
871
889
|
return [
|
|
872
890
|
path.join(ctx.rootDir, 'node_modules', '.bin', 'tsc'),
|
|
873
891
|
'--project',
|
|
874
|
-
|
|
892
|
+
project,
|
|
875
893
|
'--noEmit',
|
|
876
894
|
];
|
|
877
895
|
},
|
|
@@ -1030,8 +1048,21 @@ const UI = {
|
|
|
1030
1048
|
return `${c.bold(c.yellow(`🔶 Skipping ${check.name}...`))}${c.dim(` (${reason})`)}`;
|
|
1031
1049
|
},
|
|
1032
1050
|
|
|
1051
|
+
/**
|
|
1052
|
+
* The running-log line for a finished step. A result `isSuccess` demoted to a
|
|
1053
|
+
* warning is reported as one here too, on `printSummary`'s own guard
|
|
1054
|
+
* (`exitCode === 0 && warning`), so the two surfaces cannot disagree about a
|
|
1055
|
+
* single outcome (#344). A `{ success: false, warning }` return keeps its
|
|
1056
|
+
* non-zero exit and so still renders as a failure.
|
|
1057
|
+
*/
|
|
1033
1058
|
formatCheckResult(result: CommandResult, _mode: UIMode): string {
|
|
1034
|
-
const { checkName, exitCode, duration } = result;
|
|
1059
|
+
const { checkName, exitCode, duration, warning } = result;
|
|
1060
|
+
if (exitCode === 0 && warning) {
|
|
1061
|
+
return [
|
|
1062
|
+
`${c.bold(c.yellow('⚠️'))} ${c.yellow(checkName)} ${c.yellow(`finished with a warning in ${duration}ms.`)}`,
|
|
1063
|
+
c.yellow(warning.replace(/^/gm, ' | ')),
|
|
1064
|
+
].join('\n');
|
|
1065
|
+
}
|
|
1035
1066
|
if (exitCode === 0) {
|
|
1036
1067
|
return `${c.bold(c.green('✅'))} ${c.yellow(checkName)} ${c.green(`finished successfully in ${duration}ms.`)}`;
|
|
1037
1068
|
}
|
|
@@ -54,6 +54,11 @@
|
|
|
54
54
|
* path variables (the host delivers anything else as the literal string),
|
|
55
55
|
* and an optional string option has `"default": ""` so a blank answer
|
|
56
56
|
* arrives as empty rather than as the unsubstituted placeholder.
|
|
57
|
+
* 12. README version badge parity: a shields.io `Version-<semver>-` badge in
|
|
58
|
+
* `README.md` must carry the `package.json` `version`. The badge is the
|
|
59
|
+
* package's headline version on GitHub and npmjs.com and ships in the
|
|
60
|
+
* tarball, so a half-finished bump is publicly visible. Skipped when the
|
|
61
|
+
* README, the badge, or the package version is absent (issue #418).
|
|
57
62
|
*
|
|
58
63
|
* Every check skips cleanly when its input is absent — consumers who deleted
|
|
59
64
|
* `manifest.json` for an HTTP-only deploy, or who haven't built a bundle,
|
|
@@ -99,8 +104,10 @@ const USER_CONFIG_REF = /^\$\{user_config\.([\w-]+)\}$/;
|
|
|
99
104
|
/**
|
|
100
105
|
* Root dev directories the scaffold template excludes from the bundle, and
|
|
101
106
|
* whose `.mcpbignore` patterns must be anchored with `/` to avoid also
|
|
102
|
-
* stripping nested runtime paths like `node_modules/x/framework-skills/`. Keep
|
|
103
|
-
* with the directory entries in `
|
|
107
|
+
* stripping nested runtime paths like `node_modules/x/framework-skills/`. Keep
|
|
108
|
+
* in step with the directory entries in this project's `.mcpbignore` — seeded
|
|
109
|
+
* from the mcp-ts-core repository's `templates/_.mcpbignore`, whose `_` prefix
|
|
110
|
+
* `init` drops on copy.
|
|
104
111
|
*/
|
|
105
112
|
export const KNOWN_DEV_DIRS = ['framework-skills/', '.agents/', '.claude/'];
|
|
106
113
|
|
|
@@ -121,7 +128,10 @@ export const CRITICAL_RUNTIME_PATHS = [
|
|
|
121
128
|
* `framework-skills/` is this framework's tree; `skills/` covers any other
|
|
122
129
|
* dependency that vendors agent skills.
|
|
123
130
|
* KEEP IN SYNC with `AGENT_DOC_ENTRY` in `scripts/clean-mcpb.ts` (the strip
|
|
124
|
-
* step this check verifies) —
|
|
131
|
+
* step this check verifies) — edit both literals together. The assertion that
|
|
132
|
+
* they match lives in the mcp-ts-core repository's own test suite; `tests/` is
|
|
133
|
+
* not part of the published package, so nothing enforces the pair in a server
|
|
134
|
+
* these scripts were copied into.
|
|
125
135
|
*/
|
|
126
136
|
export const AGENT_DOC_ENTRY =
|
|
127
137
|
/^node_modules\/.*(?:\/framework-skills\/|\/skills\/|\/\.claude\/|\/\.agents\/|\/SKILL\.md$)/;
|
|
@@ -129,7 +139,10 @@ export const AGENT_DOC_ENTRY =
|
|
|
129
139
|
/**
|
|
130
140
|
* Platform-specific native binding packages that must not ship in a bundle.
|
|
131
141
|
* KEEP IN SYNC with `NATIVE_BINDING_ENTRY` in `scripts/clean-mcpb.ts` (the
|
|
132
|
-
* strip step this check verifies) —
|
|
142
|
+
* strip step this check verifies) — edit both literals together. The assertion
|
|
143
|
+
* that they match lives in the mcp-ts-core repository's own test suite;
|
|
144
|
+
* `tests/` is not part of the published package, so nothing enforces the pair
|
|
145
|
+
* in a server these scripts were copied into.
|
|
133
146
|
*/
|
|
134
147
|
export const NATIVE_BINDING_ENTRY = /^node_modules\/@duckdb\/node-bindings-[^/]+\//;
|
|
135
148
|
|
|
@@ -673,6 +686,62 @@ export function checkPluginManifests(
|
|
|
673
686
|
return errors;
|
|
674
687
|
}
|
|
675
688
|
|
|
689
|
+
/**
|
|
690
|
+
* The shields.io static version badge, anchored on the `Version-` label and the
|
|
691
|
+
* `-` that closes the version segment. A literal `-` inside a badge segment is
|
|
692
|
+
* escaped as `--`, so the segment is "runs of non-dash characters joined by
|
|
693
|
+
* escaped dashes" — which also keeps the scan linear, since the alternation
|
|
694
|
+
* cannot match the same character two ways. Anchoring on the label and the
|
|
695
|
+
* trailing `-` tolerates colour, extension, and query-string variation without
|
|
696
|
+
* enumerating them, and matches no other badge: a live `img.shields.io/npm/v/…`
|
|
697
|
+
* badge has no `badge/Version-` path.
|
|
698
|
+
*/
|
|
699
|
+
const README_VERSION_BADGE = /img\.shields\.io\/badge\/Version-([^-]*(?:--[^-]*)*)-/;
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* A version the badge can be compared against once its `--` escapes are
|
|
703
|
+
* decoded: the semver core, then at most one `-` prerelease segment and one
|
|
704
|
+
* `+` build segment. The two are separate optionals rather than one repeated
|
|
705
|
+
* `(?:[-+]…)*`, because `-` is itself a member of the segment character class
|
|
706
|
+
* — a repeated group can split a run of dashes two ways and backtracks
|
|
707
|
+
* exponentially on a segment the check is about to reject (CodeQL `js/redos`,
|
|
708
|
+
* CWE-1333). `+` is outside the class, so each segment's end is determined and
|
|
709
|
+
* the scan stays linear.
|
|
710
|
+
*/
|
|
711
|
+
const READABLE_VERSION = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* Check 12: README version badge parity. When `README.md` carries a shields.io
|
|
715
|
+
* `Version-<semver>-` badge, its version must equal `package.json` `version` —
|
|
716
|
+
* the badge is the package's headline version on GitHub and npmjs.com, and it
|
|
717
|
+
* ships in the tarball, so a half-finished bump is publicly visible.
|
|
718
|
+
*
|
|
719
|
+
* Skipped when the README, the badge, or the package version is absent: a
|
|
720
|
+
* server that replaced the static badge with a live `npm/v` one has nothing to
|
|
721
|
+
* check, and a version-less `package.json` is the same fail-safe the
|
|
722
|
+
* plugin-manifest parity check applies. A badge that exists but cannot be read
|
|
723
|
+
* is drift the check cannot rule out, so it fails rather than skips.
|
|
724
|
+
*/
|
|
725
|
+
export function checkReadmeVersionBadge(readme: string, packageVersion?: string): string[] {
|
|
726
|
+
if (!packageVersion) return [];
|
|
727
|
+
|
|
728
|
+
const segment = README_VERSION_BADGE.exec(readme)?.[1];
|
|
729
|
+
if (segment === undefined) return [];
|
|
730
|
+
|
|
731
|
+
const badgeVersion = segment.replaceAll('--', '-');
|
|
732
|
+
if (!READABLE_VERSION.test(badgeVersion)) {
|
|
733
|
+
return [
|
|
734
|
+
`README.md version badge segment is "${segment}" — not a readable version, so it cannot be ` +
|
|
735
|
+
`checked against the package.json version "${packageVersion}"; write the badge as ` +
|
|
736
|
+
`"Version-${packageVersion.replaceAll('-', '--')}-"`,
|
|
737
|
+
];
|
|
738
|
+
}
|
|
739
|
+
if (badgeVersion === packageVersion) return [];
|
|
740
|
+
return [
|
|
741
|
+
`README.md version badge is "${badgeVersion}" — must equal the package.json version "${packageVersion}"`,
|
|
742
|
+
];
|
|
743
|
+
}
|
|
744
|
+
|
|
676
745
|
/** Read `packaging.pluginManifests` from devcheck.config.json; default on. */
|
|
677
746
|
function pluginManifestsEnabled(): boolean {
|
|
678
747
|
const cfg = tryReadJson<{ packaging?: { pluginManifests?: boolean } }>(
|
|
@@ -805,6 +874,12 @@ async function main(): Promise<void> {
|
|
|
805
874
|
}
|
|
806
875
|
}
|
|
807
876
|
|
|
877
|
+
// ── README version badge (check 12) ──
|
|
878
|
+
const readmePath = resolve('README.md');
|
|
879
|
+
if (existsSync(readmePath)) {
|
|
880
|
+
errors.push(...checkReadmeVersionBadge(readFileSync(readmePath, 'utf-8'), pkg?.version));
|
|
881
|
+
}
|
|
882
|
+
|
|
808
883
|
// ── Plugin marketplace manifests (check 10) ──
|
|
809
884
|
if (unscopedName && pkg?.name) {
|
|
810
885
|
if (pluginManifestsEnabled()) {
|
package/templates/.env.example
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# ── Transport ──────────────────────────────────────────────────────────
|
|
2
2
|
# MCP_TRANSPORT_TYPE=stdio # stdio | http (default: stdio)
|
|
3
3
|
# MCP_HTTP_PORT=3010 # HTTP port (default: 3010)
|
|
4
|
-
# MCP_HTTP_HOST=
|
|
4
|
+
# MCP_HTTP_HOST=127.0.0.1 # HTTP host (default: 127.0.0.1)
|
|
5
5
|
# MCP_HTTP_ENDPOINT_PATH=/mcp # HTTP endpoint path (default: /mcp)
|
|
6
6
|
# MCP_HTTP_MAX_BODY_BYTES=1048576 # Max request body bytes; 413 over limit, 0 disables (default: 1048576)
|
|
7
7
|
# MCP_PUBLIC_URL= # Public origin behind a TLS-terminating proxy (e.g. https://mcp.example.com)
|
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
# STORAGE_PROVIDER_TYPE=in-memory # in-memory | filesystem | supabase | cloudflare-r2 | cloudflare-kv | cloudflare-d1
|
|
15
15
|
|
|
16
16
|
# ── Session ──────────────────────────────────────────────────────────
|
|
17
|
-
|
|
17
|
+
MCP_SESSION_MODE=stateless # stateful | stateless | auto. Set here, not left to the schema default
|
|
18
|
+
# (auto, which resolves to stateful). Stateless fits a data API:
|
|
19
|
+
# no session store, horizontally scalable. Use stateful when a tool
|
|
20
|
+
# asks the caller for input mid-handler (ctx.requestInput).
|
|
18
21
|
# MCP_HTTP_RESUMABILITY=false # SSE replay on a dropped stateful stream. Default: true.
|
|
19
22
|
# Kill switch only — no effect on stateless serving.
|
|
20
23
|
# MCP_HTTP_RESUMABILITY_MAX_EVENTS=512 # Retained per session, oldest evicted first
|
package/templates/AGENTS.md
CHANGED
|
@@ -174,6 +174,22 @@ await createApp({
|
|
|
174
174
|
|
|
175
175
|
`instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
|
|
176
176
|
|
|
177
|
+
### Session posture and shutdown
|
|
178
|
+
|
|
179
|
+
Two more `createApp()` options shape how the server runs rather than how it presents itself:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
await createApp({
|
|
183
|
+
sessionMode: 'stateless', // or { default: 'stateful', require: 'stateful' }
|
|
184
|
+
setup(core) { startMyWatcher(core.config); },
|
|
185
|
+
async teardown() { await stopMyWatcher(); },
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`sessionMode` declares the HTTP session posture in `src/` instead of leaving it to a deployment's `MCP_SESSION_MODE`, which still wins whenever it carries a meaningful value (an empty string and an unsubstituted `${…}` placeholder read as unset and fall through to the option). Add `require: 'stateful'` when a tool asks the caller for input mid-handler via `ctx.requestInput`: startup then fails with a `ConfigurationError` rather than serving a mode in which a 2025-era client can never answer the prompt. Stdio is never refused.
|
|
190
|
+
|
|
191
|
+
`teardown(core)` is the `setup()` counterpart — release a watcher, socket, or non-`unref()`'d timer there. It runs after the transport stops and before the logger closes, on every shutdown path, and a signal-triggered shutdown then exits the process explicitly (0, or 1 if a step never settles within the framework's 10 s ceiling).
|
|
192
|
+
|
|
177
193
|
---
|
|
178
194
|
|
|
179
195
|
## Context
|
package/templates/CLAUDE.md
CHANGED
|
@@ -174,6 +174,22 @@ await createApp({
|
|
|
174
174
|
|
|
175
175
|
`instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
|
|
176
176
|
|
|
177
|
+
### Session posture and shutdown
|
|
178
|
+
|
|
179
|
+
Two more `createApp()` options shape how the server runs rather than how it presents itself:
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
await createApp({
|
|
183
|
+
sessionMode: 'stateless', // or { default: 'stateful', require: 'stateful' }
|
|
184
|
+
setup(core) { startMyWatcher(core.config); },
|
|
185
|
+
async teardown() { await stopMyWatcher(); },
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`sessionMode` declares the HTTP session posture in `src/` instead of leaving it to a deployment's `MCP_SESSION_MODE`, which still wins whenever it carries a meaningful value (an empty string and an unsubstituted `${…}` placeholder read as unset and fall through to the option). Add `require: 'stateful'` when a tool asks the caller for input mid-handler via `ctx.requestInput`: startup then fails with a `ConfigurationError` rather than serving a mode in which a 2025-era client can never answer the prompt. Stdio is never refused.
|
|
190
|
+
|
|
191
|
+
`teardown(core)` is the `setup()` counterpart — release a watcher, socket, or non-`unref()`'d timer there. It runs after the transport stops and before the logger closes, on every shutdown path, and a signal-triggered shutdown then exits the process explicitly (0, or 1 if a step never settles within the framework's 10 s ceiling).
|
|
192
|
+
|
|
177
193
|
---
|
|
178
194
|
|
|
179
195
|
## Context
|
package/templates/src/index.ts
CHANGED
|
@@ -20,4 +20,14 @@ await createApp({
|
|
|
20
20
|
// instructions: 'Server-level orientation forwarded to the model on every initialize.\n' +
|
|
21
21
|
// '- Use shortcut `X` for the most common case\n' +
|
|
22
22
|
// '- Tools require auth via the `inventory:read` scope',
|
|
23
|
+
|
|
24
|
+
// Session posture in code rather than in a Dockerfile. MCP_SESSION_MODE still
|
|
25
|
+
// wins when it is set. Add `require: 'stateful'` — `{ default: 'stateful',
|
|
26
|
+
// require: 'stateful' }` — when a tool asks the caller for input mid-handler,
|
|
27
|
+
// so a stateless deployment fails at startup instead of losing that tool.
|
|
28
|
+
// sessionMode: 'stateless',
|
|
29
|
+
|
|
30
|
+
// Release what setup() allocated: a watcher, a socket, a timer the framework
|
|
31
|
+
// cannot see. Runs after the transport stops and before the logger closes.
|
|
32
|
+
// teardown(core) { core.logger.info('bye', { requestId: 'shutdown', timestamp: new Date().toISOString() }); },
|
|
23
33
|
});
|