@cyanheads/mcp-ts-core 0.11.3 → 0.11.5

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.
Files changed (73) hide show
  1. package/AGENTS.md +2 -2
  2. package/CLAUDE.md +2 -2
  3. package/README.md +1 -1
  4. package/changelog/0.11.x/0.11.4.md +67 -0
  5. package/changelog/0.11.x/0.11.5.md +28 -0
  6. package/changelog/template.md +55 -16
  7. package/dist/linter/rules/enrichment-rules.d.ts +3 -2
  8. package/dist/linter/rules/enrichment-rules.d.ts.map +1 -1
  9. package/dist/linter/rules/enrichment-rules.js +35 -7
  10. package/dist/linter/rules/enrichment-rules.js.map +1 -1
  11. package/dist/linter/rules/format-parity-rules.d.ts +8 -4
  12. package/dist/linter/rules/format-parity-rules.d.ts.map +1 -1
  13. package/dist/linter/rules/format-parity-rules.js +72 -20
  14. package/dist/linter/rules/format-parity-rules.js.map +1 -1
  15. package/dist/linter/rules/index.d.ts +1 -1
  16. package/dist/linter/rules/index.d.ts.map +1 -1
  17. package/dist/linter/rules/index.js +1 -1
  18. package/dist/linter/rules/index.js.map +1 -1
  19. package/dist/linter/rules/prompt-rules.d.ts.map +1 -1
  20. package/dist/linter/rules/prompt-rules.js +6 -3
  21. package/dist/linter/rules/prompt-rules.js.map +1 -1
  22. package/dist/linter/rules/resource-rules.d.ts.map +1 -1
  23. package/dist/linter/rules/resource-rules.js +11 -5
  24. package/dist/linter/rules/resource-rules.js.map +1 -1
  25. package/dist/linter/rules/schema-rules.d.ts +21 -2
  26. package/dist/linter/rules/schema-rules.d.ts.map +1 -1
  27. package/dist/linter/rules/schema-rules.js +110 -2
  28. package/dist/linter/rules/schema-rules.js.map +1 -1
  29. package/dist/linter/rules/tool-rules.d.ts.map +1 -1
  30. package/dist/linter/rules/tool-rules.js +11 -5
  31. package/dist/linter/rules/tool-rules.js.map +1 -1
  32. package/dist/logs/combined.log +22 -16
  33. package/dist/logs/error.log +18 -12
  34. package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
  35. package/dist/mcp-server/tools/utils/toolHandlerFactory.js +44 -10
  36. package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
  37. package/dist/mcp-server/transports/http/httpTransport.d.ts.map +1 -1
  38. package/dist/mcp-server/transports/http/httpTransport.js +67 -4
  39. package/dist/mcp-server/transports/http/httpTransport.js.map +1 -1
  40. package/dist/mcp-server/transports/http/sessionStore.d.ts +25 -2
  41. package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -1
  42. package/dist/mcp-server/transports/http/sessionStore.js +58 -4
  43. package/dist/mcp-server/transports/http/sessionStore.js.map +1 -1
  44. package/dist/testing/fuzz.d.ts.map +1 -1
  45. package/dist/testing/fuzz.js +57 -2
  46. package/dist/testing/fuzz.js.map +1 -1
  47. package/dist/utils/overflow/outlineOnOverflow.d.ts +4 -3
  48. package/dist/utils/overflow/outlineOnOverflow.d.ts.map +1 -1
  49. package/dist/utils/overflow/outlineOnOverflow.js +27 -8
  50. package/dist/utils/overflow/outlineOnOverflow.js.map +1 -1
  51. package/package.json +1 -1
  52. package/scripts/tree.ts +9 -3
  53. package/skills/api-canvas/SKILL.md +2 -2
  54. package/skills/api-context/SKILL.md +2 -1
  55. package/skills/api-linter/SKILL.md +59 -5
  56. package/skills/api-testing/SKILL.md +3 -1
  57. package/skills/design-mcp-server/SKILL.md +4 -2
  58. package/skills/git-wrapup/SKILL.md +3 -3
  59. package/skills/setup/SKILL.md +6 -3
  60. package/skills/techniques/SKILL.md +1 -1
  61. package/skills/techniques/references/outline-on-overflow.md +3 -1
  62. package/templates/.github/CODE_OF_CONDUCT.md +28 -0
  63. package/templates/.github/CONTRIBUTING.md +50 -0
  64. package/templates/.github/SECURITY.md +24 -0
  65. package/templates/Dockerfile +7 -2
  66. package/templates/_tsconfig.build.json +5 -0
  67. package/templates/_tsconfig.json +4 -2
  68. package/templates/changelog/template.md +55 -16
  69. package/templates/package.json +1 -0
  70. package/templates/tests/prompts/echo.prompt.test.ts +4 -3
  71. package/templates/tests/resources/echo.resource.test.ts +11 -3
  72. package/templates/tests/smoke/definitions.smoke.test.ts +8 -4
  73. package/templates/tests/tools/echo.tool.test.ts +23 -4
