@cyanheads/mcp-ts-core 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -1
- package/README.md +6 -2
- package/changelog/0.9.x/0.9.1.md +41 -0
- package/changelog/0.9.x/0.9.2.md +55 -0
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/core/app.d.ts.map +1 -1
- package/dist/core/app.js +3 -0
- package/dist/core/app.js.map +1 -1
- package/dist/core/context.d.ts +6 -0
- package/dist/core/context.d.ts.map +1 -1
- package/dist/core/context.js +2 -0
- package/dist/core/context.js.map +1 -1
- package/dist/core/serverManifest.d.ts +8 -2
- package/dist/core/serverManifest.d.ts.map +1 -1
- package/dist/core/serverManifest.js +16 -2
- package/dist/core/serverManifest.js.map +1 -1
- package/dist/linter/rules/format-parity-rules.d.ts.map +1 -1
- package/dist/linter/rules/format-parity-rules.js +134 -106
- package/dist/linter/rules/format-parity-rules.js.map +1 -1
- package/dist/logs/combined.log +7 -7
- package/dist/logs/error.log +5 -5
- package/dist/mcp-server/resources/resource-registration.d.ts.map +1 -1
- package/dist/mcp-server/resources/resource-registration.js +2 -0
- package/dist/mcp-server/resources/resource-registration.js.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts +2 -0
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js +2 -0
- package/dist/mcp-server/resources/utils/resourceHandlerFactory.js.map +1 -1
- package/dist/mcp-server/server.d.ts +7 -0
- package/dist/mcp-server/server.d.ts.map +1 -1
- package/dist/mcp-server/server.js +11 -7
- package/dist/mcp-server/server.js.map +1 -1
- package/dist/mcp-server/tools/tool-registration.d.ts.map +1 -1
- package/dist/mcp-server/tools/tool-registration.js +4 -0
- package/dist/mcp-server/tools/tool-registration.js.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts +2 -0
- package/dist/mcp-server/tools/utils/toolHandlerFactory.d.ts.map +1 -1
- package/dist/mcp-server/tools/utils/toolHandlerFactory.js +2 -0
- 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 +9 -0
- package/dist/mcp-server/transports/http/httpTransport.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/styles.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/assets/styles.js +90 -72
- package/dist/mcp-server/transports/http/landing-page/assets/styles.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/connect.d.ts +6 -4
- package/dist/mcp-server/transports/http/landing-page/sections/connect.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/connect.js +76 -19
- package/dist/mcp-server/transports/http/landing-page/sections/connect.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/head.d.ts +2 -1
- package/dist/mcp-server/transports/http/landing-page/sections/head.d.ts.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/head.js +20 -2
- package/dist/mcp-server/transports/http/landing-page/sections/head.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/prompts.js +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/prompts.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/resources.js +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/resources.js.map +1 -1
- package/dist/mcp-server/transports/http/landing-page/sections/tools.js +23 -16
- package/dist/mcp-server/transports/http/landing-page/sections/tools.js.map +1 -1
- package/dist/mcp-server/transports/http/robotsTxt.d.ts +24 -0
- package/dist/mcp-server/transports/http/robotsTxt.d.ts.map +1 -0
- package/dist/mcp-server/transports/http/robotsTxt.js +39 -0
- package/dist/mcp-server/transports/http/robotsTxt.js.map +1 -0
- package/dist/testing/fuzz.js +1 -1
- package/dist/testing/fuzz.js.map +1 -1
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/utils/telemetry/instrumentation.d.ts.map +1 -1
- package/dist/utils/telemetry/instrumentation.js +5 -6
- package/dist/utils/telemetry/instrumentation.js.map +1 -1
- package/package.json +40 -35
- package/scripts/devcheck.ts +35 -4
- package/scripts/lint-packaging.ts +116 -0
- package/scripts/list-skills.ts +170 -0
- package/skills/api-workers/SKILL.md +15 -1
- package/skills/field-test/SKILL.md +96 -90
- package/skills/maintenance/SKILL.md +3 -1
- package/skills/multi-server-orchestration/SKILL.md +123 -0
- package/skills/multi-server-orchestration/references/greenfield-buildout.md +215 -0
- package/skills/multi-server-orchestration/references/maintenance-pass.md +119 -0
- package/skills/multi-server-orchestration/references/release-pass.md +189 -0
- package/skills/polish-docs-meta/SKILL.md +1 -1
- package/skills/polish-docs-meta/references/package-meta.md +1 -1
- package/skills/polish-docs-meta/references/readme.md +10 -7
- package/skills/polish-docs-meta/references/server-json.md +2 -2
- package/skills/release-and-publish/SKILL.md +38 -7
- package/skills/setup/SKILL.md +1 -1
- package/templates/AGENTS.md +37 -0
- package/templates/CLAUDE.md +37 -0
- package/templates/_.mcpbignore +13 -0
- package/templates/manifest.json +26 -0
- package/templates/package.json +6 -1
package/scripts/devcheck.ts
CHANGED
|
@@ -416,6 +416,20 @@ const ALL_CHECKS: Check[] = [
|
|
|
416
416
|
tip: (c) =>
|
|
417
417
|
`Fix definition errors above — each diagnostic links to its rule in ${c.bold('skills/api-linter/SKILL.md')}.`,
|
|
418
418
|
},
|
|
419
|
+
{
|
|
420
|
+
name: 'Packaging',
|
|
421
|
+
flag: '--no-packaging',
|
|
422
|
+
canFix: false,
|
|
423
|
+
// Validates env var alignment between manifest.json (MCPB bundle) and
|
|
424
|
+
// server.json (MCP Registry). Skipped cleanly when manifest.json is absent
|
|
425
|
+
// — consumers who deleted it for an HTTP-only deploy are unaffected.
|
|
426
|
+
getCommand: () => {
|
|
427
|
+
if (!existsSync(path.join(ROOT_DIR, 'manifest.json'))) return null;
|
|
428
|
+
return ['bun', 'run', 'scripts/lint-packaging.ts'];
|
|
429
|
+
},
|
|
430
|
+
tip: (c) =>
|
|
431
|
+
`Align env var names between ${c.bold('manifest.json')} ${c.bold('mcp_config.env')} and ${c.bold('server.json')} stdio package ${c.bold('environmentVariables[]')}.`,
|
|
432
|
+
},
|
|
419
433
|
{
|
|
420
434
|
name: 'Framework Antipatterns',
|
|
421
435
|
flag: '--no-framework-antipatterns',
|
|
@@ -591,20 +605,24 @@ const ALL_CHECKS: Check[] = [
|
|
|
591
605
|
const output = result.stdout.trim();
|
|
592
606
|
if (result.exitCode !== 0 && !output.includes('|')) return false;
|
|
593
607
|
|
|
594
|
-
// Parse the tabular output.
|
|
595
|
-
//
|
|
608
|
+
// Parse the tabular output. `bun outdated` emits markdown-style rows
|
|
609
|
+
// (`| col1 | col2 | ... |`), so split('|') yields an empty leading cell —
|
|
610
|
+
// package data starts at index [1]. Strip the trailing `(dev|peer|prod|optional)`
|
|
611
|
+
// workspace-type marker so the allowlist takes the bare package name.
|
|
596
612
|
const lines = output.split('\n');
|
|
613
|
+
const stripWorkspaceMarker = (cell: string): string =>
|
|
614
|
+
cell.replace(/\s*\((?:dev|peer|prod|optional)\)$/, '');
|
|
597
615
|
const packageLines = lines.filter((line) => {
|
|
598
616
|
if (!line.includes('|')) return false;
|
|
599
617
|
// Skip table chrome: header row and separator (e.g., "---")
|
|
600
|
-
const firstCell = line.split('|')[
|
|
618
|
+
const firstCell = line.split('|')[1]?.trim() ?? '';
|
|
601
619
|
if (!firstCell || firstCell === 'Package' || /^-+$/.test(firstCell)) return false;
|
|
602
620
|
return true;
|
|
603
621
|
});
|
|
604
622
|
|
|
605
623
|
// Check if every outdated package is in the allowlist
|
|
606
624
|
const unexpected = packageLines.filter((line) => {
|
|
607
|
-
const pkgName = line.split('|')[
|
|
625
|
+
const pkgName = stripWorkspaceMarker(line.split('|')[1]?.trim() ?? '');
|
|
608
626
|
return !OUTDATED_ALLOWLIST.has(pkgName);
|
|
609
627
|
});
|
|
610
628
|
|
|
@@ -915,6 +933,19 @@ async function runCheck(check: Check, ctx: AppContext): Promise<CommandResult> {
|
|
|
915
933
|
const result = await Shell.exec(command, { cwd: ctx.rootDir });
|
|
916
934
|
const duration = Math.round(performance.now() - startTime);
|
|
917
935
|
|
|
936
|
+
// Bun's node-shim (via `bun run`) emits "Registry URL must be" errors when
|
|
937
|
+
// depcheck encounters `cloudflare:*` virtual-module specifiers in Workers
|
|
938
|
+
// tests. depcheck.ignores already filters them from the report — strip the
|
|
939
|
+
// cosmetic stderr so the summary stays clean.
|
|
940
|
+
if (name === 'Unused Dependencies' && result.stderr) {
|
|
941
|
+
result.stderr = result.stderr
|
|
942
|
+
.replace(
|
|
943
|
+
/error: Registry URL must be http:\/\/ or https:\/\/\nReceived: "cloudflare:[^"]*"\n?/g,
|
|
944
|
+
'',
|
|
945
|
+
)
|
|
946
|
+
.trim();
|
|
947
|
+
}
|
|
948
|
+
|
|
918
949
|
const finalResult: CommandResult = {
|
|
919
950
|
...baseResult,
|
|
920
951
|
...result,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview MCPB packaging linter — validates env var alignment between
|
|
4
|
+
* `manifest.json` (MCPB bundle install UX) and `server.json` (MCP Registry
|
|
5
|
+
* discovery) for stdio packages.
|
|
6
|
+
*
|
|
7
|
+
* Used by devcheck and as a standalone script: `bun run lint:packaging` /
|
|
8
|
+
* `npm run lint:packaging`.
|
|
9
|
+
*
|
|
10
|
+
* Checks:
|
|
11
|
+
* 1. Every `${user_config.X}` reference in manifest `mcp_config.env` must
|
|
12
|
+
* appear in server.json stdio `environmentVariables[]` (the registry
|
|
13
|
+
* advertises the configurable knob the bundle surfaces).
|
|
14
|
+
* 2. Every required stdio env var in server.json (no default) must appear
|
|
15
|
+
* as a key in manifest `mcp_config.env` (the bundle can receive it).
|
|
16
|
+
*
|
|
17
|
+
* Skips cleanly when `manifest.json` is absent — consumers who deleted it for
|
|
18
|
+
* an HTTP-only deploy should not fail this check.
|
|
19
|
+
*
|
|
20
|
+
* @module scripts/lint-packaging
|
|
21
|
+
*/
|
|
22
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
23
|
+
import { resolve } from 'node:path';
|
|
24
|
+
|
|
25
|
+
interface ServerJsonEnvVar {
|
|
26
|
+
default?: string;
|
|
27
|
+
isRequired?: boolean;
|
|
28
|
+
name: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface ServerJsonPackage {
|
|
32
|
+
environmentVariables?: ServerJsonEnvVar[];
|
|
33
|
+
transport?: { type?: string };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface ServerJson {
|
|
37
|
+
packages?: ServerJsonPackage[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface Manifest {
|
|
41
|
+
server?: { mcp_config?: { env?: Record<string, string> } };
|
|
42
|
+
user_config?: Record<string, unknown>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const USER_CONFIG_REF = /^\$\{user_config\.([\w-]+)\}$/;
|
|
46
|
+
|
|
47
|
+
function tryReadJson<T>(path: string): T | undefined {
|
|
48
|
+
try {
|
|
49
|
+
if (!existsSync(path)) return;
|
|
50
|
+
return JSON.parse(readFileSync(path, 'utf-8')) as T;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error(`Failed to parse ${path}: ${err instanceof Error ? err.message : err}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function main(): void {
|
|
58
|
+
const manifestPath = resolve('manifest.json');
|
|
59
|
+
if (!existsSync(manifestPath)) {
|
|
60
|
+
console.log('No manifest.json — skipping lint:packaging.');
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const manifest = tryReadJson<Manifest>(manifestPath);
|
|
65
|
+
if (!manifest) {
|
|
66
|
+
console.error('manifest.json is unreadable or malformed.');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const serverJson = tryReadJson<ServerJson>(resolve('server.json'));
|
|
71
|
+
if (!serverJson) {
|
|
72
|
+
console.log('No server.json — skipping cross-validation.');
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const manifestEnv = manifest.server?.mcp_config?.env ?? {};
|
|
77
|
+
const manifestEnvKeys = new Set(Object.keys(manifestEnv));
|
|
78
|
+
|
|
79
|
+
const manifestUserConfigKeys = new Set(
|
|
80
|
+
Object.entries(manifestEnv)
|
|
81
|
+
.filter(([, v]) => typeof v === 'string' && USER_CONFIG_REF.test(v))
|
|
82
|
+
.map(([k]) => k),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const stdioEnvVars = (serverJson.packages ?? [])
|
|
86
|
+
.filter((p) => p.transport?.type === 'stdio')
|
|
87
|
+
.flatMap((p) => p.environmentVariables ?? []);
|
|
88
|
+
const stdioEnvNames = new Set(stdioEnvVars.map((v) => v.name));
|
|
89
|
+
const requiredStdioEnvNames = new Set(
|
|
90
|
+
stdioEnvVars.filter((v) => v.isRequired === true && v.default == null).map((v) => v.name),
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const missingInServerJson = [...manifestUserConfigKeys].filter((k) => !stdioEnvNames.has(k));
|
|
94
|
+
const missingInManifest = [...requiredStdioEnvNames].filter((k) => !manifestEnvKeys.has(k));
|
|
95
|
+
|
|
96
|
+
const errors: string[] = [];
|
|
97
|
+
if (missingInServerJson.length > 0) {
|
|
98
|
+
errors.push(
|
|
99
|
+
`manifest.json references user_config env var(s) not advertised in server.json stdio environmentVariables[]: ${missingInServerJson.join(', ')}`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
if (missingInManifest.length > 0) {
|
|
103
|
+
errors.push(
|
|
104
|
+
`server.json declares required stdio env var(s) without default missing from manifest.json mcp_config.env: ${missingInManifest.join(', ')}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (errors.length === 0) {
|
|
109
|
+
console.log('Packaging alignment OK.');
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
for (const err of errors) console.error(` ✗ ${err}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
main();
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Surfaces the YAML frontmatter of all SKILL.md files in this
|
|
4
|
+
* project's `.claude/skills/` directory (falling back to `skills/`). Mirrors
|
|
5
|
+
* how the Claude Code harness lists available skills, but as plain stdout an
|
|
6
|
+
* agent can read.
|
|
7
|
+
*
|
|
8
|
+
* Sub-agents spawned via the Agent tool do NOT inherit the parent session's
|
|
9
|
+
* skill registry — they see only the parent's skills, not the project-local
|
|
10
|
+
* skills in their working directory. Running this script gives a sub-agent
|
|
11
|
+
* operating in this project a quick index of available local skills. The
|
|
12
|
+
* agent can then read any relevant SKILL.md by the printed path before
|
|
13
|
+
* following its steps.
|
|
14
|
+
*
|
|
15
|
+
* @module scripts/list-skills
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* bun run scripts/list-skills.ts
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync } from 'node:fs';
|
|
22
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
23
|
+
import { join, resolve } from 'node:path';
|
|
24
|
+
|
|
25
|
+
const CANDIDATE_DIRS = ['.claude/skills', 'skills'] as const;
|
|
26
|
+
|
|
27
|
+
interface SkillEntry {
|
|
28
|
+
description: string;
|
|
29
|
+
name: string;
|
|
30
|
+
path: string;
|
|
31
|
+
references: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Naive YAML frontmatter parser. Handles flat `key: value` pairs (with
|
|
36
|
+
* optional surrounding `"` / `'` quotes stripped) and folded (`>`) / literal
|
|
37
|
+
* (`|`) block scalars over indented continuation lines — folded joins lines
|
|
38
|
+
* with spaces, literal preserves newlines. Sufficient for SKILL.md
|
|
39
|
+
* frontmatter — do not extend to general YAML; pull in a real parser if the
|
|
40
|
+
* format outgrows this.
|
|
41
|
+
*/
|
|
42
|
+
function parseFrontmatter(content: string): Record<string, string> {
|
|
43
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
44
|
+
const body = match?.[1];
|
|
45
|
+
if (!body) return {};
|
|
46
|
+
const out: Record<string, string> = {};
|
|
47
|
+
const lines = body.split('\n');
|
|
48
|
+
let i = 0;
|
|
49
|
+
while (i < lines.length) {
|
|
50
|
+
const line = lines[i];
|
|
51
|
+
if (line === undefined) break;
|
|
52
|
+
const kv = line.match(/^(\w+):\s*(.*)$/);
|
|
53
|
+
const key = kv?.[1];
|
|
54
|
+
if (!kv || !key) {
|
|
55
|
+
i++;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const val = (kv[2] ?? '').trim();
|
|
59
|
+
const block = val.match(/^([>|])[-+]?$/);
|
|
60
|
+
if (block) {
|
|
61
|
+
const buf: string[] = [];
|
|
62
|
+
i++;
|
|
63
|
+
while (i < lines.length) {
|
|
64
|
+
const next = lines[i];
|
|
65
|
+
if (next === undefined || !/^\s+\S/.test(next)) break;
|
|
66
|
+
buf.push(next.trim());
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
out[key] = buf.join(block[1] === '|' ? '\n' : ' ').trim();
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
out[key] = stripQuotes(val);
|
|
73
|
+
i++;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function stripQuotes(s: string): string {
|
|
79
|
+
if (s.length < 2) return s;
|
|
80
|
+
const first = s[0];
|
|
81
|
+
const last = s[s.length - 1];
|
|
82
|
+
if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
|
|
83
|
+
return s.slice(1, -1);
|
|
84
|
+
}
|
|
85
|
+
return s;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function listReferences(skillDir: string): Promise<string[]> {
|
|
89
|
+
try {
|
|
90
|
+
const entries = await readdir(join(skillDir, 'references'));
|
|
91
|
+
return entries.filter((e) => e.endsWith('.md')).sort();
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return [];
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function findSkillsDir(): { rel: string; abs: string } | null {
|
|
99
|
+
// Resolve candidates relative to the script's parent dir (project root),
|
|
100
|
+
// not CWD — invocation may be from a sub-agent's worktree or subdirectory.
|
|
101
|
+
const root = resolve(import.meta.dirname, '..');
|
|
102
|
+
for (const dir of CANDIDATE_DIRS) {
|
|
103
|
+
const abs = join(root, dir);
|
|
104
|
+
if (existsSync(abs)) return { rel: dir, abs };
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function main(): Promise<void> {
|
|
110
|
+
const dir = findSkillsDir();
|
|
111
|
+
const root = resolve(import.meta.dirname, '..');
|
|
112
|
+
if (!dir) {
|
|
113
|
+
console.error(
|
|
114
|
+
`No skills directory found. Expected one of: ${CANDIDATE_DIRS.join(', ')} relative to project root (${root}).`,
|
|
115
|
+
);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const entries = await readdir(dir.abs, { withFileTypes: true });
|
|
120
|
+
const skills = (
|
|
121
|
+
await Promise.all(
|
|
122
|
+
entries
|
|
123
|
+
.filter((e) => e.isDirectory())
|
|
124
|
+
.map(async (entry): Promise<SkillEntry | null> => {
|
|
125
|
+
const skillDir = join(dir.abs, entry.name);
|
|
126
|
+
const skillPath = join(skillDir, 'SKILL.md');
|
|
127
|
+
let content: string;
|
|
128
|
+
try {
|
|
129
|
+
content = await readFile(skillPath, 'utf-8');
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
const fm = parseFrontmatter(content);
|
|
135
|
+
return {
|
|
136
|
+
name: fm.name ?? entry.name,
|
|
137
|
+
description: fm.description ?? '',
|
|
138
|
+
path: skillPath,
|
|
139
|
+
references: await listReferences(skillDir),
|
|
140
|
+
};
|
|
141
|
+
}),
|
|
142
|
+
)
|
|
143
|
+
).filter((s): s is SkillEntry => s !== null);
|
|
144
|
+
|
|
145
|
+
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
146
|
+
|
|
147
|
+
console.log(`# Skills available in ${dir.rel}/`);
|
|
148
|
+
console.log(`# Project root: ${root}`);
|
|
149
|
+
console.log(`#`);
|
|
150
|
+
console.log(`# Sub-agents: this list mimics the parent harness's skill registry.`);
|
|
151
|
+
console.log(
|
|
152
|
+
`# Read the full SKILL.md at the listed path before following a skill's procedure.\n`,
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
for (const s of skills) {
|
|
156
|
+
console.log(`- ${s.name} (${s.path})`);
|
|
157
|
+
if (s.description) console.log(` ${s.description}`);
|
|
158
|
+
if (s.references.length > 0) {
|
|
159
|
+
console.log(` references: ${s.references.join(', ')}`);
|
|
160
|
+
}
|
|
161
|
+
console.log();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log(`Total: ${skills.length} skills`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
main().catch((err) => {
|
|
168
|
+
console.error(err);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
|
@@ -4,7 +4,7 @@ description: >
|
|
|
4
4
|
Cloudflare Workers deployment using `createWorkerHandler` from `@cyanheads/mcp-ts-core/worker`. Covers the full handler signature, binding types, CloudflareBindings extensibility, runtime compatibility guards, and wrangler.toml requirements.
|
|
5
5
|
metadata:
|
|
6
6
|
author: cyanheads
|
|
7
|
-
version: "1.
|
|
7
|
+
version: "1.4"
|
|
8
8
|
audience: external
|
|
9
9
|
type: reference
|
|
10
10
|
---
|
|
@@ -49,6 +49,7 @@ Fresh scaffolds register definitions directly in the entry point as shown above.
|
|
|
49
49
|
| `resources` | `AnyResourceDefinition[]` | Resource definitions to register |
|
|
50
50
|
| `prompts` | `PromptDefinition[]` | Prompt definitions to register |
|
|
51
51
|
| `extensions` | `Record<string, object>` | SEP-2133 extensions to advertise in server capabilities |
|
|
52
|
+
| `instructions` | `string \| (env: CloudflareBindings) => string` | Server-level orientation forwarded to the model on every `initialize`. Resolver form runs inside `initializeApp(env)` so env-derived text is available (see Workers-specific warnings). Empty string treated as unset. |
|
|
52
53
|
| `setup` | `(core: CoreServices) => void \| Promise<void>` | Runs after core services are ready, during the first request (lazy init inside the fetch handler) |
|
|
53
54
|
| `extraEnvBindings` | `[bindingKey: string, processEnvKey: string][]` | Maps CF string bindings to `process.env` keys |
|
|
54
55
|
| `extraObjectBindings` | `[bindingKey: string, globalKey: string][]` | Maps CF object bindings (KV, R2, D1, AI) to `globalThis` keys |
|
|
@@ -156,6 +157,19 @@ bucket_name = "..."
|
|
|
156
157
|
|
|
157
158
|
## Workers-specific warnings
|
|
158
159
|
|
|
160
|
+
**`instructions` resolver runs after env injection.** When `instructions` is a function, it runs inside `initializeApp(env)` — after `injectEnvVars()` — so env-derived text reaches the model without fighting the Workers module-load lifecycle:
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
export default createWorkerHandler({
|
|
164
|
+
tools: [echoTool],
|
|
165
|
+
instructions: (env) =>
|
|
166
|
+
`Region: ${env.ENVIRONMENT ?? 'production'}.` +
|
|
167
|
+
(env.MAINTENANCE_MODE ? ' Read-only mode — writes disabled.' : ''),
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Plain strings work the same as on `createApp`. Type extends `Omit<CreateAppOptions, 'instructions'>`, so this is the only option whose shape differs between Node and Worker entry points.
|
|
172
|
+
|
|
159
173
|
**Lazy env parsing is mandatory.** Cloudflare injects env bindings at request time via `injectEnvVars()`, after all static module imports complete. Never parse `process.env` at module top-level in Workers:
|
|
160
174
|
|
|
161
175
|
```ts
|