frontmcp 1.0.4 → 1.1.0

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 (100) hide show
  1. package/package.json +10 -4
  2. package/src/commands/build/adapters/cloudflare.d.ts +1 -1
  3. package/src/commands/build/adapters/cloudflare.js +1 -0
  4. package/src/commands/build/adapters/cloudflare.js.map +1 -1
  5. package/src/commands/build/adapters/distributed.d.ts +15 -0
  6. package/src/commands/build/adapters/distributed.js +30 -0
  7. package/src/commands/build/adapters/distributed.js.map +1 -0
  8. package/src/commands/build/adapters/index.d.ts +3 -2
  9. package/src/commands/build/adapters/index.js +4 -1
  10. package/src/commands/build/adapters/index.js.map +1 -1
  11. package/src/commands/build/adapters/lambda.d.ts +1 -1
  12. package/src/commands/build/adapters/lambda.js +1 -0
  13. package/src/commands/build/adapters/lambda.js.map +1 -1
  14. package/src/commands/build/adapters/vercel.d.ts +1 -1
  15. package/src/commands/build/adapters/vercel.js +1 -0
  16. package/src/commands/build/adapters/vercel.js.map +1 -1
  17. package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +122 -34
  18. package/src/commands/build/exec/cli-runtime/generate-cli-entry.js.map +1 -1
  19. package/src/commands/build/exec/config.d.ts +2 -2
  20. package/src/commands/build/exec/config.js.map +1 -1
  21. package/src/commands/build/exec/esbuild-bundler.d.ts +1 -1
  22. package/src/commands/build/exec/esbuild-bundler.js +1 -1
  23. package/src/commands/build/exec/esbuild-bundler.js.map +1 -1
  24. package/src/commands/build/exec/index.d.ts +1 -1
  25. package/src/commands/build/exec/index.js +5 -38
  26. package/src/commands/build/exec/index.js.map +1 -1
  27. package/src/commands/build/exec/skill-assets.d.ts +27 -0
  28. package/src/commands/build/exec/skill-assets.js +60 -0
  29. package/src/commands/build/exec/skill-assets.js.map +1 -0
  30. package/src/commands/build/index.d.ts +1 -1
  31. package/src/commands/build/index.js +44 -5
  32. package/src/commands/build/index.js.map +1 -1
  33. package/src/commands/build/mcpb/binary.d.ts +37 -0
  34. package/src/commands/build/mcpb/binary.js +72 -0
  35. package/src/commands/build/mcpb/binary.js.map +1 -0
  36. package/src/commands/build/mcpb/constants.d.ts +21 -0
  37. package/src/commands/build/mcpb/constants.js +31 -0
  38. package/src/commands/build/mcpb/constants.js.map +1 -0
  39. package/src/commands/build/mcpb/index.d.ts +20 -0
  40. package/src/commands/build/mcpb/index.js +241 -0
  41. package/src/commands/build/mcpb/index.js.map +1 -0
  42. package/src/commands/build/mcpb/manifest.d.ts +183 -0
  43. package/src/commands/build/mcpb/manifest.js +252 -0
  44. package/src/commands/build/mcpb/manifest.js.map +1 -0
  45. package/src/commands/build/mcpb/stage.d.ts +50 -0
  46. package/src/commands/build/mcpb/stage.js +94 -0
  47. package/src/commands/build/mcpb/stage.js.map +1 -0
  48. package/src/commands/build/mcpb/user-config.d.ts +26 -0
  49. package/src/commands/build/mcpb/user-config.js +147 -0
  50. package/src/commands/build/mcpb/user-config.js.map +1 -0
  51. package/src/commands/build/mcpb/validate.d.ts +27 -0
  52. package/src/commands/build/mcpb/validate.js +218 -0
  53. package/src/commands/build/mcpb/validate.js.map +1 -0
  54. package/src/commands/build/mcpb/zip.d.ts +37 -0
  55. package/src/commands/build/mcpb/zip.js +85 -0
  56. package/src/commands/build/mcpb/zip.js.map +1 -0
  57. package/src/commands/build/register.d.ts +1 -1
  58. package/src/commands/build/register.js +7 -1
  59. package/src/commands/build/register.js.map +1 -1
  60. package/src/commands/build/sdk/index.d.ts +1 -1
  61. package/src/commands/build/sdk/index.js +1 -1
  62. package/src/commands/build/sdk/index.js.map +1 -1
  63. package/src/commands/build/types.d.ts +1 -1
  64. package/src/commands/build/types.js.map +1 -1
  65. package/src/commands/mcpb/register.d.ts +2 -0
  66. package/src/commands/mcpb/register.js +14 -0
  67. package/src/commands/mcpb/register.js.map +1 -0
  68. package/src/commands/mcpb/validate.d.ts +1 -0
  69. package/src/commands/mcpb/validate.js +28 -0
  70. package/src/commands/mcpb/validate.js.map +1 -0
  71. package/src/commands/scaffold/create.js +5 -7
  72. package/src/commands/scaffold/create.js.map +1 -1
  73. package/src/config/define-config.d.ts +26 -0
  74. package/src/config/define-config.js +31 -0
  75. package/src/config/define-config.js.map +1 -0
  76. package/src/config/frontmcp-config.loader.d.ts +32 -0
  77. package/src/config/frontmcp-config.loader.js +113 -0
  78. package/src/config/frontmcp-config.loader.js.map +1 -0
  79. package/src/config/frontmcp-config.schema.d.ts +1062 -0
  80. package/src/config/frontmcp-config.schema.js +313 -0
  81. package/src/config/frontmcp-config.schema.js.map +1 -0
  82. package/src/config/frontmcp-config.types.d.ts +287 -0
  83. package/src/config/frontmcp-config.types.js +14 -0
  84. package/src/config/frontmcp-config.types.js.map +1 -0
  85. package/src/config/index.d.ts +5 -0
  86. package/src/config/index.js +13 -0
  87. package/src/config/index.js.map +1 -0
  88. package/src/core/args.d.ts +7 -2
  89. package/src/core/args.js +12 -2
  90. package/src/core/args.js.map +1 -1
  91. package/src/core/bridge.d.ts +1 -1
  92. package/src/core/bridge.js +10 -0
  93. package/src/core/bridge.js.map +1 -1
  94. package/src/core/cli.js +3 -1
  95. package/src/core/cli.js.map +1 -1
  96. package/src/core/program.js +15 -13
  97. package/src/core/program.js.map +1 -1
  98. package/src/index.d.ts +2 -0
  99. package/src/index.js +7 -0
  100. package/src/index.js.map +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-config.js","sourceRoot":"","sources":["../../../../../src/commands/build/mcpb/user-config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Translate FrontMCP `setup.steps` into MCPB `user_config` + `mcp_config.env`.\n *\n * MCPB's user_config is a flat key/value form. FrontMCP's setup graph supports\n * branching (`step.next`) and conditional visibility (`step.showWhen`). Those\n * features have no MCPB equivalent — we emit a warning and render every step\n * unconditionally.\n */\n\nimport { idToEnvName, type SetupStep, zodSchemaToJsonSchema } from '../exec/setup';\nimport type {\n McpbDeployment,\n McpbUserConfigEntry,\n McpbUserConfigType,\n} from '../../../config/frontmcp-config.types';\nimport { USER_CONFIG_PREFIX } from './constants';\n\nexport interface UserConfigTranslationResult {\n /** MCPB user_config block. */\n userConfig: Record<string, McpbUserConfigEntry>;\n /** mcp_config.env map: ENV_NAME → ${user_config.key}. */\n env: Record<string, string>;\n /** Warnings to surface to the CLI log. */\n warnings: string[];\n}\n\n/** Convert kebab/snake/SCREAMING_SNAKE id to camelCase for the MCPB key. */\nexport function idToCamelKey(id: string): string {\n const normalized = id.replace(/[^a-zA-Z0-9]+/g, ' ').trim().toLowerCase();\n const parts = normalized.split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return 'value';\n return parts\n .map((part, idx) => (idx === 0 ? part : part[0].toUpperCase() + part.slice(1)))\n .join('');\n}\n\n/** Resolve the user_config.type for a setup step. */\nfunction resolveType(\n jsonSchema: Record<string, unknown>,\n override?: McpbUserConfigType,\n): { type: McpbUserConfigType; multiple: boolean } {\n if (override) {\n return { type: override, multiple: false };\n }\n const schemaType = jsonSchema['type'];\n if (schemaType === 'boolean') return { type: 'boolean', multiple: false };\n if (schemaType === 'number' || schemaType === 'integer') {\n return { type: 'number', multiple: false };\n }\n if (schemaType === 'array') {\n const items = jsonSchema['items'];\n const itemType =\n items && typeof items === 'object' && 'type' in items\n ? (items as { type: unknown }).type\n : 'string';\n if (itemType === 'number' || itemType === 'integer') {\n return { type: 'number', multiple: true };\n }\n if (itemType === 'boolean') {\n return { type: 'boolean', multiple: true };\n }\n return { type: 'string', multiple: true };\n }\n return { type: 'string', multiple: false };\n}\n\n/**\n * Produce MCPB user_config + env mapping from FrontMCP setup steps.\n * Also applies any per-key `deployment.userConfig` overrides (e.g., to change\n * type to `file` or `directory`).\n */\nexport function setupStepsToUserConfig(\n steps: SetupStep[] | undefined,\n deployment?: McpbDeployment,\n): UserConfigTranslationResult {\n const userConfig: Record<string, McpbUserConfigEntry> = {};\n const env: Record<string, string> = {};\n const warnings: string[] = [];\n\n if (!steps || steps.length === 0) {\n // Allow deployment.userConfig to stand alone (no setup graph).\n if (deployment?.userConfig) {\n for (const [key, entry] of Object.entries(deployment.userConfig)) {\n userConfig[key] = entry;\n }\n }\n return { userConfig, env, warnings };\n }\n\n for (const step of steps) {\n if (step.showWhen || step.next) {\n warnings.push(\n `Step \"${step.id}\" uses showWhen/next — MCPB has no equivalent; rendered unconditionally`,\n );\n }\n\n const jsonSchema =\n step.jsonSchema ?? (step.schema ? zodSchemaToJsonSchema(step.schema) : { type: 'string' });\n const key = idToCamelKey(step.id);\n const override = deployment?.userConfig?.[key];\n\n const { type, multiple } = resolveType(jsonSchema, override?.type);\n\n const entry: McpbUserConfigEntry = {\n type,\n title: override?.title ?? step.prompt,\n ...(step.description || override?.description\n ? { description: override?.description ?? step.description }\n : {}),\n ...(step.sensitive || override?.sensitive ? { sensitive: true } : {}),\n ...(multiple || override?.multiple ? { multiple: true } : {}),\n };\n\n const required = inferRequired(jsonSchema, override?.required);\n if (required) entry.required = true;\n\n const defaultVal = jsonSchema['default'] ?? override?.default;\n if (defaultVal !== undefined && !entry.sensitive) {\n if (\n (entry.type === 'string' || entry.type === 'directory' || entry.type === 'file') &&\n typeof defaultVal === 'string'\n ) {\n entry.default = defaultVal;\n } else if (entry.type === 'number' && typeof defaultVal === 'number') {\n entry.default = defaultVal;\n } else if (entry.type === 'boolean' && typeof defaultVal === 'boolean') {\n entry.default = defaultVal;\n }\n }\n\n const min = pickNumber(jsonSchema['minimum'] ?? jsonSchema['minLength']) ?? override?.min;\n const max = pickNumber(jsonSchema['maximum'] ?? jsonSchema['maxLength']) ?? override?.max;\n if (min !== undefined) entry.min = min;\n if (max !== undefined) entry.max = max;\n\n // Merge explicit deployment.userConfig fields that weren't captured above.\n if (override) {\n for (const prop of ['title', 'description', 'required', 'multiple', 'sensitive', 'min', 'max', 'default'] as const) {\n if (override[prop] !== undefined && entry[prop] === undefined) {\n (entry as unknown as Record<string, unknown>)[prop] = override[prop];\n }\n }\n }\n\n userConfig[key] = entry;\n\n const envName = step.env ?? idToEnvName(step.id);\n env[envName] = `\\${${USER_CONFIG_PREFIX}${key}}`;\n }\n\n // Any deployment.userConfig entries not derived from a setup step are merged verbatim.\n if (deployment?.userConfig) {\n for (const [key, entry] of Object.entries(deployment.userConfig)) {\n if (!userConfig[key]) {\n userConfig[key] = entry;\n }\n }\n }\n\n return { userConfig, env, warnings };\n}\n\nfunction inferRequired(jsonSchema: Record<string, unknown>, override?: boolean): boolean {\n if (override !== undefined) return override;\n // JSON Schema 'required' arrays apply at the parent level; for a single-value\n // schema, treat missing default + no `.optional()` marker as required.\n if (jsonSchema['default'] !== undefined) return false;\n // Best-effort: Zod/v4 encodes optional as a union with undefined or nullable.\n const anyOf = jsonSchema['anyOf'];\n if (Array.isArray(anyOf) && anyOf.some((s) => s && typeof s === 'object' && (s as { type?: string }).type === 'null')) {\n return false;\n }\n return true;\n}\n\nfunction pickNumber(value: unknown): number | undefined {\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n"]}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Validate a `.mcpb` archive against the subset of MCPB v0.3 that FrontMCP
3
+ * emits. Surfaces problems clients would hit when loading the bundle.
4
+ *
5
+ * Checks performed:
6
+ * 1. Archive opens as a zip
7
+ * 2. `manifest.json` exists and parses as JSON
8
+ * 3. Manifest matches `mcpbManifestSchema`
9
+ * 4. `server.entry_point` resolves to an entry inside the archive
10
+ * 5. Every `${user_config.KEY}` reference declares matching user_config[KEY]
11
+ * 6. Only allow-listed variables appear in substitutions
12
+ * 7. `manifest.icon` file exists when referenced
13
+ * 8. No zip-slip (normalized entry names escaping the archive root)
14
+ * 9. Warnings on large archives, absolute-path args, node_modules presence
15
+ */
16
+ import { type McpbManifest } from './manifest';
17
+ export interface ValidateResult {
18
+ ok: boolean;
19
+ errors: string[];
20
+ warnings: string[];
21
+ manifest?: McpbManifest;
22
+ entries?: string[];
23
+ /** Archive size in bytes. */
24
+ size?: number;
25
+ }
26
+ /** Validate a `.mcpb` archive at the given path. */
27
+ export declare function validateMcpb(archivePath: string): Promise<ValidateResult>;
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ /**
3
+ * Validate a `.mcpb` archive against the subset of MCPB v0.3 that FrontMCP
4
+ * emits. Surfaces problems clients would hit when loading the bundle.
5
+ *
6
+ * Checks performed:
7
+ * 1. Archive opens as a zip
8
+ * 2. `manifest.json` exists and parses as JSON
9
+ * 3. Manifest matches `mcpbManifestSchema`
10
+ * 4. `server.entry_point` resolves to an entry inside the archive
11
+ * 5. Every `${user_config.KEY}` reference declares matching user_config[KEY]
12
+ * 6. Only allow-listed variables appear in substitutions
13
+ * 7. `manifest.icon` file exists when referenced
14
+ * 8. No zip-slip (normalized entry names escaping the archive root)
15
+ * 9. Warnings on large archives, absolute-path args, node_modules presence
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.validateMcpb = validateMcpb;
19
+ const tslib_1 = require("tslib");
20
+ const fs = tslib_1.__importStar(require("fs"));
21
+ const path = tslib_1.__importStar(require("path"));
22
+ const manifest_1 = require("./manifest");
23
+ const constants_1 = require("./constants");
24
+ /** Validate a `.mcpb` archive at the given path. */
25
+ async function validateMcpb(archivePath) {
26
+ const result = { ok: false, errors: [], warnings: [] };
27
+ let archive;
28
+ try {
29
+ archive = await readArchive(archivePath);
30
+ }
31
+ catch (err) {
32
+ result.errors.push(`Cannot open archive: ${err.message}`);
33
+ return result;
34
+ }
35
+ result.entries = archive.entries;
36
+ result.size = archive.size;
37
+ if (archive.size > constants_1.ARCHIVE_SIZE_ERROR) {
38
+ result.warnings.push(`Archive is ${(archive.size / 1024 / 1024).toFixed(1)} MB — consider tuning esbuild externals or disabling node_modules inclusion`);
39
+ }
40
+ else if (archive.size > constants_1.ARCHIVE_SIZE_WARN) {
41
+ result.warnings.push(`Archive is ${(archive.size / 1024 / 1024).toFixed(1)} MB`);
42
+ }
43
+ // Zip-slip: flag entries that escape the archive root.
44
+ // Substring-matching `..` would false-positive on legit names like `foo..bar`,
45
+ // so we normalize and check for literal `..` segments plus absolute paths.
46
+ for (const entry of archive.entries) {
47
+ if (isUnsafeArchivePath(entry)) {
48
+ result.errors.push(`Zip-slip risk: entry "${entry}"`);
49
+ }
50
+ }
51
+ if (archive.entries.some((e) => e.startsWith('server/node_modules/'))) {
52
+ result.warnings.push('Archive contains server/node_modules/ — opt-in only; verify this was intentional');
53
+ }
54
+ if (!archive.manifestRaw) {
55
+ result.errors.push('manifest.json is missing from the archive root');
56
+ return result;
57
+ }
58
+ let parsed;
59
+ try {
60
+ parsed = JSON.parse(archive.manifestRaw);
61
+ }
62
+ catch (err) {
63
+ result.errors.push(`manifest.json is not valid JSON: ${err.message}`);
64
+ return result;
65
+ }
66
+ const schemaResult = manifest_1.mcpbManifestSchema.safeParse(parsed);
67
+ if (!schemaResult.success) {
68
+ const issues = schemaResult.error.issues
69
+ .map((i) => `${i.path.join('.')}: ${i.message}`)
70
+ .join('; ');
71
+ result.errors.push(`manifest.json fails schema: ${issues}`);
72
+ return result;
73
+ }
74
+ const manifest = schemaResult.data;
75
+ result.manifest = manifest;
76
+ // entry_point must live in the archive
77
+ if (!archive.entries.includes(manifest.server.entry_point)) {
78
+ result.errors.push(`server.entry_point "${manifest.server.entry_point}" is not present in the archive`);
79
+ }
80
+ // Variable substitution + user_config cross-check
81
+ checkMcpConfig(manifest.server.mcp_config, manifest.user_config, result);
82
+ // Icon existence
83
+ if (manifest.icon && !archive.entries.includes(manifest.icon)) {
84
+ result.errors.push(`icon "${manifest.icon}" is not present in the archive`);
85
+ }
86
+ // Binary references declared via platform_overrides must exist
87
+ const overrides = manifest.server.mcp_config.platform_overrides || {};
88
+ for (const [platform, cfg] of Object.entries(overrides)) {
89
+ const cmd = cfg.command;
90
+ const entryName = stripDirname(cmd);
91
+ if (entryName && !archive.entries.includes(entryName)) {
92
+ result.errors.push(`platform_overrides["${platform}"].command points to "${entryName}" which is not in the archive`);
93
+ }
94
+ }
95
+ result.ok = result.errors.length === 0;
96
+ return result;
97
+ }
98
+ function checkMcpConfig(cfg, userConfig, result) {
99
+ const declared = new Set(Object.keys(userConfig ?? {}));
100
+ const valuesToScan = [cfg.command, ...(cfg.args ?? [])];
101
+ if (cfg.env)
102
+ valuesToScan.push(...Object.values(cfg.env));
103
+ for (const value of valuesToScan) {
104
+ verifySubstitutions(value, declared, result);
105
+ if (isAbsolutePath(value) && !value.startsWith('${__dirname}')) {
106
+ result.warnings.push(`Absolute path in mcp_config: "${value}" — consider using \${__dirname}`);
107
+ }
108
+ }
109
+ if (cfg.platform_overrides) {
110
+ for (const [platform, nested] of Object.entries(cfg.platform_overrides)) {
111
+ checkMcpConfig(nested, userConfig, result);
112
+ void platform;
113
+ }
114
+ }
115
+ }
116
+ function verifySubstitutions(value, declared, result) {
117
+ const re = /\$\{([^}]+)\}/g;
118
+ let match;
119
+ while ((match = re.exec(value)) !== null) {
120
+ const variable = match[1];
121
+ if (variable.startsWith(constants_1.USER_CONFIG_PREFIX)) {
122
+ const key = variable.slice(constants_1.USER_CONFIG_PREFIX.length);
123
+ if (!declared.has(key)) {
124
+ result.errors.push(`Unknown user_config reference: "${variable}"`);
125
+ }
126
+ continue;
127
+ }
128
+ if (!constants_1.ALLOWED_SUBSTITUTION_VARS.has(variable)) {
129
+ result.errors.push(`Unknown substitution variable: "\${${variable}}"`);
130
+ }
131
+ }
132
+ }
133
+ function isAbsolutePath(value) {
134
+ if (!value)
135
+ return false;
136
+ if (value.startsWith('/'))
137
+ return true;
138
+ return /^[a-zA-Z]:[\\/]/.test(value); // C:\ or C:/
139
+ }
140
+ /** Strip a leading ${__dirname}/ from a command path, returning the in-archive entry. */
141
+ function stripDirname(command) {
142
+ const prefix = '${__dirname}/';
143
+ return command.startsWith(prefix) ? command.slice(prefix.length) : undefined;
144
+ }
145
+ function readArchive(archivePath) {
146
+ const yauzl = require('yauzl');
147
+ // yauzl's ZipFile doesn't expose the archive byte size, so measure it
148
+ // directly from disk. stat before open mirrors the error path for missing
149
+ // files.
150
+ const size = fs.statSync(archivePath).size;
151
+ return new Promise((resolve, reject) => {
152
+ yauzl.open(archivePath, { lazyEntries: true }, (err, zip) => {
153
+ if (err || !zip) {
154
+ reject(err || new Error('yauzl returned no handle'));
155
+ return;
156
+ }
157
+ const entries = [];
158
+ let manifestRaw;
159
+ let settled = false;
160
+ const settle = (fn) => {
161
+ if (settled)
162
+ return;
163
+ settled = true;
164
+ try {
165
+ zip.close();
166
+ }
167
+ catch {
168
+ // best-effort close — don't mask the original error
169
+ }
170
+ fn();
171
+ };
172
+ zip.readEntry();
173
+ zip.on('entry', (entry) => {
174
+ entries.push(entry.fileName);
175
+ if (entry.fileName === 'manifest.json') {
176
+ zip.openReadStream(entry, (streamErr, stream) => {
177
+ if (streamErr || !stream) {
178
+ settle(() => reject(streamErr || new Error('Failed to open manifest stream')));
179
+ return;
180
+ }
181
+ const chunks = [];
182
+ stream.on('data', (chunk) => chunks.push(chunk));
183
+ stream.on('end', () => {
184
+ manifestRaw = Buffer.concat(chunks).toString('utf-8');
185
+ zip.readEntry();
186
+ });
187
+ stream.on('error', (e) => settle(() => reject(e)));
188
+ });
189
+ }
190
+ else {
191
+ zip.readEntry();
192
+ }
193
+ });
194
+ zip.on('end', () => {
195
+ settle(() => resolve({ entries, manifestRaw, size }));
196
+ });
197
+ zip.on('error', (e) => settle(() => reject(e)));
198
+ });
199
+ });
200
+ }
201
+ /**
202
+ * A zip entry is unsafe if it uses an absolute path or any normalized segment
203
+ * would let it escape the archive root (`..`). Backslashes are also rejected
204
+ * so Windows-style paths can't bypass the POSIX check.
205
+ */
206
+ function isUnsafeArchivePath(entry) {
207
+ if (!entry)
208
+ return false;
209
+ if (entry.startsWith('/') || entry.includes('\\'))
210
+ return true;
211
+ if (/^[a-zA-Z]:/.test(entry))
212
+ return true; // drive-letter absolute
213
+ const normalized = path.posix.normalize(entry);
214
+ if (normalized.startsWith('../') || normalized === '..')
215
+ return true;
216
+ return normalized.split('/').some((segment) => segment === '..');
217
+ }
218
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../../../src/commands/build/mcpb/validate.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate a `.mcpb` archive against the subset of MCPB v0.3 that FrontMCP\n * emits. Surfaces problems clients would hit when loading the bundle.\n *\n * Checks performed:\n * 1. Archive opens as a zip\n * 2. `manifest.json` exists and parses as JSON\n * 3. Manifest matches `mcpbManifestSchema`\n * 4. `server.entry_point` resolves to an entry inside the archive\n * 5. Every `${user_config.KEY}` reference declares matching user_config[KEY]\n * 6. Only allow-listed variables appear in substitutions\n * 7. `manifest.icon` file exists when referenced\n * 8. No zip-slip (normalized entry names escaping the archive root)\n * 9. Warnings on large archives, absolute-path args, node_modules presence\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { Entry, ZipFile } from 'yauzl';\nimport { mcpbManifestSchema, type McpbManifest, type McpbMcpConfig } from './manifest';\nimport { ALLOWED_SUBSTITUTION_VARS, ARCHIVE_SIZE_ERROR, ARCHIVE_SIZE_WARN, USER_CONFIG_PREFIX } from './constants';\n\nexport interface ValidateResult {\n ok: boolean;\n errors: string[];\n warnings: string[];\n manifest?: McpbManifest;\n entries?: string[];\n /** Archive size in bytes. */\n size?: number;\n}\n\n/** Validate a `.mcpb` archive at the given path. */\nexport async function validateMcpb(archivePath: string): Promise<ValidateResult> {\n const result: ValidateResult = { ok: false, errors: [], warnings: [] };\n\n let archive: { entries: string[]; manifestRaw?: string; size: number };\n try {\n archive = await readArchive(archivePath);\n } catch (err) {\n result.errors.push(`Cannot open archive: ${(err as Error).message}`);\n return result;\n }\n\n result.entries = archive.entries;\n result.size = archive.size;\n\n if (archive.size > ARCHIVE_SIZE_ERROR) {\n result.warnings.push(\n `Archive is ${(archive.size / 1024 / 1024).toFixed(1)} MB — consider tuning esbuild externals or disabling node_modules inclusion`,\n );\n } else if (archive.size > ARCHIVE_SIZE_WARN) {\n result.warnings.push(`Archive is ${(archive.size / 1024 / 1024).toFixed(1)} MB`);\n }\n\n // Zip-slip: flag entries that escape the archive root.\n // Substring-matching `..` would false-positive on legit names like `foo..bar`,\n // so we normalize and check for literal `..` segments plus absolute paths.\n for (const entry of archive.entries) {\n if (isUnsafeArchivePath(entry)) {\n result.errors.push(`Zip-slip risk: entry \"${entry}\"`);\n }\n }\n\n if (archive.entries.some((e) => e.startsWith('server/node_modules/'))) {\n result.warnings.push('Archive contains server/node_modules/ — opt-in only; verify this was intentional');\n }\n\n if (!archive.manifestRaw) {\n result.errors.push('manifest.json is missing from the archive root');\n return result;\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(archive.manifestRaw);\n } catch (err) {\n result.errors.push(`manifest.json is not valid JSON: ${(err as Error).message}`);\n return result;\n }\n\n const schemaResult = mcpbManifestSchema.safeParse(parsed);\n if (!schemaResult.success) {\n const issues = schemaResult.error.issues\n .map((i) => `${i.path.join('.')}: ${i.message}`)\n .join('; ');\n result.errors.push(`manifest.json fails schema: ${issues}`);\n return result;\n }\n\n const manifest = schemaResult.data as McpbManifest;\n result.manifest = manifest;\n\n // entry_point must live in the archive\n if (!archive.entries.includes(manifest.server.entry_point)) {\n result.errors.push(\n `server.entry_point \"${manifest.server.entry_point}\" is not present in the archive`,\n );\n }\n\n // Variable substitution + user_config cross-check\n checkMcpConfig(manifest.server.mcp_config, manifest.user_config, result);\n\n // Icon existence\n if (manifest.icon && !archive.entries.includes(manifest.icon)) {\n result.errors.push(`icon \"${manifest.icon}\" is not present in the archive`);\n }\n\n // Binary references declared via platform_overrides must exist\n const overrides = manifest.server.mcp_config.platform_overrides || {};\n for (const [platform, cfg] of Object.entries(overrides)) {\n const cmd = cfg.command;\n const entryName = stripDirname(cmd);\n if (entryName && !archive.entries.includes(entryName)) {\n result.errors.push(\n `platform_overrides[\"${platform}\"].command points to \"${entryName}\" which is not in the archive`,\n );\n }\n }\n\n result.ok = result.errors.length === 0;\n return result;\n}\n\nfunction checkMcpConfig(\n cfg: McpbMcpConfig,\n userConfig: Record<string, unknown> | undefined,\n result: ValidateResult,\n): void {\n const declared = new Set(Object.keys(userConfig ?? {}));\n const valuesToScan: string[] = [cfg.command, ...(cfg.args ?? [])];\n if (cfg.env) valuesToScan.push(...Object.values(cfg.env));\n for (const value of valuesToScan) {\n verifySubstitutions(value, declared, result);\n if (isAbsolutePath(value) && !value.startsWith('${__dirname}')) {\n result.warnings.push(`Absolute path in mcp_config: \"${value}\" — consider using \\${__dirname}`);\n }\n }\n if (cfg.platform_overrides) {\n for (const [platform, nested] of Object.entries(cfg.platform_overrides)) {\n checkMcpConfig(nested, userConfig, result);\n void platform;\n }\n }\n}\n\nfunction verifySubstitutions(value: string, declared: Set<string>, result: ValidateResult): void {\n const re = /\\$\\{([^}]+)\\}/g;\n let match: RegExpExecArray | null;\n while ((match = re.exec(value)) !== null) {\n const variable = match[1];\n if (variable.startsWith(USER_CONFIG_PREFIX)) {\n const key = variable.slice(USER_CONFIG_PREFIX.length);\n if (!declared.has(key)) {\n result.errors.push(`Unknown user_config reference: \"${variable}\"`);\n }\n continue;\n }\n if (!ALLOWED_SUBSTITUTION_VARS.has(variable)) {\n result.errors.push(`Unknown substitution variable: \"\\${${variable}}\"`);\n }\n }\n}\n\nfunction isAbsolutePath(value: string): boolean {\n if (!value) return false;\n if (value.startsWith('/')) return true;\n return /^[a-zA-Z]:[\\\\/]/.test(value); // C:\\ or C:/\n}\n\n/** Strip a leading ${__dirname}/ from a command path, returning the in-archive entry. */\nfunction stripDirname(command: string): string | undefined {\n const prefix = '${__dirname}/';\n return command.startsWith(prefix) ? command.slice(prefix.length) : undefined;\n}\n\ninterface RawArchive {\n entries: string[];\n manifestRaw?: string;\n size: number;\n}\n\nfunction readArchive(archivePath: string): Promise<RawArchive> {\n const yauzl = require('yauzl') as typeof import('yauzl');\n // yauzl's ZipFile doesn't expose the archive byte size, so measure it\n // directly from disk. stat before open mirrors the error path for missing\n // files.\n const size = fs.statSync(archivePath).size;\n return new Promise<RawArchive>((resolve, reject) => {\n yauzl.open(archivePath, { lazyEntries: true }, (err: Error | null, zip: ZipFile | undefined) => {\n if (err || !zip) {\n reject(err || new Error('yauzl returned no handle'));\n return;\n }\n const entries: string[] = [];\n let manifestRaw: string | undefined;\n let settled = false;\n const settle = (fn: () => void): void => {\n if (settled) return;\n settled = true;\n try {\n zip.close();\n } catch {\n // best-effort close — don't mask the original error\n }\n fn();\n };\n\n zip.readEntry();\n zip.on('entry', (entry: Entry) => {\n entries.push(entry.fileName);\n if (entry.fileName === 'manifest.json') {\n zip.openReadStream(entry, (streamErr: Error | null, stream: NodeJS.ReadableStream | undefined) => {\n if (streamErr || !stream) {\n settle(() => reject(streamErr || new Error('Failed to open manifest stream')));\n return;\n }\n const chunks: Buffer[] = [];\n stream.on('data', (chunk: Buffer) => chunks.push(chunk));\n stream.on('end', () => {\n manifestRaw = Buffer.concat(chunks).toString('utf-8');\n zip.readEntry();\n });\n stream.on('error', (e: Error) => settle(() => reject(e)));\n });\n } else {\n zip.readEntry();\n }\n });\n zip.on('end', () => {\n settle(() => resolve({ entries, manifestRaw, size }));\n });\n zip.on('error', (e: Error) => settle(() => reject(e)));\n });\n });\n}\n\n/**\n * A zip entry is unsafe if it uses an absolute path or any normalized segment\n * would let it escape the archive root (`..`). Backslashes are also rejected\n * so Windows-style paths can't bypass the POSIX check.\n */\nfunction isUnsafeArchivePath(entry: string): boolean {\n if (!entry) return false;\n if (entry.startsWith('/') || entry.includes('\\\\')) return true;\n if (/^[a-zA-Z]:/.test(entry)) return true; // drive-letter absolute\n const normalized = path.posix.normalize(entry);\n if (normalized.startsWith('../') || normalized === '..') return true;\n return normalized.split('/').some((segment) => segment === '..');\n}\n"]}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Deterministic ZIP creation for MCPB archives.
3
+ *
4
+ * Determinism is achieved by:
5
+ * - walking the stage directory in sorted order
6
+ * - uniform mtime (`DETERMINISTIC_MTIME`) on every entry
7
+ * - forceZip64Format: false (yazl default — keeps the archive small & portable)
8
+ *
9
+ * Two back-to-back builds of the same staged directory produce byte-identical
10
+ * `.mcpb` archives with matching SHA-256 hashes.
11
+ */
12
+ export interface ZipResult {
13
+ /** Absolute path to the produced archive. */
14
+ archivePath: string;
15
+ /** Final archive size in bytes. */
16
+ size: number;
17
+ /** SHA-256 of the archive contents (lowercase hex). */
18
+ sha256: string;
19
+ /** Entry names in archive order (useful for tests/logging). */
20
+ entries: string[];
21
+ }
22
+ export interface ZipOptions {
23
+ /** Apply DETERMINISTIC_MTIME to every entry. @default true */
24
+ deterministic?: boolean;
25
+ /** When true, deflate entries; when false, store uncompressed. @default true */
26
+ compress?: boolean;
27
+ }
28
+ /**
29
+ * Recursively walk a directory and return every file's {archivePath, absPath}
30
+ * pair. Archive paths use forward slashes regardless of host OS.
31
+ */
32
+ export declare function listFilesForArchive(stageDir: string): Array<{
33
+ archivePath: string;
34
+ absPath: string;
35
+ }>;
36
+ /** Create a deterministic `.mcpb` archive from a staged directory. */
37
+ export declare function createDeterministicZip(stageDir: string, archivePath: string, options?: ZipOptions): Promise<ZipResult>;
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ /**
3
+ * Deterministic ZIP creation for MCPB archives.
4
+ *
5
+ * Determinism is achieved by:
6
+ * - walking the stage directory in sorted order
7
+ * - uniform mtime (`DETERMINISTIC_MTIME`) on every entry
8
+ * - forceZip64Format: false (yazl default — keeps the archive small & portable)
9
+ *
10
+ * Two back-to-back builds of the same staged directory produce byte-identical
11
+ * `.mcpb` archives with matching SHA-256 hashes.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.listFilesForArchive = listFilesForArchive;
15
+ exports.createDeterministicZip = createDeterministicZip;
16
+ const tslib_1 = require("tslib");
17
+ const crypto = tslib_1.__importStar(require("crypto"));
18
+ const fs = tslib_1.__importStar(require("fs"));
19
+ const path = tslib_1.__importStar(require("path"));
20
+ const constants_1 = require("./constants");
21
+ /**
22
+ * Recursively walk a directory and return every file's {archivePath, absPath}
23
+ * pair. Archive paths use forward slashes regardless of host OS.
24
+ */
25
+ function listFilesForArchive(stageDir) {
26
+ const out = [];
27
+ const stack = [{ dir: stageDir, rel: '' }];
28
+ while (stack.length > 0) {
29
+ const next = stack.pop();
30
+ if (!next)
31
+ break;
32
+ const { dir, rel } = next;
33
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
34
+ for (const entry of entries) {
35
+ const abs = path.join(dir, entry.name);
36
+ const relPath = rel ? `${rel}/${entry.name}` : entry.name;
37
+ if (entry.isDirectory()) {
38
+ stack.push({ dir: abs, rel: relPath });
39
+ }
40
+ else if (entry.isFile()) {
41
+ out.push({ archivePath: relPath, absPath: abs });
42
+ }
43
+ // Symlinks and other types are deliberately skipped: MCPB archives are
44
+ // expected to be portable across Windows/macOS/Linux consumers.
45
+ }
46
+ }
47
+ return out.sort((a, b) => {
48
+ if (a.archivePath < b.archivePath)
49
+ return -1;
50
+ if (a.archivePath > b.archivePath)
51
+ return 1;
52
+ return 0;
53
+ });
54
+ }
55
+ /** Create a deterministic `.mcpb` archive from a staged directory. */
56
+ async function createDeterministicZip(stageDir, archivePath, options = {}) {
57
+ const yazl = require('yazl');
58
+ const { deterministic = true, compress = true } = options;
59
+ const files = listFilesForArchive(stageDir);
60
+ const zip = new yazl.ZipFile();
61
+ for (const { archivePath: rel, absPath } of files) {
62
+ zip.addFile(absPath, rel, {
63
+ mtime: deterministic ? constants_1.DETERMINISTIC_MTIME : undefined,
64
+ compress,
65
+ });
66
+ }
67
+ zip.end({ forceZip64Format: false });
68
+ fs.mkdirSync(path.dirname(archivePath), { recursive: true });
69
+ await new Promise((resolve, reject) => {
70
+ const out = fs.createWriteStream(archivePath);
71
+ out.on('error', reject);
72
+ out.on('close', () => resolve());
73
+ zip.outputStream.on('error', reject);
74
+ zip.outputStream.pipe(out);
75
+ });
76
+ const buf = fs.readFileSync(archivePath);
77
+ const sha256 = crypto.createHash('sha256').update(buf).digest('hex');
78
+ return {
79
+ archivePath,
80
+ size: buf.byteLength,
81
+ sha256,
82
+ entries: files.map((f) => f.archivePath),
83
+ };
84
+ }
85
+ //# sourceMappingURL=zip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"zip.js","sourceRoot":"","sources":["../../../../../src/commands/build/mcpb/zip.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Deterministic ZIP creation for MCPB archives.\n *\n * Determinism is achieved by:\n * - walking the stage directory in sorted order\n * - uniform mtime (`DETERMINISTIC_MTIME`) on every entry\n * - forceZip64Format: false (yazl default — keeps the archive small & portable)\n *\n * Two back-to-back builds of the same staged directory produce byte-identical\n * `.mcpb` archives with matching SHA-256 hashes.\n */\n\nimport * as crypto from 'crypto';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { DETERMINISTIC_MTIME } from './constants';\n\nexport interface ZipResult {\n /** Absolute path to the produced archive. */\n archivePath: string;\n /** Final archive size in bytes. */\n size: number;\n /** SHA-256 of the archive contents (lowercase hex). */\n sha256: string;\n /** Entry names in archive order (useful for tests/logging). */\n entries: string[];\n}\n\nexport interface ZipOptions {\n /** Apply DETERMINISTIC_MTIME to every entry. @default true */\n deterministic?: boolean;\n /** When true, deflate entries; when false, store uncompressed. @default true */\n compress?: boolean;\n}\n\n/**\n * Recursively walk a directory and return every file's {archivePath, absPath}\n * pair. Archive paths use forward slashes regardless of host OS.\n */\nexport function listFilesForArchive(\n stageDir: string,\n): Array<{ archivePath: string; absPath: string }> {\n const out: Array<{ archivePath: string; absPath: string }> = [];\n const stack: Array<{ dir: string; rel: string }> = [{ dir: stageDir, rel: '' }];\n while (stack.length > 0) {\n const next = stack.pop();\n if (!next) break;\n const { dir, rel } = next;\n const entries = fs.readdirSync(dir, { withFileTypes: true });\n for (const entry of entries) {\n const abs = path.join(dir, entry.name);\n const relPath = rel ? `${rel}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n stack.push({ dir: abs, rel: relPath });\n } else if (entry.isFile()) {\n out.push({ archivePath: relPath, absPath: abs });\n }\n // Symlinks and other types are deliberately skipped: MCPB archives are\n // expected to be portable across Windows/macOS/Linux consumers.\n }\n }\n return out.sort((a, b) => {\n if (a.archivePath < b.archivePath) return -1;\n if (a.archivePath > b.archivePath) return 1;\n return 0;\n });\n}\n\n/** Create a deterministic `.mcpb` archive from a staged directory. */\nexport async function createDeterministicZip(\n stageDir: string,\n archivePath: string,\n options: ZipOptions = {},\n): Promise<ZipResult> {\n const yazl = require('yazl') as typeof import('yazl');\n const { deterministic = true, compress = true } = options;\n\n const files = listFilesForArchive(stageDir);\n const zip = new yazl.ZipFile();\n\n for (const { archivePath: rel, absPath } of files) {\n zip.addFile(absPath, rel, {\n mtime: deterministic ? DETERMINISTIC_MTIME : undefined,\n compress,\n });\n }\n zip.end({ forceZip64Format: false });\n\n fs.mkdirSync(path.dirname(archivePath), { recursive: true });\n await new Promise<void>((resolve, reject) => {\n const out = fs.createWriteStream(archivePath);\n out.on('error', reject);\n out.on('close', () => resolve());\n zip.outputStream.on('error', reject);\n zip.outputStream.pipe(out);\n });\n\n const buf = fs.readFileSync(archivePath);\n const sha256 = crypto.createHash('sha256').update(buf).digest('hex');\n\n return {\n archivePath,\n size: buf.byteLength,\n sha256,\n entries: files.map((f) => f.archivePath),\n };\n}\n"]}
@@ -1,2 +1,2 @@
1
- import { Command } from 'commander';
1
+ import type { Command } from 'commander';
2
2
  export declare function registerBuildCommands(program: Command): void;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerBuildCommands = registerBuildCommands;
4
4
  const bridge_1 = require("../../core/bridge");
5
- const BUILD_TARGETS = ['cli', 'node', 'sdk', 'browser', 'cloudflare', 'vercel', 'lambda'];
5
+ const BUILD_TARGETS = ['cli', 'node', 'sdk', 'browser', 'cloudflare', 'vercel', 'lambda', 'distributed', 'mcpb'];
6
6
  function registerBuildCommands(program) {
7
7
  program
8
8
  .command('build')
@@ -11,6 +11,12 @@ function registerBuildCommands(program) {
11
11
  .option('--js', 'Output JS bundle instead of native binary (cli target only)')
12
12
  .option('-o, --out-dir <dir>', 'Output directory')
13
13
  .option('-e, --entry <path>', 'Manually specify entry file path')
14
+ // MCPB-specific flags
15
+ .option('--sea', 'Build an SEA binary for the host platform and embed it in the bundle (mcpb target)')
16
+ .option('--merge-from <dir>', 'Directory of pre-built cross-platform SEA binaries to merge (mcpb target)')
17
+ .option('--icon <path>', 'Override icon path (mcpb target)')
18
+ .option('--no-deterministic', 'Disable deterministic archive output (mcpb target)')
19
+ .option('--stage-only', 'Leave the MCPB staging directory intact and skip zipping (mcpb target)')
14
20
  .action(async (options) => {
15
21
  options.outDir = options.outDir || 'dist';
16
22
  const { runBuild } = await import('./index.js');
@@ -1 +1 @@
1
- {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../../src/commands/build/register.ts"],"names":[],"mappings":";;AAKA,sDAaC;AAjBD,8CAAiD;AAEjD,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAE1F,SAAgB,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,+BAA+B,CAAC;SAC5C,MAAM,CAAC,uBAAuB,EAAE,iBAAiB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;SAC5E,MAAM,CAAC,MAAM,EAAE,6DAA6D,CAAC;SAC7E,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;SACjD,MAAM,CAAC,oBAAoB,EAAE,kCAAkC,CAAC;SAChE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC;QAC1C,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,QAAQ,CAAC,IAAA,qBAAY,EAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["import { Command } from 'commander';\nimport { toParsedArgs } from '../../core/bridge';\n\nconst BUILD_TARGETS = ['cli', 'node', 'sdk', 'browser', 'cloudflare', 'vercel', 'lambda'];\n\nexport function registerBuildCommands(program: Command): void {\n program\n .command('build')\n .description('Build for a deployment target')\n .option('-t, --target <target>', `Build target: ${BUILD_TARGETS.join(', ')}`)\n .option('--js', 'Output JS bundle instead of native binary (cli target only)')\n .option('-o, --out-dir <dir>', 'Output directory')\n .option('-e, --entry <path>', 'Manually specify entry file path')\n .action(async (options) => {\n options.outDir = options.outDir || 'dist';\n const { runBuild } = await import('./index.js');\n await runBuild(toParsedArgs('build', [], options));\n });\n}\n"]}
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../../src/commands/build/register.ts"],"names":[],"mappings":";;AAKA,sDAmBC;AAvBD,8CAAiD;AAEjD,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC;AAEjH,SAAgB,qBAAqB,CAAC,OAAgB;IACpD,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,+BAA+B,CAAC;SAC5C,MAAM,CAAC,uBAAuB,EAAE,iBAAiB,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;SAC5E,MAAM,CAAC,MAAM,EAAE,6DAA6D,CAAC;SAC7E,MAAM,CAAC,qBAAqB,EAAE,kBAAkB,CAAC;SACjD,MAAM,CAAC,oBAAoB,EAAE,kCAAkC,CAAC;QACjE,sBAAsB;SACrB,MAAM,CAAC,OAAO,EAAE,oFAAoF,CAAC;SACrG,MAAM,CAAC,oBAAoB,EAAE,2EAA2E,CAAC;SACzG,MAAM,CAAC,eAAe,EAAE,kCAAkC,CAAC;SAC3D,MAAM,CAAC,oBAAoB,EAAE,oDAAoD,CAAC;SAClF,MAAM,CAAC,cAAc,EAAE,wEAAwE,CAAC;SAChG,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,OAAO,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAC;QAC1C,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,CAAC;QAChD,MAAM,QAAQ,CAAC,IAAA,qBAAY,EAAC,OAAO,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["import type { Command } from 'commander';\nimport { toParsedArgs } from '../../core/bridge';\n\nconst BUILD_TARGETS = ['cli', 'node', 'sdk', 'browser', 'cloudflare', 'vercel', 'lambda', 'distributed', 'mcpb'];\n\nexport function registerBuildCommands(program: Command): void {\n program\n .command('build')\n .description('Build for a deployment target')\n .option('-t, --target <target>', `Build target: ${BUILD_TARGETS.join(', ')}`)\n .option('--js', 'Output JS bundle instead of native binary (cli target only)')\n .option('-o, --out-dir <dir>', 'Output directory')\n .option('-e, --entry <path>', 'Manually specify entry file path')\n // MCPB-specific flags\n .option('--sea', 'Build an SEA binary for the host platform and embed it in the bundle (mcpb target)')\n .option('--merge-from <dir>', 'Directory of pre-built cross-platform SEA binaries to merge (mcpb target)')\n .option('--icon <path>', 'Override icon path (mcpb target)')\n .option('--no-deterministic', 'Disable deterministic archive output (mcpb target)')\n .option('--stage-only', 'Leave the MCPB staging directory intact and skip zipping (mcpb target)')\n .action(async (options) => {\n options.outDir = options.outDir || 'dist';\n const { runBuild } = await import('./index.js');\n await runBuild(toParsedArgs('build', [], options));\n });\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { ParsedArgs } from '../../../core/args';
1
+ import { type ParsedArgs } from '../../../core/args';
2
2
  /**
3
3
  * Build a direct-client SDK library for Node.js applications.
4
4
  *
@@ -49,7 +49,7 @@ async function buildSdk(opts) {
49
49
  sourcemap: true,
50
50
  external: [
51
51
  '@frontmcp/sdk', '@frontmcp/di', '@frontmcp/utils',
52
- '@frontmcp/auth', '@frontmcp/adapters', '@frontmcp/plugins',
52
+ '@frontmcp/auth', '@frontmcp/adapters', '@frontmcp/lazy-zod',
53
53
  'reflect-metadata', 'better-sqlite3', 'fsevents',
54
54
  // Keep user's dependencies external
55
55
  ...Object.keys(pkg.dependencies || {}),
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/commands/build/sdk/index.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\nimport { ParsedArgs } from '../../../core/args';\nimport { c } from '../../../core/colors';\nimport { ensureDir, runCmd } from '@frontmcp/utils';\nimport { resolveEntry } from '../../../shared/fs';\n\n/**\n * Build a direct-client SDK library for Node.js applications.\n *\n * Produces CJS + ESM dual output with type declarations so the server\n * can be consumed as a library dependency (e.g., `import { DirectMcpServer } from './my-mcp'`).\n *\n * @example\n * ```bash\n * frontmcp build --target sdk\n * ```\n */\nexport async function buildSdk(opts: ParsedArgs): Promise<void> {\n const cwd = process.cwd();\n const entry = await resolveEntry(cwd, opts.entry);\n const outDir = path.resolve(cwd, opts.outDir || 'dist');\n await ensureDir(outDir);\n\n console.log(`${c('cyan', '[build:sdk]')} entry: ${path.relative(cwd, entry)}`);\n console.log(`${c('cyan', '[build:sdk]')} outDir: ${path.relative(cwd, outDir)}`);\n\n // Step 1: Compile TypeScript with declaration emit\n console.log(c('cyan', '[build:sdk] Compiling TypeScript...'));\n const tscArgs = [\n '-y', 'tsc',\n '--project', path.join(cwd, 'tsconfig.json'),\n '--outDir', outDir,\n '--declaration', '--declarationMap',\n '--skipLibCheck',\n ];\n await runCmd('npx', tscArgs);\n\n // Step 2: Bundle CJS with esbuild\n console.log(c('cyan', '[build:sdk] Bundling CJS...'));\n const esbuild = await import('esbuild');\n const pkg = require(path.join(cwd, 'package.json'));\n const appName = pkg.name || path.basename(cwd);\n\n const sharedBuildOptions: import('esbuild').BuildOptions = {\n entryPoints: [entry],\n bundle: true,\n platform: 'node',\n target: 'node22',\n keepNames: true,\n treeShaking: true,\n sourcemap: true,\n external: [\n '@frontmcp/sdk', '@frontmcp/di', '@frontmcp/utils',\n '@frontmcp/auth', '@frontmcp/adapters', '@frontmcp/plugins',\n 'reflect-metadata', 'better-sqlite3', 'fsevents',\n // Keep user's dependencies external\n ...Object.keys(pkg.dependencies || {}),\n ...Object.keys(pkg.peerDependencies || {}),\n ],\n };\n\n await esbuild.build({\n ...sharedBuildOptions,\n format: 'cjs',\n outfile: path.join(outDir, `${appName}.cjs.js`),\n });\n\n // Step 3: Bundle ESM\n console.log(c('cyan', '[build:sdk] Bundling ESM...'));\n await esbuild.build({\n ...sharedBuildOptions,\n format: 'esm',\n outfile: path.join(outDir, `${appName}.esm.mjs`),\n });\n\n console.log(c('green', `\\n SDK build complete:`));\n console.log(c('gray', ` CJS: ${appName}.cjs.js`));\n console.log(c('gray', ` ESM: ${appName}.esm.mjs`));\n console.log(c('gray', ` Types: *.d.ts`));\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/commands/build/sdk/index.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from 'path';\nimport { type ParsedArgs } from '../../../core/args';\nimport { c } from '../../../core/colors';\nimport { ensureDir, runCmd } from '@frontmcp/utils';\nimport { resolveEntry } from '../../../shared/fs';\n\n/**\n * Build a direct-client SDK library for Node.js applications.\n *\n * Produces CJS + ESM dual output with type declarations so the server\n * can be consumed as a library dependency (e.g., `import { DirectMcpServer } from './my-mcp'`).\n *\n * @example\n * ```bash\n * frontmcp build --target sdk\n * ```\n */\nexport async function buildSdk(opts: ParsedArgs): Promise<void> {\n const cwd = process.cwd();\n const entry = await resolveEntry(cwd, opts.entry);\n const outDir = path.resolve(cwd, opts.outDir || 'dist');\n await ensureDir(outDir);\n\n console.log(`${c('cyan', '[build:sdk]')} entry: ${path.relative(cwd, entry)}`);\n console.log(`${c('cyan', '[build:sdk]')} outDir: ${path.relative(cwd, outDir)}`);\n\n // Step 1: Compile TypeScript with declaration emit\n console.log(c('cyan', '[build:sdk] Compiling TypeScript...'));\n const tscArgs = [\n '-y', 'tsc',\n '--project', path.join(cwd, 'tsconfig.json'),\n '--outDir', outDir,\n '--declaration', '--declarationMap',\n '--skipLibCheck',\n ];\n await runCmd('npx', tscArgs);\n\n // Step 2: Bundle CJS with esbuild\n console.log(c('cyan', '[build:sdk] Bundling CJS...'));\n const esbuild = await import('esbuild');\n const pkg = require(path.join(cwd, 'package.json'));\n const appName = pkg.name || path.basename(cwd);\n\n const sharedBuildOptions: import('esbuild').BuildOptions = {\n entryPoints: [entry],\n bundle: true,\n platform: 'node',\n target: 'node22',\n keepNames: true,\n treeShaking: true,\n sourcemap: true,\n external: [\n '@frontmcp/sdk', '@frontmcp/di', '@frontmcp/utils',\n '@frontmcp/auth', '@frontmcp/adapters', '@frontmcp/lazy-zod',\n 'reflect-metadata', 'better-sqlite3', 'fsevents',\n // Keep user's dependencies external\n ...Object.keys(pkg.dependencies || {}),\n ...Object.keys(pkg.peerDependencies || {}),\n ],\n };\n\n await esbuild.build({\n ...sharedBuildOptions,\n format: 'cjs',\n outfile: path.join(outDir, `${appName}.cjs.js`),\n });\n\n // Step 3: Bundle ESM\n console.log(c('cyan', '[build:sdk] Bundling ESM...'));\n await esbuild.build({\n ...sharedBuildOptions,\n format: 'esm',\n outfile: path.join(outDir, `${appName}.esm.mjs`),\n });\n\n console.log(c('green', `\\n SDK build complete:`));\n console.log(c('gray', ` CJS: ${appName}.cjs.js`));\n console.log(c('gray', ` ESM: ${appName}.esm.mjs`));\n console.log(c('gray', ` Types: *.d.ts`));\n}\n"]}
@@ -45,4 +45,4 @@ export type AdapterTemplate = {
45
45
  */
46
46
  postBundle?: (outDir: string, cwd: string, bundleOutput: string) => Promise<void>;
47
47
  };
48
- export type AdapterName = 'node' | 'vercel' | 'lambda' | 'cloudflare';
48
+ export type AdapterName = 'node' | 'vercel' | 'lambda' | 'cloudflare' | 'distributed';
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/commands/build/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration for a deployment adapter.\n * Each adapter defines how to compile and package the FrontMCP server\n * for a specific deployment target.\n */\nexport type AdapterTemplate = {\n /** Module format for TypeScript compilation */\n moduleFormat: 'commonjs' | 'esnext';\n\n /**\n * Generate the entry point file content.\n * @param mainModulePath - Relative path to the compiled main module (e.g., './main.js')\n * @returns The content for index.js, or empty string if no wrapper needed\n */\n getEntryTemplate: (mainModulePath: string) => string;\n\n /**\n * Generate the serverless setup file content.\n * This file is imported first to set environment variables before decorators run.\n * @returns The content for serverless-setup.js, or undefined if not needed\n */\n getSetupTemplate?: () => string;\n\n /**\n * Whether to bundle the output with rspack.\n * Recommended for serverless deployments to avoid ESM/CJS issues.\n */\n shouldBundle?: boolean;\n\n /**\n * Output filename for the bundled file (e.g., 'handler.cjs').\n * Only used when shouldBundle is true.\n */\n bundleOutput?: string;\n\n /**\n * Generate the deployment platform config file content.\n * @param cwd - Current working directory (for detecting package manager, etc.)\n * @returns Object (for JSON) or string (for TOML/YAML)\n */\n getConfig?: (cwd: string) => object | string;\n\n /** Name of the config file (e.g., 'vercel.json', 'wrangler.toml') */\n configFileName?: string;\n\n /**\n * Post-bundle hook for creating deployment-specific output structure.\n * Called after bundling is complete.\n * @param outDir - The output directory (e.g., 'dist')\n * @param cwd - Current working directory\n * @param bundleOutput - Name of the bundled file (e.g., 'handler.cjs')\n */\n postBundle?: (outDir: string, cwd: string, bundleOutput: string) => Promise<void>;\n};\n\nexport type AdapterName = 'node' | 'vercel' | 'lambda' | 'cloudflare';\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/commands/build/types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Configuration for a deployment adapter.\n * Each adapter defines how to compile and package the FrontMCP server\n * for a specific deployment target.\n */\nexport type AdapterTemplate = {\n /** Module format for TypeScript compilation */\n moduleFormat: 'commonjs' | 'esnext';\n\n /**\n * Generate the entry point file content.\n * @param mainModulePath - Relative path to the compiled main module (e.g., './main.js')\n * @returns The content for index.js, or empty string if no wrapper needed\n */\n getEntryTemplate: (mainModulePath: string) => string;\n\n /**\n * Generate the serverless setup file content.\n * This file is imported first to set environment variables before decorators run.\n * @returns The content for serverless-setup.js, or undefined if not needed\n */\n getSetupTemplate?: () => string;\n\n /**\n * Whether to bundle the output with rspack.\n * Recommended for serverless deployments to avoid ESM/CJS issues.\n */\n shouldBundle?: boolean;\n\n /**\n * Output filename for the bundled file (e.g., 'handler.cjs').\n * Only used when shouldBundle is true.\n */\n bundleOutput?: string;\n\n /**\n * Generate the deployment platform config file content.\n * @param cwd - Current working directory (for detecting package manager, etc.)\n * @returns Object (for JSON) or string (for TOML/YAML)\n */\n getConfig?: (cwd: string) => object | string;\n\n /** Name of the config file (e.g., 'vercel.json', 'wrangler.toml') */\n configFileName?: string;\n\n /**\n * Post-bundle hook for creating deployment-specific output structure.\n * Called after bundling is complete.\n * @param outDir - The output directory (e.g., 'dist')\n * @param cwd - Current working directory\n * @param bundleOutput - Name of the bundled file (e.g., 'handler.cjs')\n */\n postBundle?: (outDir: string, cwd: string, bundleOutput: string) => Promise<void>;\n};\n\nexport type AdapterName = 'node' | 'vercel' | 'lambda' | 'cloudflare' | 'distributed';\n"]}
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerMcpbCommands(program: Command): void;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerMcpbCommands = registerMcpbCommands;
4
+ function registerMcpbCommands(program) {
5
+ const mcpb = program.command('mcpb').description('Manage MCPB (MCP Bundle) archives');
6
+ mcpb
7
+ .command('validate <path>')
8
+ .description('Validate a .mcpb archive against the MCPB v0.3 specification')
9
+ .action(async (archivePath) => {
10
+ const { runValidate } = await import('./validate.js');
11
+ await runValidate(archivePath);
12
+ });
13
+ }
14
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../../../src/commands/mcpb/register.ts"],"names":[],"mappings":";;AAEA,oDAUC;AAVD,SAAgB,oBAAoB,CAAC,OAAgB;IACnD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,mCAAmC,CAAC,CAAC;IAEtF,IAAI;SACD,OAAO,CAAC,iBAAiB,CAAC;SAC1B,WAAW,CAAC,8DAA8D,CAAC;SAC3E,MAAM,CAAC,KAAK,EAAE,WAAmB,EAAE,EAAE;QACpC,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;QACtD,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACP,CAAC","sourcesContent":["import type { Command } from 'commander';\n\nexport function registerMcpbCommands(program: Command): void {\n const mcpb = program.command('mcpb').description('Manage MCPB (MCP Bundle) archives');\n\n mcpb\n .command('validate <path>')\n .description('Validate a .mcpb archive against the MCPB v0.3 specification')\n .action(async (archivePath: string) => {\n const { runValidate } = await import('./validate.js');\n await runValidate(archivePath);\n });\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function runValidate(archivePath: string): Promise<void>;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runValidate = runValidate;
4
+ const tslib_1 = require("tslib");
5
+ const path = tslib_1.__importStar(require("path"));
6
+ const colors_1 = require("../../core/colors");
7
+ const validate_1 = require("../build/mcpb/validate");
8
+ async function runValidate(archivePath) {
9
+ const abs = path.resolve(process.cwd(), archivePath);
10
+ console.log(`${(0, colors_1.c)('cyan', '[mcpb:validate]')} ${path.relative(process.cwd(), abs)}`);
11
+ const result = await (0, validate_1.validateMcpb)(abs);
12
+ if (result.manifest) {
13
+ console.log(`${(0, colors_1.c)('gray', '[mcpb:validate]')} name=${result.manifest.name} version=${result.manifest.version}`);
14
+ console.log(`${(0, colors_1.c)('gray', '[mcpb:validate]')} tools=${result.manifest.tools?.length ?? 0} prompts=${result.manifest.prompts?.length ?? 0} user_config=${Object.keys(result.manifest.user_config ?? {}).length}`);
15
+ }
16
+ for (const w of result.warnings) {
17
+ console.log(`${(0, colors_1.c)('yellow', '[mcpb:validate]')} warning: ${w}`);
18
+ }
19
+ if (result.ok) {
20
+ console.log(`${(0, colors_1.c)('green', '[mcpb:validate]')} archive is valid`);
21
+ return;
22
+ }
23
+ for (const e of result.errors) {
24
+ console.error(`${(0, colors_1.c)('red', '[mcpb:validate]')} ${e}`);
25
+ }
26
+ throw new Error(`MCPB validation failed (${result.errors.length} error${result.errors.length === 1 ? '' : 's'})`);
27
+ }
28
+ //# sourceMappingURL=validate.js.map