@@ -0,0 +1,24 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Security fixes land on the latest release of `{{PACKAGE_NAME}}`. Older
6
+ versions are not patched — upgrade to the current release.
7
+
8
+ ## Reporting a Vulnerability
9
+
10
+ Please do not open a public issue for security reports. Instead:
11
+
12
+ <!-- GitHub's private reporting is off by default. Turn it on under
13
+ Settings → Code security → Private vulnerability reporting. -->
14
+
15
+ - Report privately via GitHub: **Security** tab → **Report a vulnerability**, or
16
+ - Email **[your-contact-email]**
17
+
18
+ Include a minimal reproduction where possible, with any API keys, tokens, or
19
+ credentials redacted — a placeholder is enough to show the shape. You'll
20
+ receive an acknowledgment, and credit in the release notes if the report leads
21
+ to a fix (unless you prefer otherwise).
22
+
23
+ <!-- If you want to commit to a response window, say so above — e.g.
24
+ "You'll receive an acknowledgment within a few days." -->
@@ -51,8 +51,13 @@ COPY package.json bun.lock ./
51
51
 
52
52
  # Install only production dependencies, ignoring any lifecycle scripts (like 'prepare')
53
53
  # that are not needed in the final production image.
54
+ # `--omit=peer` drops the framework's optional peer tiers (test runner, service
55
+ # SDKs, parsers) that Bun would otherwise auto-install. Anything this server
56
+ # actually imports belongs in its own `dependencies`, so nothing needed at
57
+ # runtime is lost. The OTEL step below carries the same flag — without it, that
58
+ # install re-resolves the graph and pulls every optional peer back in.
54
59
  RUN --mount=type=cache,target=/root/.bun/install/cache \
55
- bun install --production --frozen-lockfile --ignore-scripts
60
+ bun install --production --omit=peer --frozen-lockfile --ignore-scripts
56
61
 
57
62
  # Conditionally install OpenTelemetry optional peer dependencies (Tier 3).
58
63
  # These are not bundled by default to keep the base image lean. Enable at build time
@@ -60,7 +65,7 @@ RUN --mount=type=cache,target=/root/.bun/install/cache \
60
65
  ARG OTEL_ENABLED=true
61
66
  RUN --mount=type=cache,target=/root/.bun/install/cache \
62
67
  if [ "$OTEL_ENABLED" = "true" ]; then \
63
- bun add --omit=dev --ignore-scripts @hono/otel \
68
+ bun add --omit=dev --omit=peer --ignore-scripts @hono/otel \
64
69
  @opentelemetry/instrumentation-http \
65
70
  @opentelemetry/exporter-metrics-otlp-http \
66
71
  @opentelemetry/exporter-trace-otlp-http \
@@ -1,4 +1,9 @@
1
1
  {
2
2
  "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "noEmit": false
6
+ },
7
+ "include": ["src/**/*"],
3
8
  "exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
4
9
  }
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "extends": "@cyanheads/mcp-ts-core/tsconfig.base.json",
3
3
  "compilerOptions": {
4
- "rootDir": "src",
4
+ "rootDir": ".",
5
5
  "outDir": "dist",
6
+ "noEmit": true,
7
+ "tsBuildInfoFile": ".tsbuildinfo",
6
8
  "paths": {
7
9
  "@/*": ["./src/*"]
8
10
  }
9
11
  },
10
- "include": ["src/**/*"],
12
+ "include": ["src/**/*", "tests/**/*"],
11
13
  "exclude": ["node_modules", "dist"]
12
14
  }
@@ -4,10 +4,11 @@
4
4
  # to author a new release. Set that file's H1 to `# <version> — YYYY-MM-DD`
5
5
  # with a concrete date.
6
6
 
