@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.
- package/AGENTS.md +2 -2
- package/CLAUDE.md +2 -2
- package/README.md +1 -1
- package/changelog/0.11.x/0.11.4.md +67 -0
- package/changelog/0.11.x/0.11.5.md +28 -0
- package/changelog/template.md +55 -16
- package/dist/linter/rules/enrichment-rules.d.ts +3 -2
- package/dist/linter/rules/enrichment-rules.d.ts.map +1 -1
- package/dist/linter/rules/enrichment-rules.js +35 -7
- package/dist/linter/rules/enrichment-rules.js.map +1 -1
- package/dist/linter/rules/format-parity-rules.d.ts +8 -4
- package/dist/linter/rules/format-parity-rules.d.ts.map +1 -1
- package/dist/linter/rules/format-parity-rules.js +72 -20
- package/dist/linter/rules/format-parity-rules.js.map +1 -1
- package/dist/linter/rules/index.d.ts +1 -1
- package/dist/linter/rules/index.d.ts.map +1 -1
- package/dist/linter/rules/index.js +1 -1
- package/dist/linter/rules/index.js.map +1 -1
- package/dist/linter/rules/prompt-rules.d.ts.map +1 -1
- package/dist/linter/rules/prompt-rules.js +6 -3
- package/dist/linter/rules/prompt-rules.js.map +1 -1
- package/dist/linter/rules/resource-rules.d.ts.map +1 -1
- package/dist/linter/rules/resource-rules.js +11 -5
- package/dist/linter/rules/resource-rules.js.map +1 -1
- package/dist/linter/rules/schema-rules.d.ts +21 -2
- package/dist/linter/rules/schema-rules.d.ts.map +1 -1
- package/dist/linter/rules/schema-rules.js +110 -2
- package/dist/linter/rules/schema-rules.js.map +1 -1
- package/dist/linter/rules/tool-rules.d.ts.map +1 -1
- package/dist/linter/rules/tool-rules.js +11 -5
- package/dist/linter/rules/tool-rules.js.map +1 -1
- package/dist/logs/combined.log +22 -16
- package/dist/logs/error.log +18 -12
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +44 -10
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js.map +1 -1
- package/dist/mcp-server/transports/http/httpTransport.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/httpTransport.js +67 -4
- package/dist/mcp-server/transports/http/httpTransport.js.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.d.ts +25 -2
- package/dist/mcp-server/transports/http/sessionStore.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/sessionStore.js +58 -4
- package/dist/mcp-server/transports/http/sessionStore.js.map +1 -1
- package/dist/testing/fuzz.d.ts.map +1 -1
- package/dist/testing/fuzz.js +57 -2
- package/dist/testing/fuzz.js.map +1 -1
- package/dist/utils/overflow/outlineOnOverflow.d.ts +4 -3
- package/dist/utils/overflow/outlineOnOverflow.d.ts.map +1 -1
- package/dist/utils/overflow/outlineOnOverflow.js +27 -8
- package/dist/utils/overflow/outlineOnOverflow.js.map +1 -1
- package/package.json +1 -1
- package/scripts/tree.ts +9 -3
- package/skills/api-canvas/SKILL.md +2 -2
- package/skills/api-context/SKILL.md +2 -1
- package/skills/api-linter/SKILL.md +59 -5
- package/skills/api-testing/SKILL.md +3 -1
- package/skills/design-mcp-server/SKILL.md +4 -2
- package/skills/git-wrapup/SKILL.md +3 -3
- package/skills/setup/SKILL.md +6 -3
- package/skills/techniques/SKILL.md +1 -1
- package/skills/techniques/references/outline-on-overflow.md +3 -1
- package/templates/.github/CODE_OF_CONDUCT.md +28 -0
- package/templates/.github/CONTRIBUTING.md +50 -0
- package/templates/.github/SECURITY.md +24 -0
- package/templates/Dockerfile +7 -2
- package/templates/_tsconfig.build.json +5 -0
- package/templates/_tsconfig.json +4 -2
- package/templates/changelog/template.md +55 -16
- package/templates/package.json +1 -0
- package/templates/tests/prompts/echo.prompt.test.ts +4 -3
- package/templates/tests/resources/echo.resource.test.ts +11 -3
- package/templates/tests/smoke/definitions.smoke.test.ts +8 -4
- 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." -->
|
package/templates/Dockerfile
CHANGED
|
@@ -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 \
|
package/templates/_tsconfig.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": "@cyanheads/mcp-ts-core/tsconfig.base.json",
|
|
3
3
|
"compilerOptions": {
|
|
4
|
-
"rootDir": "
|
|
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
|
|
9
|
-
#
|
|
10
|
-
#
|
|
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.
|
|
28
|
-
#
|
|
29
|
-
#
|
|
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.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
package/templates/package.json
CHANGED
|
@@ -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
|
|
12
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
24
|
+
echoResource.params!.parse({ message: 'smoke' }),
|
|
23
25
|
ctx,
|
|
24
26
|
);
|
|
25
|
-
const promptMessages = echoPrompt.generate(
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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));
|