@cyanheads/mcp-ts-core 0.10.8 → 0.10.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Package:** `@cyanheads/mcp-ts-core`
4
- **Version:** 0.10.8
4
+ **Version:** 0.10.10
5
5
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
6
6
  **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
7
7
  **Zod:** ^4.4.3
package/CLAUDE.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Developer Protocol
2
2
 
3
3
  **Package:** `@cyanheads/mcp-ts-core`
4
- **Version:** 0.10.8
4
+ **Version:** 0.10.10
5
5
  **Engines:** Bun ≥1.3.0, Node ≥24.0.0
6
6
  **MCP SDK:** `@modelcontextprotocol/sdk` ^1.29.0
7
7
  **Zod:** ^4.4.3
package/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  <div align="center">
7
7
 
8
- [![Version](https://img.shields.io/badge/Version-0.10.8-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx)
8
+ [![Version](https://img.shields.io/badge/Version-0.10.10-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx)
9
9
 
10
10
  [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.0%2B-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
@@ -301,15 +301,6 @@ bun run lint:mcp # validate MCP definitions against spec
301
301
  bun run test:all # vitest: unit + Workers pool + integration
302
302
  ```
303
303
 
304
- ## Contributing
305
-
306
- Issues and pull requests welcome. Run checks before submitting:
307
-
308
- ```bash
309
- bun run devcheck
310
- bun run test:all
311
- ```
312
-
313
304
  ## License
314
305
 
315
306
  Apache 2.0 — see [LICENSE](./LICENSE).
package/biome.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.5.0/schema.json",
2
+ "$schema": "https://biomejs.dev/schemas/2.5.1/schema.json",
3
3
  "vcs": {
4
4
  "enabled": true,
5
5
  "clientKind": "git",
@@ -0,0 +1,39 @@
1
+ ---
2
+ summary: "js-yaml v4 → v5 migration: YamlParser uses YAML11_SCHEMA to preserve YAML 1.1 load semantics after v5 defaults to CORE_SCHEMA, namespace import replaces the removed default export, @types/js-yaml dropped (v5 self-types). Breaking: the js-yaml peer dependency moves ^4 → ^5. Plus biome 2.5.1 and a dependency refresh."
3
+ breaking: true
4
+ security: false
5
+ agent-notes: |
6
+ The js-yaml peer dependency moved ^4 → ^5 (major). A consumer server that
7
+ installs js-yaml directly to use the Tier-3 YamlParser must bump it to ^5. No
8
+ consumer code change is required — the framework now loads with YAML11_SCHEMA,
9
+ preserving the prior YAML 1.1 semantics, so parsing behavior is unchanged.
10
+ ---
11
+
12
+ # 0.10.10 — 2026-06-30
13
+
14
+ ## Changed
15
+
16
+ - **`YamlParser.parse`** — js-yaml v5 removed `DEFAULT_SCHEMA` and defaults `load()` to the stricter YAML 1.2 `CORE_SCHEMA`; the parser now passes `YAML11_SCHEMA` to preserve the prior YAML 1.1 load semantics (`yes`/`no`/`on`/`off` → boolean, `!!timestamp` → `Date`) that LLM-generated YAML relies on.
17
+ - **`scripts/fetch-openapi-spec.ts`** — switched to `import * as yaml from 'js-yaml'`; v5 dropped the default export.
18
+ - **`biome.json`** — `$schema` `2.5.0` → `2.5.1`, tracking the `@biomejs/biome` bump.
19
+
20
+ ## Removed
21
+
22
+ - **`@types/js-yaml`** — js-yaml v5 ships its own type declarations, so the separate `@types` package is gone.
23
+ - **`js-yaml` resolution pin** — dropped the `resolutions`/`overrides` `js-yaml: ^4.2.0` entry; the v5 upgrade resolves the dependency tree without it.
24
+
25
+ ### Dependency bumps
26
+
27
+ - `js-yaml` `^4.2.0` → `^5.2.0` (peer `^4.1.1` → `^5.2.0`)
28
+ - `@hono/node-server` `^2.0.5` → `^2.0.6`
29
+ - `hono` `^4.12.26` → `^4.12.27`
30
+ - `@biomejs/biome` `2.5.0` → `2.5.1`
31
+ - `@cloudflare/vitest-pool-workers` `^0.16.18` → `^0.16.20`
32
+ - `@cloudflare/workers-types` `4.20260620.1` → `4.20260630.1`
33
+ - `@supabase/supabase-js` `^2.108.2` → `^2.109.0`
34
+ - `@types/node` `26.0.0` → `26.0.1`
35
+ - `defuddle` `^0.19.0` → `^0.19.1`
36
+ - `node-cron` `^4.4.1` → `^4.5.0`
37
+ - `openai` `^6.44.0` → `^6.45.0`
38
+ - `repomix` `^1.15.0` → `^1.16.0`
39
+ - `vite` `8.0.16` → `8.1.0`
@@ -0,0 +1,16 @@
1
+ ---
2
+ summary: "devcheck gains two static guards: floating dependency specifiers (latest/*/dist-tags) in package.json + bun.lock workspaces, and plugin marketplace manifest correctness (descriptions + scoped/unscoped identity)"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 0.10.9 — 2026-06-20
8
+
9
+ ## Added
10
+
11
+ - **`check-dependency-specifiers` devcheck step** — new `scripts/check-dependency-specifiers.ts`, registered in `ALL_CHECKS` (`Dependency Specifiers`, flag `--no-dep-specifiers`), hard-fails on floating specifiers in the manifests we control: `package.json`'s four dependency sections and `bun.lock`'s `workspaces` specifier maps (never the `packages` section, which records third-party nested declarations that legitimately float). `latest` fails in every section; `*` and the pre-release dist-tags `next`/`beta`/`canary`/`rc` fail in `dependencies`/`devDependencies` but are allowed in `peerDependencies`/`optionalDependencies` ("any host version"). Catches the case where `bun update --latest` writes a literal `latest` dist-tag into the lock's workspace map — correct at write time, so it clears every other gate, then lets the next `bun install` re-resolve the dep past the `package.json` range and past any intentional hold. Static and local (no network, no git), so it runs in the default and `--fast` passes; ships to consumers via `package.json` `files:`. ([#246](https://github.com/cyanheads/mcp-ts-core/issues/246))
12
+ - **Plugin marketplace manifest checks in `lint:packaging`** — `scripts/lint-packaging.ts` gains check 10, validating `.claude-plugin/plugin.json`, `.codex-plugin/plugin.json`, and `.codex-plugin/mcp.json` when present: non-empty descriptions, display fields (`name`, server key, `interface.displayName`) carrying the **unscoped** machine name, and the `npx -y` install arg carrying the **full** `package.json` name. An unscoped install arg for a scoped package is a guaranteed install 404 — the high-value catch. Gated by a new `devcheck.config.json` `packaging.pluginManifests` flag (default on; surfaced in `templates/devcheck.config.json`); each manifest is skipped cleanly when absent, so HTTP-only and non-plugin consumers are unaffected. The `devcheck` "Packaging" gate, previously skipped unless `manifest.json` existed, now also runs when any plugin manifest is present. `skills/polish-docs-meta` step 10 documents the automated subset. ([#240](https://github.com/cyanheads/mcp-ts-core/issues/240))
13
+
14
+ ## Changed
15
+
16
+ - **Dependency:** `@cloudflare/workers-types` 4.20260619.1 → 4.20260620.1
@@ -13,7 +13,7 @@ export declare class YamlParser {
13
13
  * This method is async because `js-yaml` is loaded lazily on first call.
14
14
  * If the input begins with a `<think>...</think>` block, that block is stripped
15
15
  * and its content logged before parsing the remainder. Uses `js-yaml`'s
16
- * `DEFAULT_SCHEMA` for parsing.
16
+ * `YAML11_SCHEMA` (YAML 1.1 load semantics) for parsing.
17
17
  *
18
18
  * @template T - The expected type of the parsed result. Defaults to `unknown`.
19
19
  * @param yamlString - The YAML string to parse. May be prefixed with a `<think>` block.
@@ -1 +1 @@
1
- {"version":3,"file":"yamlParser.d.ts","sourceRoot":"","sources":["../../../src/utils/parsing/yamlParser.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,cAAc,EAAyB,MAAM,oCAAoC,CAAC;AAQhG;;;;;;GAMG;AACH,qBAAa,UAAU;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACG,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CAyDnF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,UAAU,YAAmB,CAAC"}
1
+ {"version":3,"file":"yamlParser.d.ts","sourceRoot":"","sources":["../../../src/utils/parsing/yamlParser.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,cAAc,EAAyB,MAAM,oCAAoC,CAAC;AAQhG;;;;;;GAMG;AACH,qBAAa,UAAU;IACrB;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACG,KAAK,CAAC,CAAC,GAAG,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,CAAC,CAAC;CA4DnF;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,UAAU,YAAmB,CAAC"}
@@ -27,7 +27,7 @@ export class YamlParser {
27
27
  * This method is async because `js-yaml` is loaded lazily on first call.
28
28
  * If the input begins with a `<think>...</think>` block, that block is stripped
29
29
  * and its content logged before parsing the remainder. Uses `js-yaml`'s
30
- * `DEFAULT_SCHEMA` for parsing.
30
+ * `YAML11_SCHEMA` (YAML 1.1 load semantics) for parsing.
31
31
  *
32
32
  * @template T - The expected type of the parsed result. Defaults to `unknown`.
33
33
  * @param yamlString - The YAML string to parse. May be prefixed with a `<think>` block.
@@ -75,7 +75,10 @@ export class YamlParser {
75
75
  }
76
76
  try {
77
77
  const yaml = await getYaml();
78
- return yaml.load(stringToParse, { schema: yaml.DEFAULT_SCHEMA });
78
+ // js-yaml v5 removed DEFAULT_SCHEMA and defaults load() to the stricter 1.2
79
+ // CORE_SCHEMA; YAML11_SCHEMA preserves the prior 1.1 load semantics
80
+ // (yes/no/on/off → boolean, !!timestamp → Date) that LLM YAML output relies on.
81
+ return yaml.load(stringToParse, { schema: yaml.YAML11_SCHEMA });
79
82
  }
80
83
  catch (e) {
81
84
  const error = e instanceof Error ? e : new Error(String(e));
@@ -1 +1 @@
1
- {"version":3,"file":"yamlParser.js","sourceRoot":"","sources":["../../../src/utils/parsing/yamlParser.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"yamlParser.js","sourceRoot":"","sources":["../../../src/utils/parsing/yamlParser.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/mcp-ts-core",
3
- "version": "0.10.8",
3
+ "version": "0.10.10",
4
4
  "mcpName": "io.github.cyanheads/mcp-ts-core",
5
5
  "description": "Agent-native TypeScript framework for building MCP servers. Declarative definitions with auth, multi-backend storage, OpenTelemetry, and first-class support for Bun/Node/Cloudflare Workers.",
6
6
  "main": "dist/core/index.js",
@@ -10,6 +10,7 @@
10
10
  "dist/",
11
11
  "scripts/build-changelog.ts",
12
12
  "scripts/build.ts",
13
+ "scripts/check-dependency-specifiers.ts",
13
14
  "scripts/check-docs-sync.ts",
14
15
  "scripts/check-framework-antipatterns.ts",
15
16
  "scripts/check-skill-versions.ts",
@@ -192,13 +193,10 @@
192
193
  "release:github": "bun run scripts/release-github.ts",
193
194
  "publish-mcp": "mcp-publisher login github -token \"$(security find-generic-password -a \"$USER\" -s mcp-publisher-github-pat -w)\" && mcp-publisher publish"
194
195
  },
195
- "resolutions": {
196
- "js-yaml": "^4.2.0"
197
- },
198
196
  "devDependencies": {
199
- "@biomejs/biome": "2.5.0",
200
- "@cloudflare/vitest-pool-workers": "^0.16.18",
201
- "@cloudflare/workers-types": "4.20260619.1",
197
+ "@biomejs/biome": "2.5.1",
198
+ "@cloudflare/vitest-pool-workers": "^0.16.20",
199
+ "@cloudflare/workers-types": "4.20260630.1",
202
200
  "@duckdb/node-api": "^1.5.4-r.1",
203
201
  "@hono/otel": "^1.1.2",
204
202
  "@opentelemetry/exporter-metrics-otlp-http": "^0.219.0",
@@ -210,10 +208,9 @@
210
208
  "@opentelemetry/sdk-node": "^0.219.0",
211
209
  "@opentelemetry/sdk-trace-node": "^2.8.0",
212
210
  "@opentelemetry/semantic-conventions": "^1.41.1",
213
- "@supabase/supabase-js": "^2.108.2",
211
+ "@supabase/supabase-js": "^2.109.0",
214
212
  "@types/bun": "^1.3.14",
215
- "@types/js-yaml": "^4.0.9",
216
- "@types/node": "26.0.0",
213
+ "@types/node": "26.0.1",
217
214
  "@types/papaparse": "^5.5.2",
218
215
  "@types/sanitize-html": "^2.16.1",
219
216
  "@types/validator": "^13.15.10",
@@ -223,29 +220,29 @@
223
220
  "bun-types": "^1.3.14",
224
221
  "chrono-node": "^2.9.1",
225
222
  "clipboardy": "^5.3.1",
226
- "defuddle": "^0.19.0",
223
+ "defuddle": "^0.19.1",
227
224
  "depcheck": "^1.4.7",
228
225
  "diff": "^9.0.0",
229
226
  "execa": "^9.6.1",
230
227
  "fast-check": "^4.8.0",
231
228
  "fast-xml-parser": "^5.9.3",
232
229
  "ignore": "^7.0.5",
233
- "js-yaml": "^4.2.0",
230
+ "js-yaml": "^5.2.0",
234
231
  "linkedom": "^0.18.12",
235
- "node-cron": "^4.4.1",
236
- "openai": "^6.44.0",
232
+ "node-cron": "^4.5.0",
233
+ "openai": "^6.45.0",
237
234
  "papaparse": "^5.5.4",
238
235
  "partial-json": "^0.1.7",
239
236
  "pdf-lib": "^1.17.1",
240
237
  "pino-pretty": "^13.1.3",
241
- "repomix": "^1.15.0",
238
+ "repomix": "^1.16.0",
242
239
  "sanitize-html": "^2.17.5",
243
240
  "tsc-alias": "^1.8.17",
244
241
  "typedoc": "^0.28.19",
245
242
  "typescript": "^6.0.3",
246
243
  "unpdf": "^1.6.2",
247
244
  "validator": "^13.15.35",
248
- "vite": "8.0.16",
245
+ "vite": "8.1.0",
249
246
  "vitest": "^4.1.9"
250
247
  },
251
248
  "keywords": [
@@ -298,12 +295,12 @@
298
295
  },
299
296
  "dependencies": {
300
297
  "@hono/mcp": "^0.3.0",
301
- "@hono/node-server": "^2.0.5",
298
+ "@hono/node-server": "^2.0.6",
302
299
  "@modelcontextprotocol/ext-apps": "^1.7.4",
303
300
  "@modelcontextprotocol/sdk": "^1.29.0",
304
301
  "@opentelemetry/api": "^1.9.1",
305
302
  "dotenv": "^17.4.2",
306
- "hono": "^4.12.26",
303
+ "hono": "^4.12.27",
307
304
  "jose": "^6.2.3",
308
305
  "pino": "^10.3.1",
309
306
  "zod": "^4.4.3"
@@ -328,7 +325,7 @@
328
325
  "fast-check": "^4.0.0",
329
326
  "vitest": ">=4.0.0",
330
327
  "fast-xml-parser": "^5.8.0",
331
- "js-yaml": "^4.1.1",
328
+ "js-yaml": "^5.2.0",
332
329
  "linkedom": "^0.18.12",
333
330
  "node-cron": "^4.2.1",
334
331
  "openai": "^6.34.0",
@@ -0,0 +1,190 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @fileoverview Rejects floating dependency specifiers — `latest`, `*`, and
4
+ * pre-release dist-tags (`next`/`beta`/`canary`/`rc`) — in the manifests we
5
+ * control: `package.json`'s dependency sections and `bun.lock`'s `workspaces`
6
+ * specifier maps. `bun update --latest` bumps `package.json` to a caret range
7
+ * but writes the literal `latest` dist-tag into the lock's workspace map; the
8
+ * concrete resolution is correct at write time, so it clears every other gate
9
+ * and ships unnoticed, letting the next `bun install` re-resolve the dep past
10
+ * the `package.json` range and past any intentional version hold (#246).
11
+ *
12
+ * Section-specific rules — `peer`/`optional` may legitimately float to "any
13
+ * host version", so `*` and dist-tags are allowed there; `latest` never is:
14
+ *
15
+ * | specifier | dependencies / devDependencies | peer / optional |
16
+ * |---------------------------|:------------------------------:|:---------------:|
17
+ * | latest | fail | fail |
18
+ * | * | fail | allow |
19
+ * | next / beta / canary / rc | fail | allow |
20
+ *
21
+ * Only the `workspaces` section of `bun.lock` is inspected — the `packages`
22
+ * section records third-party packages' own nested declarations, which can
23
+ * legitimately be `latest`/`*` (e.g. a vendored `"@edge-runtime/vm": "*"`), so
24
+ * a naive whole-file grep false-positives on those. The lock is JSONC (trailing
25
+ * commas), so it is normalized before parsing.
26
+ *
27
+ * Shipped to consumers via `package.json` `files:` because `devcheck` invokes
28
+ * it. Runs standalone (`bun run scripts/check-dependency-specifiers.ts`) and as
29
+ * a devcheck step; no network, no git — runs in the default and `--fast` passes.
30
+ *
31
+ * @module scripts/check-dependency-specifiers
32
+ */
33
+ import { existsSync, readFileSync } from 'node:fs';
34
+ import { resolve } from 'node:path';
35
+ import process from 'node:process';
36
+
37
+ const ROOT = resolve('.');
38
+
39
+ const DEP_SECTIONS = [
40
+ 'dependencies',
41
+ 'devDependencies',
42
+ 'peerDependencies',
43
+ 'optionalDependencies',
44
+ ] as const;
45
+ type DepSection = (typeof DEP_SECTIONS)[number];
46
+
47
+ /** Sections where a host project legitimately accepts "any version" (`*`/dist-tag). */
48
+ const HOST_SECTIONS = new Set<DepSection>(['peerDependencies', 'optionalDependencies']);
49
+
50
+ /** Pre-release dist-tags that float like `latest`, narrower in reach. */
51
+ const FLOATING_DIST_TAGS = new Set(['next', 'beta', 'canary', 'rc']);
52
+
53
+ interface Offender {
54
+ location: string;
55
+ name: string;
56
+ specifier: string;
57
+ }
58
+
59
+ type DepMap = Record<string, string>;
60
+ type SectionedManifest = Partial<Record<DepSection, DepMap>>;
61
+
62
+ /** A specifier floats if it can re-resolve to a different version over time. */
63
+ function isFloating(specifier: string, section: DepSection): boolean {
64
+ const spec = specifier.trim();
65
+ if (spec === 'latest') return true; // never reproducible, in any section
66
+ if (HOST_SECTIONS.has(section)) return false; // `*` / dist-tags are legitimate here
67
+ return spec === '*' || FLOATING_DIST_TAGS.has(spec);
68
+ }
69
+
70
+ /** Scan the four dependency sections of one manifest object. */
71
+ function scanManifest(manifest: SectionedManifest, label: string): Offender[] {
72
+ const offenders: Offender[] = [];
73
+ for (const section of DEP_SECTIONS) {
74
+ const deps = manifest[section];
75
+ if (!deps || typeof deps !== 'object') continue;
76
+ for (const [name, specifier] of Object.entries(deps)) {
77
+ if (typeof specifier === 'string' && isFloating(specifier, section)) {
78
+ offenders.push({ name, specifier, location: `${label} ${section}` });
79
+ }
80
+ }
81
+ }
82
+ return offenders;
83
+ }
84
+
85
+ /**
86
+ * Minimal JSONC → object parse: strips `//` and block comments and trailing
87
+ * commas while preserving string contents verbatim, then `JSON.parse`s.
88
+ * `bun.lock` is emitted with trailing commas, which strict `JSON.parse` rejects.
89
+ */
90
+ function parseJsonc<T>(text: string): T {
91
+ let out = '';
92
+ let i = 0;
93
+ const n = text.length;
94
+ while (i < n) {
95
+ const ch = text[i] as string;
96
+ if (ch === '"') {
97
+ // Copy a string literal verbatim, honoring escapes.
98
+ out += ch;
99
+ i++;
100
+ while (i < n) {
101
+ const c = text[i] as string;
102
+ out += c;
103
+ if (c === '\\') {
104
+ out += text[i + 1] ?? '';
105
+ i += 2;
106
+ continue;
107
+ }
108
+ i++;
109
+ if (c === '"') break;
110
+ }
111
+ continue;
112
+ }
113
+ if (ch === '/' && text[i + 1] === '/') {
114
+ const nl = text.indexOf('\n', i);
115
+ if (nl === -1) break;
116
+ i = nl;
117
+ continue;
118
+ }
119
+ if (ch === '/' && text[i + 1] === '*') {
120
+ const end = text.indexOf('*/', i + 2);
121
+ i = end === -1 ? n : end + 2;
122
+ continue;
123
+ }
124
+ if (ch === ',') {
125
+ // Drop the comma when the next significant char closes an object/array.
126
+ let j = i + 1;
127
+ while (j < n && /\s/.test(text[j] as string)) j++;
128
+ if (text[j] === '}' || text[j] === ']') {
129
+ i++;
130
+ continue;
131
+ }
132
+ }
133
+ out += ch;
134
+ i++;
135
+ }
136
+ return JSON.parse(out) as T;
137
+ }
138
+
139
+ const offenders: Offender[] = [];
140
+
141
+ // ── package.json (all four dependency sections) ──
142
+ const pkgPath = resolve(ROOT, 'package.json');
143
+ if (existsSync(pkgPath)) {
144
+ try {
145
+ offenders.push(
146
+ ...scanManifest(
147
+ JSON.parse(readFileSync(pkgPath, 'utf-8')) as SectionedManifest,
148
+ 'package.json',
149
+ ),
150
+ );
151
+ } catch (err) {
152
+ console.error(`Failed to parse package.json: ${err instanceof Error ? err.message : err}`);
153
+ process.exit(2);
154
+ }
155
+ }
156
+
157
+ // ── bun.lock (workspaces specifier maps only — never the `packages` section) ──
158
+ const lockPath = resolve(ROOT, 'bun.lock');
159
+ if (existsSync(lockPath)) {
160
+ try {
161
+ const lock = parseJsonc<{ workspaces?: Record<string, SectionedManifest> }>(
162
+ readFileSync(lockPath, 'utf-8'),
163
+ );
164
+ for (const [ws, manifest] of Object.entries(lock.workspaces ?? {})) {
165
+ const label = ws === '' ? 'bun.lock workspaces[root]' : `bun.lock workspaces["${ws}"]`;
166
+ offenders.push(...scanManifest(manifest, label));
167
+ }
168
+ } catch (err) {
169
+ console.error(`Failed to parse bun.lock: ${err instanceof Error ? err.message : err}`);
170
+ process.exit(2);
171
+ }
172
+ }
173
+
174
+ if (offenders.length === 0) {
175
+ console.log('No floating dependency specifiers found.');
176
+ process.exit(0);
177
+ }
178
+
179
+ const lines = [
180
+ `${offenders.length} floating dependency specifier(s) found:`,
181
+ '',
182
+ ...offenders.map((o) => ` - ${o.name} → ${o.specifier} (${o.location})`),
183
+ '',
184
+ 'Fix: pin to a concrete semver range (e.g. ^1.2.3). A `latest`/`*`/dist-tag',
185
+ 'specifier lets `bun install` re-resolve the dep past the package.json range',
186
+ 'and past any intentional hold. After `bun update --latest`, run a plain',
187
+ '`bun install` to reconcile bun.lock to the package.json ranges.',
188
+ ];
189
+ console.error(lines.join('\n'));
190
+ process.exit(1);
@@ -437,10 +437,16 @@ const ALL_CHECKS: Check[] = [
437
437
  flag: '--no-packaging',
438
438
  canFix: false,
439
439
  // Validates env var alignment between manifest.json (MCPB bundle) and
440
- // server.json (MCP Registry). Skipped cleanly when manifest.json is absent
441
- // consumers who deleted it for an HTTP-only deploy are unaffected.
440
+ // server.json (MCP Registry), plus plugin marketplace manifests (#240).
441
+ // Runs when manifest.json OR any plugin manifest is present; skipped cleanly
442
+ // when none exist — consumers on an HTTP-only deploy are unaffected.
442
443
  getCommand: () => {
443
- if (!existsSync(path.join(ROOT_DIR, 'manifest.json'))) return null;
444
+ const hasManifest = existsSync(path.join(ROOT_DIR, 'manifest.json'));
445
+ const hasPluginManifest =
446
+ existsSync(path.join(ROOT_DIR, '.claude-plugin/plugin.json')) ||
447
+ existsSync(path.join(ROOT_DIR, '.codex-plugin/plugin.json')) ||
448
+ existsSync(path.join(ROOT_DIR, '.codex-plugin/mcp.json'));
449
+ if (!hasManifest && !hasPluginManifest) return null;
444
450
  return ['bun', 'run', 'scripts/lint-packaging.ts'];
445
451
  },
446
452
  tip: (c) =>
@@ -458,6 +464,18 @@ const ALL_CHECKS: Check[] = [
458
464
  tip: (c) =>
459
465
  `Remove the flagged SDK-coupling shortcut. See ${c.bold('scripts/check-framework-antipatterns.ts')} for rule rationale.`,
460
466
  },
467
+ {
468
+ name: 'Dependency Specifiers',
469
+ flag: '--no-dep-specifiers',
470
+ canFix: false,
471
+ // Rejects floating specifiers (latest/*/dist-tags) in package.json + the
472
+ // bun.lock workspaces map (#246). Static and local — no network, no git —
473
+ // so it runs in the default and --fast passes. Shipped to consumers via
474
+ // package.json `files:`; reads package.json/bun.lock directly, no guard.
475
+ getCommand: () => ['bun', 'run', 'scripts/check-dependency-specifiers.ts'],
476
+ tip: (c) =>
477
+ `Pin the flagged dep to a concrete range; after ${c.bold('bun update --latest')} run a plain ${c.bold('bun install')} to reconcile ${c.bold('bun.lock')}.`,
478
+ },
461
479
  {
462
480
  name: 'Open-Indexed Interfaces',
463
481
  flag: '--no-open-index',
@@ -32,6 +32,15 @@
32
32
  * `createWorkerHandler()` (src/index.ts, src/worker.ts) and manifest
33
33
  * `display_name` must equal the unscoped package name; a partial
34
34
  * `name`/`title` pair warns without failing (issue #231).
35
+ * 10. Plugin marketplace manifests (`.claude-plugin/plugin.json`,
36
+ * `.codex-plugin/plugin.json`, `.codex-plugin/mcp.json`): non-empty
37
+ * descriptions, and identity/install correctness — display fields
38
+ * (`name`, server key, `interface.displayName`) carry the unscoped machine
39
+ * name while the `npx -y` install arg carries the full `package.json`
40
+ * name (scoped if scoped). An unscoped install arg for a scoped package
41
+ * is a guaranteed install 404. Gated by `devcheck.config.json`
42
+ * `packaging.pluginManifests` (default on); each manifest is skipped
43
+ * cleanly when absent (issue #240).
35
44
  *
36
45
  * Every check skips cleanly when its input is absent — consumers who deleted
37
46
  * `manifest.json` for an HTTP-only deploy, or who haven't built a bundle,
@@ -374,6 +383,128 @@ export function checkManifestIdentity(manifest: Manifest, unscopedName: string):
374
383
  return [];
375
384
  }
376
385
 
386
+ /** Parsed plugin marketplace manifests; an absent manifest is `undefined`. */
387
+ export interface PluginManifestInputs {
388
+ claudePlugin?: unknown;
389
+ codexMcp?: unknown;
390
+ codexPlugin?: unknown;
391
+ }
392
+
393
+ const isRecord = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null;
394
+ const isNonEmptyString = (v: unknown): boolean => typeof v === 'string' && v.trim().length > 0;
395
+
396
+ /** The `npx -y <pkg>` install target is the arg after the `-y` flag (args[1]). */
397
+ function installArg(entry: Record<string, unknown>): unknown {
398
+ return Array.isArray(entry.args) ? entry.args[1] : undefined;
399
+ }
400
+
401
+ /**
402
+ * Check 10: plugin marketplace manifests. Display fields (`name`, server key,
403
+ * `interface.displayName`) must equal the unscoped machine name; the install
404
+ * arg must equal the full `package.json` name (the real `npx` target). Empty
405
+ * descriptions ship blank marketplace cards. Each manifest is validated only
406
+ * when present, so HTTP-only and non-plugin consumers are unaffected. The
407
+ * caller gates the whole check on `packaging.pluginManifests`.
408
+ */
409
+ export function checkPluginManifests(
410
+ inputs: PluginManifestInputs,
411
+ unscopedName: string,
412
+ fullName: string,
413
+ ): string[] {
414
+ const errors: string[] = [];
415
+ const optOut =
416
+ '(or set "packaging": { "pluginManifests": false } in devcheck.config.json to opt out)';
417
+
418
+ // ── .claude-plugin/plugin.json ──
419
+ const claude = inputs.claudePlugin;
420
+ if (isRecord(claude)) {
421
+ const f = '.claude-plugin/plugin.json';
422
+ if (!isNonEmptyString(claude.description)) {
423
+ errors.push(`${f} "description" is empty — populate it ${optOut}`);
424
+ }
425
+ if (claude.name !== unscopedName) {
426
+ errors.push(
427
+ `${f} "name" is "${String(claude.name)}" — must equal the unscoped package name "${unscopedName}"`,
428
+ );
429
+ }
430
+ const servers = claude.mcpServers;
431
+ if (isRecord(servers)) {
432
+ if (!(unscopedName in servers)) {
433
+ errors.push(
434
+ `${f} mcpServers has no "${unscopedName}" entry — the server key must be the unscoped package name`,
435
+ );
436
+ }
437
+ const entry = servers[unscopedName];
438
+ if (isRecord(entry) && installArg(entry) !== fullName) {
439
+ errors.push(
440
+ `${f} mcpServers["${unscopedName}"] install arg is "${String(installArg(entry))}" — ` +
441
+ `must be the full package name "${fullName}" (the npx -y target; an unscoped arg for a scoped package 404s)`,
442
+ );
443
+ }
444
+ }
445
+ }
446
+
447
+ // ── .codex-plugin/plugin.json ──
448
+ const codex = inputs.codexPlugin;
449
+ if (isRecord(codex)) {
450
+ const f = '.codex-plugin/plugin.json';
451
+ if (!isNonEmptyString(codex.description)) {
452
+ errors.push(`${f} "description" is empty — populate it ${optOut}`);
453
+ }
454
+ if (codex.name !== unscopedName) {
455
+ errors.push(
456
+ `${f} "name" is "${String(codex.name)}" — must equal the unscoped package name "${unscopedName}"`,
457
+ );
458
+ }
459
+ const iface = codex.interface;
460
+ if (isRecord(iface)) {
461
+ if (iface.displayName !== unscopedName) {
462
+ errors.push(
463
+ `${f} interface.displayName is "${String(iface.displayName)}" — must equal the unscoped package name "${unscopedName}"`,
464
+ );
465
+ }
466
+ if (!isNonEmptyString(iface.shortDescription)) {
467
+ errors.push(`${f} interface.shortDescription is empty — populate it ${optOut}`);
468
+ }
469
+ if (!isNonEmptyString(iface.longDescription)) {
470
+ errors.push(`${f} interface.longDescription is empty — populate it ${optOut}`);
471
+ }
472
+ } else {
473
+ errors.push(
474
+ `${f} is missing the "interface" object (displayName / shortDescription / longDescription)`,
475
+ );
476
+ }
477
+ }
478
+
479
+ // ── .codex-plugin/mcp.json ──
480
+ const codexMcp = inputs.codexMcp;
481
+ if (isRecord(codexMcp)) {
482
+ const f = '.codex-plugin/mcp.json';
483
+ if (!(unscopedName in codexMcp)) {
484
+ errors.push(
485
+ `${f} has no "${unscopedName}" server entry — the server key must be the unscoped package name`,
486
+ );
487
+ }
488
+ const entry = codexMcp[unscopedName];
489
+ if (isRecord(entry) && installArg(entry) !== fullName) {
490
+ errors.push(
491
+ `${f} "${unscopedName}" install arg is "${String(installArg(entry))}" — ` +
492
+ `must be the full package name "${fullName}" (the npx -y target)`,
493
+ );
494
+ }
495
+ }
496
+
497
+ return errors;
498
+ }
499
+
500
+ /** Read `packaging.pluginManifests` from devcheck.config.json; default on. */
501
+ function pluginManifestsEnabled(): boolean {
502
+ const cfg = tryReadJson<{ packaging?: { pluginManifests?: boolean } }>(
503
+ resolve('devcheck.config.json'),
504
+ );
505
+ return cfg?.packaging?.pluginManifests ?? true;
506
+ }
507
+
377
508
  async function main(): Promise<void> {
378
509
  const errors: string[] = [];
379
510
  const warnings: string[] = [];
@@ -496,6 +627,27 @@ async function main(): Promise<void> {
496
627
  }
497
628
  }
498
629
 
630
+ // ── Plugin marketplace manifests (check 10) ──
631
+ if (unscopedName && pkg?.name) {
632
+ if (pluginManifestsEnabled()) {
633
+ errors.push(
634
+ ...checkPluginManifests(
635
+ {
636
+ claudePlugin: tryReadJson(resolve('.claude-plugin/plugin.json')),
637
+ codexPlugin: tryReadJson(resolve('.codex-plugin/plugin.json')),
638
+ codexMcp: tryReadJson(resolve('.codex-plugin/mcp.json')),
639
+ },
640
+ unscopedName,
641
+ pkg.name,
642
+ ),
643
+ );
644
+ } else {
645
+ notes.push(
646
+ 'Plugin-manifest checks disabled via devcheck.config.json packaging.pluginManifests.',
647
+ );
648
+ }
649
+ }
650
+
499
651
  for (const note of notes) console.log(note);
500
652
  for (const warning of warnings) console.warn(` ⚠ ${warning}`);
501
653
 
@@ -23,7 +23,7 @@ All parsers are **Tier 3** — lazy-load their peer dependency on first call. Al
23
23
  |:-------|:----------|
24
24
  | `parse` | `<T = unknown>(yamlString, context?) -> Promise<T>` |
25
25
 
26
- Uses `js-yaml` `DEFAULT_SCHEMA`. Throws `ConfigurationError` if dep missing, `ValidationError` on empty/malformed input.
26
+ Uses `js-yaml` (v5) `YAML11_SCHEMA` (YAML 1.1 load semantics). Throws `ConfigurationError` if dep missing, `ValidationError` on empty/malformed input.
27
27
 
28
28
  ```ts
29
29
  const config = await yamlParser.parse<ServerConfig>(yamlString);
@@ -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.8"
7
+ version: "2.9"
8
8
  audience: external
9
9
  type: workflow
10
10
  ---
@@ -169,20 +169,22 @@ Never hand-edit `CHANGELOG.md` when using this pattern — it's a build artifact
169
169
 
170
170
  ### 10. Plugin Metadata (Codex / Claude Code)
171
171
 
172
+ `lint:packaging` (run by `devcheck`) now enforces the high-value subset automatically when these manifests are present: non-empty descriptions, and identity/install correctness — display fields (`name`, server key, `interface.displayName`) must be the **unscoped** machine name, while the `npx -y` install arg must be the full `package.json` `name` (scoped if scoped). Opt out per project with `"packaging": { "pluginManifests": false }` in `devcheck.config.json`. The checks below cover the fields the gate doesn't (version / repository / license sync, category, env vars).
173
+
172
174
  If `.codex-plugin/plugin.json` exists, verify it's populated and in sync with `package.json` and `server.json`:
173
175
 
174
- - `name` matches `package.json` `name`
176
+ - `name` is the unscoped `package.json` `name` (display identity is the machine name on every surface)
175
177
  - `version` matches `package.json` `version`
176
178
  - `description` matches `package.json` `description`
177
179
  - `repository` matches `package.json` `repository.url`
178
180
  - `license` matches `package.json` `license`
179
- - `interface.displayName` = `package.json` `name`
181
+ - `interface.displayName` is the unscoped `package.json` `name`
180
182
  - `interface.shortDescription` matches `package.json` `description`
181
183
  - `interface.category` is set to a meaningful category
182
184
 
183
- If `.codex-plugin/mcp.json` exists, verify the server name key matches `package.json` `name` and env vars include any required API keys from the server config schema.
185
+ If `.codex-plugin/mcp.json` exists, verify the server-name key is the unscoped `package.json` `name`, the `npx -y` install arg is the full `package.json` `name`, and env vars include any required API keys from the server config schema.
184
186
 
185
- If `.claude-plugin/plugin.json` exists, apply the same checks: `name`, `version`, `description`, `repository`, `license` from `package.json`. Verify the inline `mcpServers` entry key matches `package.json` `name` and env vars include any required API keys.
187
+ If `.claude-plugin/plugin.json` exists, apply the same checks: `name` (unscoped), `version`, `description`, `repository`, `license` from `package.json`. Verify the inline `mcpServers` entry key is the unscoped name, its `npx -y` install arg is the full `package.json` `name`, and env vars include any required API keys.
186
188
 
187
189
  ### 11. MCPB Bundling Artifacts
188
190
 
@@ -21,7 +21,6 @@ Framework badge ← solo spotlight row — `Built on @cy
21
21
  ## Running the server ← dev, production, Workers/Docker
22
22
  ## Project structure ← directory/purpose table
23
23
  ## Development guide ← link to CLAUDE.md/AGENTS.md, key rules
24
- ## Contributing ← brief
25
24
  ## License ← one line
26
25
  ```
27
26
 
@@ -484,19 +483,6 @@ See [`CLAUDE.md`/`AGENTS.md`](./CLAUDE.md) for development guidelines and archit
484
483
  - Wrap external API calls: validate raw → normalize to domain type → return output schema; never fabricate missing fields
485
484
  ```
486
485
 
487
- ### Contributing
488
-
489
- ```markdown
490
- ## Contributing
491
-
492
- Issues and pull requests are welcome. Run checks and tests before submitting:
493
-
494
- \`\`\`sh
495
- bun run devcheck
496
- bun run test
497
- \`\`\`
498
- ```
499
-
500
486
  ### License
501
487
 
502
488
  One line referencing the LICENSE file.
@@ -12,5 +12,8 @@
12
12
  },
13
13
  "outdated": {
14
14
  "allowlist": []
15
+ },
16
+ "packaging": {
17
+ "pluginManifests": true
15
18
  }
16
19
  }
@@ -1,8 +0,0 @@
1
- {"level":40,"time":1781913320360,"env":"testing","version":"0.10.8","pid":20168,"transport":"http","requestId":"WSD7Y-6FNIU","timestamp":"2026-06-19T23:55:20.360Z","operation":"TransportManager.start","component":"HttpTransportSetup","msg":"MCP_ALLOWED_ORIGINS is not set — CORS is wildcard for CLI clients; browser Origin headers are restricted to loopback. Set MCP_ALLOWED_ORIGINS for production deployments accepting remote browser origins."}
2
- {"level":40,"time":1781913322185,"env":"testing","version":"0.10.8","pid":20168,"component":"HttpTransport","requestId":"Y3XD8-YV6FO","timestamp":"2026-06-19T23:55:22.184Z","operation":"HttpRpcRequest","sessionId":"not-a-real-session-1781913322182","msg":"Session validation failed - invalid or hijacked session"}
3
- {"level":50,"time":1781913329461,"env":"testing","version":"0.0.0-test","pid":20387,"requestId":"5JUEU-0VDJE","timestamp":"2026-06-19T23:55:29.461Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"da08fbd6c9656cf3f0b23b60935dfa84e7923c134e40b457be98bda811ed2efc","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"da08fbd6c9656cf3f0b23b60935dfa84e7923c134e40b457be98bda811ed2efc","toolName":"scoped_echo","requestId":"5JUEU-0VDJE","timestamp":"2026-06-19T23:55:29.461Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (file:///Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:54)\n at withRequiredScopes (file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283:17\n at McpServer.executeToolHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:233:42)\n at file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43\n at async wrappedHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js:125:32)"},"stack":"McpError: Insufficient permissions.\n at ErrorHandler.handleError (file:///Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:19)\n at file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:332:26\n at McpServer.executeToolHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:233:42)\n at file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43\n at async wrappedHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js:125:32)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
4
- {"level":50,"time":1781913329472,"env":"testing","version":"0.0.0-test","pid":20387,"requestId":"R7MGX-J7GDG","timestamp":"2026-06-19T23:55:29.472Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"d784c7b4dd86d4f145dee03c30c3a68ae51a8606097461f46ebb719cfd85d619","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"d784c7b4dd86d4f145dee03c30c3a68ae51a8606097461f46ebb719cfd85d619","toolName":"scoped_echo","requestId":"R7MGX-J7GDG","timestamp":"2026-06-19T23:55:29.472Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (file:///Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:54)\n at withRequiredScopes (file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283:17\n at McpServer.executeToolHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:233:42)\n at file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43\n at async wrappedHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js:125:32)"},"stack":"McpError: Insufficient permissions.\n at ErrorHandler.handleError (file:///Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:19)\n at file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:332:26\n at McpServer.executeToolHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:233:42)\n at file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43\n at async wrappedHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js:125:32)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
5
- {"level":40,"time":1781913343027,"env":"testing","version":"0.10.8","pid":20630,"transport":"http","requestId":"GF046-IMNJH","timestamp":"2026-06-19T23:55:43.027Z","operation":"TransportManager.start","component":"HttpTransportSetup","msg":"MCP_ALLOWED_ORIGINS is not set — CORS is wildcard for CLI clients; browser Origin headers are restricted to loopback. Set MCP_ALLOWED_ORIGINS for production deployments accepting remote browser origins."}
6
- {"level":40,"time":1781913344790,"env":"testing","version":"0.10.8","pid":20630,"component":"HttpTransport","requestId":"Z5YGA-X0LTR","timestamp":"2026-06-19T23:55:44.790Z","operation":"HttpRpcRequest","sessionId":"not-a-real-session-1781913344790","msg":"Session validation failed - invalid or hijacked session"}
7
- {"level":50,"time":1781913345263,"env":"testing","version":"0.0.0-test","pid":20668,"requestId":"4BROD-WQSCR","timestamp":"2026-06-19T23:55:45.262Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"25c684941147a157673abe23c7519112dbe02be9c0262c28478ff1fc436ebf1b","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"25c684941147a157673abe23c7519112dbe02be9c0262c28478ff1fc436ebf1b","toolName":"scoped_echo","requestId":"4BROD-WQSCR","timestamp":"2026-06-19T23:55:45.262Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:332:26)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
8
- {"level":50,"time":1781913345270,"env":"testing","version":"0.0.0-test","pid":20668,"requestId":"3OCQP-0V3O2","timestamp":"2026-06-19T23:55:45.270Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"10e3337a2b2dbc0eb5bf700bf8e875b1b829ebb783a65abcf06d9ffbf3006536","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"10e3337a2b2dbc0eb5bf700bf8e875b1b829ebb783a65abcf06d9ffbf3006536","toolName":"scoped_echo","requestId":"3OCQP-0V3O2","timestamp":"2026-06-19T23:55:45.270Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:332:26)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
@@ -1,4 +0,0 @@
1
- {"level":50,"time":1781913329461,"env":"testing","version":"0.0.0-test","pid":20387,"requestId":"5JUEU-0VDJE","timestamp":"2026-06-19T23:55:29.461Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"da08fbd6c9656cf3f0b23b60935dfa84e7923c134e40b457be98bda811ed2efc","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"da08fbd6c9656cf3f0b23b60935dfa84e7923c134e40b457be98bda811ed2efc","toolName":"scoped_echo","requestId":"5JUEU-0VDJE","timestamp":"2026-06-19T23:55:29.461Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (file:///Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:54)\n at withRequiredScopes (file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283:17\n at McpServer.executeToolHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:233:42)\n at file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43\n at async wrappedHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js:125:32)"},"stack":"McpError: Insufficient permissions.\n at ErrorHandler.handleError (file:///Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:19)\n at file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:332:26\n at McpServer.executeToolHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:233:42)\n at file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43\n at async wrappedHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js:125:32)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
2
- {"level":50,"time":1781913329472,"env":"testing","version":"0.0.0-test","pid":20387,"requestId":"R7MGX-J7GDG","timestamp":"2026-06-19T23:55:29.472Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"d784c7b4dd86d4f145dee03c30c3a68ae51a8606097461f46ebb719cfd85d619","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"d784c7b4dd86d4f145dee03c30c3a68ae51a8606097461f46ebb719cfd85d619","toolName":"scoped_echo","requestId":"R7MGX-J7GDG","timestamp":"2026-06-19T23:55:29.472Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (file:///Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:54)\n at withRequiredScopes (file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283:17\n at McpServer.executeToolHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:233:42)\n at file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43\n at async wrappedHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js:125:32)"},"stack":"McpError: Insufficient permissions.\n at ErrorHandler.handleError (file:///Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:19)\n at file:///Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:332:26\n at McpServer.executeToolHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:233:42)\n at file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43\n at async wrappedHandler (file:///Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/index.js:125:32)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
3
- {"level":50,"time":1781913345263,"env":"testing","version":"0.0.0-test","pid":20668,"requestId":"4BROD-WQSCR","timestamp":"2026-06-19T23:55:45.262Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"25c684941147a157673abe23c7519112dbe02be9c0262c28478ff1fc436ebf1b","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"25c684941147a157673abe23c7519112dbe02be9c0262c28478ff1fc436ebf1b","toolName":"scoped_echo","requestId":"4BROD-WQSCR","timestamp":"2026-06-19T23:55:45.262Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["tool:other:read"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:332:26)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
4
- {"level":50,"time":1781913345270,"env":"testing","version":"0.0.0-test","pid":20668,"requestId":"3OCQP-0V3O2","timestamp":"2026-06-19T23:55:45.270Z","operation":"HandleToolRequest","critical":false,"errorCode":-32005,"originalErrorType":"McpError","finalErrorType":"McpError","sessionId":"10e3337a2b2dbc0eb5bf700bf8e875b1b829ebb783a65abcf06d9ffbf3006536","toolName":"scoped_echo","tenantId":"authz-tenant","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"errorData":{"sessionId":"10e3337a2b2dbc0eb5bf700bf8e875b1b829ebb783a65abcf06d9ffbf3006536","toolName":"scoped_echo","requestId":"3OCQP-0V3O2","timestamp":"2026-06-19T23:55:45.270Z","tenantId":"authz-tenant","operation":"HandleToolRequest","auth":{"sub":"authz-user","scopes":["openid","email","profile","offline_access"],"clientId":"authz-client","tenantId":"authz-tenant","token":"[REDACTED]"},"originalErrorName":"McpError","originalMessage":"Insufficient permissions.","originalStack":"McpError: Insufficient permissions.\n at forbidden (/Users/casey/Developer/github/mcp-ts-core/dist/types-global/errors.js:84:58)\n at withRequiredScopes (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/transports/auth/lib/authUtils.js:68:15)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:283:17)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)"},"stack":"McpError: Insufficient permissions.\n at handleError (/Users/casey/Developer/github/mcp-ts-core/dist/utils/internal/error-handler/errorHandler.js:170:23)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/dist/mcp-server/tools/utils/toolHandlerFactory.js:332:26)\n at executeToolHandler (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:231:34)\n at <anonymous> (/Users/casey/Developer/github/mcp-ts-core/node_modules/@modelcontextprotocol/sdk/dist/esm/server/mcp.js:126:43)\n at processTicksAndRejections (native:7:39)","msg":"Error in tool:scoped_echo: Insufficient permissions."}
File without changes