7
- # Required. One-line GitHub Release-style headline. 350 character cap.
8
- # Default short and scannable. Don't pad, don't stitch unrelated changes with
9
- # semicolons — pick the headline. Quotes required: unquoted YAML treats `: `
10
- # inside the value as a key separator and fails GitHub's strict parser.
7
+ # Required. One-line GitHub Release-style headline. 350 character cap — a
8
+ # ceiling, not a target. Default short and scannable. Don't pad, don't stitch
9
+ # unrelated changes with commas/semicolons into an inventory — pick the
10
+ # headline, like a tag's theme line. Quotes required: unquoted YAML treats
11
+ # `: ` inside the value as a key separator and fails GitHub's strict parser.
11
12
  summary: ""
12
13
 
13
14
  # Set `true` when consumers must change code to upgrade: API removals,
@@ -24,9 +25,10 @@ security: false
24
25
 
25
26
  # Optional free-form notes for maintenance agents processing this release.
26
27
  # Not rendered in CHANGELOG — consumed by agents running `maintenance` on
27
- # downstream servers. Use for adoption instructions that don't fit the
28
- # human-facing sections: new files to create, fields to populate, one-time
29
- # migration steps. Omit the field entirely when there's nothing to say.
28
+ # downstream servers. ADOPTION STEPS ONLY — new files to create, fields to
29
+ # populate, one-time migration steps. Never a second rendering of the body:
30
+ # if a body bullet already says it, name the bullet's symbol instead of
31
+ # re-explaining. Omit the field entirely when there's nothing to say.
30
32
  # agent-notes: |
31
33
  # <instructions for downstream maintenance agents>
32
34
  ---
@@ -41,17 +43,54 @@ security: false
41
43
  each bullet with the symbol or concept name in **bold** so they can skip
42
44
  what's irrelevant and zoom in on what's not.
43
45
 
44
- Tone: terse, fact-dense, not verbose. Default to one sentence per bullet —
45
- name the symbol, state what changed, stop. Use a second sentence only when
46
- it carries weight. If a bullet feels long, it is.
47
-
48
- Cut: mechanism walkthroughs (those belong in JSDoc, CLAUDE.md/AGENTS.md, or the
49
- relevant skill), ceremonial framings ("This release introduces…",
50
- backwards-compat paragraphs), file-by-file test enumerations, internal
51
- implementation notes. Prefer code/symbol names over English re-explanations.
46
+ Tone: terse, fact-dense, not verbose. Bullet shape: **symbol** + what
47
+ changed + at most one consumer-facing caveat. One sentence by default, two
48
+ when the second carries weight — a bullet past ~40 words or three sentences
49
+ is wrong. The depth lives one hop away: the linked issue carries the why,
50
+ the commit diff carries the how. The changelog names what changed and what
51
+ a consumer does about it; a reader who wants mechanism opens the link.
52
+
53
+ Model length on THIS guide, never on the previous entry — entries modeled
54
+ on entries compound.
55
+
56
+ Cut (each has shipped as a wall of text; these are the cruft):
57
+ - History/justification narration — how the bug worked, why the old
58
+ behavior was wrong. One short clause at most; the issue carries the story.
59
+ - Design-rationale defense — "chosen over Y because…", "guarding the
60
+ getter is not enough…". That is the author arguing with a reviewer;
61
+ reviewers read the PR, not the changelog.
62
+ - Defensive unchanged-clauses — "X is unchanged", "byte-identical to
63
+ <prev>". Keep one only where its absence would cause a real misread,
64
+ as a short parenthetical.
65
+ - Edge-case inventories — marker lists, not-flagged lists, escape tables.
66
+ Tests and the issue carry those.
67
+ - Mechanism walkthroughs (JSDoc, CLAUDE.md/AGENTS.md, or the relevant
68
+ skill own those), ceremonial framings ("This release introduces…"),
69
+ backwards-compat paragraphs, file-by-file test enumerations. Prefer
70
+ code/symbol names over English re-explanations.
71
+
72
+ Verified ≠ included: the every-claim-verified-from-the-diff rule bounds
73
+ the TRUTH of what you write, never the AMOUNT.
74
+
75
+ Example — same fact, right size:
76
+
77
+ TOO LONG: **`fetchWithTimeout`'s `timeoutMs` bounds the whole exchange**
78
+ (#341). `fetch` resolves once headers arrive and the deadline was
79
+ cleared as the helper returned, so a peer that answered promptly and
80
+ then stalled the stream held the request open indefinitely. A 2xx
81
+ carrying a body now comes back as a passthrough wrapper that disarms
82
+ the deadline when the body closes, errors, or is cancelled; …
83
+ [+90 more words of mechanism and edge cases]
84
+
85
+ RIGHT: **`fetchWithTimeout`'s `timeoutMs` now bounds the whole
86
+ exchange, not just the headers** (#341). A stalled body aborts with
87
+ the same `Timeout` error; the returned `Response` is a wrapper, so
88
+ identity assertions (`toBe(response)`) no longer hold.
52
89
 
53
90
  Narrative intro: skip by default. Add one short sentence only when the
54
- release theme genuinely needs framing the bullets can't carry.
91
+ release theme genuinely needs framing the bullets can't carry. When many
92
+ bullets share one upgrade consequence, state it ONCE — intro line or
93
+ agent-notes — never per bullet.
55
94
 
56
95
  Sections: Keep a Changelog order — Added, Changed, Deprecated, Removed,
57
96
  Fixed, Security. Include only sections with entries; delete the rest
@@ -70,6 +70,7 @@
70
70
  "@types/node": "26.1.1",
71
71
  "@vitest/coverage-istanbul": "4.1.10",
72
72
  "depcheck": "^1.4.7",
73
+ "fast-check": "^4.9.0",
73
74
  "ignore": "^7.0.6",
74
75
  "tsc-alias": "^1.9.1",
75
76
  "typescript": "^7.0.2",
@@ -7,9 +7,10 @@ import { describe, expect, it } from 'vitest';
7
7
  import { echoPrompt } from '@/mcp-server/prompts/definitions/echo.prompt.js';
8
8
 
9
9
  describe('echoPrompt', () => {
10
- it('generates a user message with the echoed text', () => {
11
- const args = echoPrompt.args.parse({ message: 'hello world' });
12
- const messages = echoPrompt.generate(args);
10
+ it('generates a user message with the echoed text', async () => {
11
+ const args = echoPrompt.args!.parse({ message: 'hello world' });
12
+ // `generate` may be async — await covers both shapes.
13
+ const messages = await echoPrompt.generate(args);
13
14
  expect(messages).toHaveLength(1);
14
15
  expect(messages[0]).toMatchObject({
15
16
  role: 'user',
@@ -10,13 +10,21 @@ import { echoResource } from '@/mcp-server/resources/definitions/echo.resource.j
10
10
  describe('echoResource', () => {
11
11
  it('echoes the message from params', async () => {
12
12
  const ctx = createMockContext();
13
- const params = echoResource.params.parse({ message: 'hello world' });
13
+ const params = echoResource.params!.parse({ message: 'hello world' });
14
14
  const result = await echoResource.handler(params, ctx);
15
15
  expect(result).toEqual({ message: 'hello world' });
16
16
  });
17
17
 
18
- it('lists available resources', () => {
19
- const listing = echoResource.list!();
18
+ it('lists available resources', async () => {
19
+ // `list` receives the SDK's request-handler extra, not a Context, and may be
20
+ // async — a minimal literal is enough for a listing that ignores it.
21
+ const extra = {
22
+ signal: new AbortController().signal,
23
+ requestId: 'test',
24
+ sendNotification: () => Promise.resolve(),
25
+ sendRequest: () => Promise.resolve({} as never),
26
+ };
27
+ const listing = await echoResource.list!(extra);
20
28
  expect(listing.resources).toHaveLength(1);
21
29
  expect(listing.resources[0]).toMatchObject({
22
30
  uri: 'echo://hello',
@@ -13,23 +13,27 @@ import { echoTool } from '@/mcp-server/tools/definitions/echo.tool.js';
13
13
 
14
14
  describe('scaffold definition smoke test', () => {
15
15
  it('executes the shipped tool, resource, and prompt definitions', async () => {
16
- const ctx = createMockContext();
16
+ // `echoTool` declares an error contract, so its handler wants a context
17
+ // typed against it — passing `errors` narrows what `createMockContext` returns.
18
+ const ctx = createMockContext({ errors: echoTool.errors });
17
19
  const toolResult = await echoTool.handler(
18
20
  echoTool.input.parse({ message: 'smoke' }),
19
21
  ctx,
20
22
  );
21
23
  const resourceResult = await echoResource.handler(
22
- echoResource.params.parse({ message: 'smoke' }),
24
+ echoResource.params!.parse({ message: 'smoke' }),
23
25
  ctx,
24
26
  );
25
- const promptMessages = echoPrompt.generate(echoPrompt.args.parse({ message: 'smoke' }));
27
+ const promptMessages = await echoPrompt.generate(
28
+ echoPrompt.args!.parse({ message: 'smoke' }),
29
+ );
26
30
  const appResult = await echoAppTool.handler(
27
31
  echoAppTool.input.parse({ message: 'smoke app' }),
28
32
  ctx,
29
33
  );
30
34
  const appContent = echoAppTool.format?.(appResult);
31
35
  const appHtml = await echoAppUiResource.handler(
32
- echoAppUiResource.params.parse({}),
36
+ echoAppUiResource.params!.parse({}),
33
37
  createMockContext({ uri: new URL('ui://template-echo-app/app.html') }),
34
38
  );
35
39
 
@@ -3,22 +3,41 @@
3
3
  * @module tests/tools/echo.tool.test
4
4
  */
5
5
 
6
+ import type { HandlerContext, ReasonOf } from '@cyanheads/mcp-ts-core';
6
7
  import { describe, expect, it } from 'vitest';
7
8
  import { createMockContext } from '@cyanheads/mcp-ts-core/testing';
8
9
  import { mcpTest } from '@cyanheads/mcp-ts-core/testing/vitest';
9
10
  import { echoTool } from '@/mcp-server/tools/definitions/echo.tool.js';
10
11
 
12
+ /**
13
+ * A tool that declares `errors[]` types its handler's `ctx` against that
14
+ * contract, so tests hand it a context built the same way — `createMockContext`
15
+ * narrows its return type from the `errors` it is given. Drop this if your tool
16
+ * declares no error contract; a bare `createMockContext()` is enough then.
17
+ */
18
+ type EchoContext = HandlerContext<ReasonOf<typeof echoTool.errors>>;
19
+
20
+ const echoContext = () => createMockContext({ errors: echoTool.errors });
21
+
11
22
  // ---------------------------------------------------------------------------
12
23
  // Fixture-based tests (mcpTest) — fresh ctx per test, no manual construction
13
24
  // ---------------------------------------------------------------------------
14
25
 
15
- mcpTest('echoTool: echoes the message back (fixture)', async ({ ctx }) => {
26
+ /** Function form, not a bare value — every test gets its own context. */
27
+ const echoTest = mcpTest.extend<{ ctx: EchoContext }>({
28
+ // biome-ignore lint/correctness/noEmptyPattern: vitest's fixture API requires a destructuring pattern as the first parameter
29
+ ctx: async ({}, use) => {
30
+ await use(echoContext());
31
+ },
32
+ });
33
+
34
+ echoTest('echoTool: echoes the message back (fixture)', async ({ ctx }) => {
16
35
  const input = echoTool.input.parse({ message: 'hello world' });
17
36
  const result = await echoTool.handler(input, ctx);
18
37
  expect(result).toEqual({ message: 'hello world' });
19
38
  });
20
39
 
21
- mcpTest('echoTool: output conforms to declared schema (fixture)', async ({ ctx }) => {
40
+ echoTest('echoTool: output conforms to declared schema (fixture)', async ({ ctx }) => {
22
41
  const input = echoTool.input.parse({ message: 'hello world' });
23
42
  const result = await echoTool.handler(input, ctx);
24
43
  expect(result).toEqual(expect.schemaMatching(echoTool.output));
@@ -30,14 +49,14 @@ mcpTest('echoTool: output conforms to declared schema (fixture)', async ({ ctx }
30
49
 
31
50
  describe('echoTool', () => {
32
51
  it('echoes the message back', async () => {
33
- const ctx = createMockContext();
52
+ const ctx = echoContext();
34
53
  const input = echoTool.input.parse({ message: 'hello world' });
35
54
  const result = await echoTool.handler(input, ctx);
36
55
  expect(result).toEqual({ message: 'hello world' });
37
56
  });
38
57
 
39
58
  it('output conforms to the declared output schema', async () => {
40
- const ctx = createMockContext();
59
+ const ctx = echoContext();
41
60
  const input = echoTool.input.parse({ message: 'hello world' });
42
61
  const result = await echoTool.handler(input, ctx);
43
62
  expect(result).toEqual(expect.schemaMatching(echoTool.output));