@shippp/mcp 0.2.0 → 0.3.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.
- package/dist/index.js +290 -99
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,7 +18,208 @@ function loadConfig() {
|
|
|
18
18
|
return { apiUrl, apiKey };
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// ../../src/lib/repoSync/wireReceipt.ts
|
|
22
|
+
import { createHash } from "node:crypto";
|
|
23
|
+
import { z as z2 } from "zod";
|
|
24
|
+
|
|
25
|
+
// ../../src/lib/repoSync/receiptContract.ts
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
var hash = z.string().regex(/^sha256:[0-9a-f]{64}$/);
|
|
28
|
+
var receiptFileEntrySchema = z.object({
|
|
29
|
+
path: z.string().min(1),
|
|
30
|
+
hash,
|
|
31
|
+
bytes: z.number().int().nonnegative()
|
|
32
|
+
});
|
|
33
|
+
var syncReceiptSchema = z.object({
|
|
34
|
+
shippp_receipt: z.literal("v2"),
|
|
35
|
+
// `revision`/`versionId`: MC-9.2 kompatibilis bővítés — hiányuk a bővítés
|
|
36
|
+
// előtti receipt érvényes olvasata; a zod-objektum strippelne, ezért a
|
|
37
|
+
// mezők itt is deklaráltak, hogy a repóba írt receipt megőrizze őket.
|
|
38
|
+
source: z.object({
|
|
39
|
+
file: z.string(),
|
|
40
|
+
unit: z.string(),
|
|
41
|
+
version: hash,
|
|
42
|
+
revision: z.number().int().nullable().optional(),
|
|
43
|
+
versionId: z.string().optional()
|
|
44
|
+
}),
|
|
45
|
+
target: z.object({
|
|
46
|
+
outputProfile: z.string().min(1),
|
|
47
|
+
profileSchemaVersion: z.number().int().positive(),
|
|
48
|
+
adapterVersion: z.string().min(1),
|
|
49
|
+
packagerVersion: z.string().min(1),
|
|
50
|
+
packages: z.object({
|
|
51
|
+
dependencies: z.record(z.string(), z.string()),
|
|
52
|
+
devDependencies: z.record(z.string(), z.string())
|
|
53
|
+
})
|
|
54
|
+
}),
|
|
55
|
+
config: z.object({ engine: z.string().min(1), irVersion: z.string().min(1), tokens: hash }),
|
|
56
|
+
output: z.object({ hash, files: z.array(receiptFileEntrySchema) }),
|
|
57
|
+
reproducible: z.literal(true),
|
|
58
|
+
generated_at: z.string()
|
|
59
|
+
});
|
|
60
|
+
var ReceiptParseError = class extends Error {
|
|
61
|
+
};
|
|
62
|
+
function parseSyncReceipt(raw) {
|
|
63
|
+
let json;
|
|
64
|
+
try {
|
|
65
|
+
json = JSON.parse(raw);
|
|
66
|
+
} catch {
|
|
67
|
+
throw new ReceiptParseError("A receipt nem \xE9rv\xE9nyes JSON.");
|
|
68
|
+
}
|
|
69
|
+
const parsed = syncReceiptSchema.safeParse(json);
|
|
70
|
+
if (!parsed.success) {
|
|
71
|
+
throw new ReceiptParseError(
|
|
72
|
+
`A receipt nem felel meg a v2 kontraktusnak: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return parsed.data;
|
|
76
|
+
}
|
|
77
|
+
function serializeSyncReceipt(receipt) {
|
|
78
|
+
return `${JSON.stringify(receipt, null, 2)}
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
var compilerEnvOfReceipt = (r) => ({
|
|
82
|
+
engine: r.config.engine,
|
|
83
|
+
irVersion: r.config.irVersion,
|
|
84
|
+
adapterVersion: r.target.adapterVersion,
|
|
85
|
+
packagerVersion: r.target.packagerVersion
|
|
86
|
+
});
|
|
87
|
+
function compilerDeltaOf(previous, next) {
|
|
88
|
+
const delta = [];
|
|
89
|
+
if (previous.config.engine !== next.engine) delta.push(`engine: ${previous.config.engine} \u2192 ${next.engine}`);
|
|
90
|
+
if (previous.config.irVersion !== next.irVersion) delta.push(`IR: ${previous.config.irVersion} \u2192 ${next.irVersion}`);
|
|
91
|
+
if (previous.target.adapterVersion !== next.adapterVersion) {
|
|
92
|
+
delta.push(`adapter: ${previous.target.adapterVersion} \u2192 ${next.adapterVersion}`);
|
|
93
|
+
}
|
|
94
|
+
if (previous.target.packagerVersion !== next.packagerVersion) {
|
|
95
|
+
delta.push(`packager: ${previous.target.packagerVersion} \u2192 ${next.packagerVersion}`);
|
|
96
|
+
}
|
|
97
|
+
return delta;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ../../src/lib/repoSync/wireReceipt.ts
|
|
101
|
+
var wireReceiptRefEntrySchema = z2.object({
|
|
102
|
+
path: z2.string().min(1),
|
|
103
|
+
bytes: z2.number().int().nonnegative(),
|
|
104
|
+
ref: z2.object({ uploadId: z2.string().min(1) })
|
|
105
|
+
});
|
|
106
|
+
var wireReceiptOutputEntrySchema = z2.union([receiptFileEntrySchema, wireReceiptRefEntrySchema]);
|
|
107
|
+
var wireSyncReceiptSchema = syncReceiptSchema.extend({
|
|
108
|
+
output: z2.object({ hash, files: z2.array(wireReceiptOutputEntrySchema) })
|
|
109
|
+
});
|
|
110
|
+
var WireReceiptError = class extends Error {
|
|
111
|
+
};
|
|
112
|
+
function parseWireSyncReceipt(raw) {
|
|
113
|
+
let json;
|
|
114
|
+
try {
|
|
115
|
+
json = JSON.parse(raw);
|
|
116
|
+
} catch {
|
|
117
|
+
throw new WireReceiptError("A wire-receipt nem \xE9rv\xE9nyes JSON.");
|
|
118
|
+
}
|
|
119
|
+
const parsed = wireSyncReceiptSchema.safeParse(json);
|
|
120
|
+
if (!parsed.success) {
|
|
121
|
+
throw new WireReceiptError(
|
|
122
|
+
`A wire-receipt nem felel meg a v2 kontraktusnak: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return parsed.data;
|
|
126
|
+
}
|
|
127
|
+
var sha = (s) => `sha256:${createHash("sha256").update(s).digest("hex")}`;
|
|
128
|
+
function outputCanonicalOf(entries) {
|
|
129
|
+
return [...entries].sort((a, b) => a.path.localeCompare(b.path)).map((e) => "ref" in e ? `${e.path}\0ref:${e.ref.uploadId}:${e.bytes}` : `${e.path}\0${e.hash}`).join("\0");
|
|
130
|
+
}
|
|
131
|
+
function outputHashOfEntries(entries) {
|
|
132
|
+
return sha(outputCanonicalOf(entries));
|
|
133
|
+
}
|
|
134
|
+
function completeWireReceipt(wire, bytesByPath) {
|
|
135
|
+
const files = wire.output.files.map((entry) => {
|
|
136
|
+
if (!("ref" in entry)) return entry;
|
|
137
|
+
const bytes = bytesByPath.get(entry.path);
|
|
138
|
+
if (!bytes) {
|
|
139
|
+
throw new WireReceiptError(`A wire-receipt ref-sora feloldatlan maradt: ${entry.path} (${entry.ref.uploadId}).`);
|
|
140
|
+
}
|
|
141
|
+
if (bytes.byteLength !== entry.bytes) {
|
|
142
|
+
throw new WireReceiptError(
|
|
143
|
+
`A feloldott asset m\xE9rete nem egyezik a receipt kontraktus\xE1val: ${entry.path} \u2014 v\xE1rt ${entry.bytes}, kapott ${bytes.byteLength} b\xE1jt.`
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return { path: entry.path, hash: sha(bytes), bytes: bytes.byteLength };
|
|
147
|
+
});
|
|
148
|
+
const completed = { ...wire, output: { hash: outputHashOfEntries(files), files } };
|
|
149
|
+
const parsed = syncReceiptSchema.safeParse(completed);
|
|
150
|
+
if (!parsed.success) {
|
|
151
|
+
throw new WireReceiptError(
|
|
152
|
+
`A kieg\xE9sz\xEDtett receipt nem felel meg a sync-kontraktusnak: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return parsed.data;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ../../src/lib/repoSync/exportArtifactContract.ts
|
|
159
|
+
var ExportArtifactContractError = class extends Error {
|
|
160
|
+
};
|
|
161
|
+
function bytesOfBase64(b64) {
|
|
162
|
+
if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
|
|
163
|
+
return new Uint8Array(Buffer.from(b64, "base64"));
|
|
164
|
+
}
|
|
165
|
+
const binary = atob(b64);
|
|
166
|
+
const out = new Uint8Array(binary.length);
|
|
167
|
+
for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i);
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
function inlineFileOf(entry) {
|
|
171
|
+
if (typeof entry.path !== "string" || typeof entry.content !== "string") {
|
|
172
|
+
throw new ExportArtifactContractError("The export artifact container has a malformed file row.");
|
|
173
|
+
}
|
|
174
|
+
return entry.encoding === "base64" ? { path: entry.path, content: bytesOfBase64(entry.content) } : { path: entry.path, content: entry.content };
|
|
175
|
+
}
|
|
176
|
+
function refOf(entry) {
|
|
177
|
+
const ref = entry.ref;
|
|
178
|
+
if (typeof entry.path !== "string" || !ref || typeof ref.uploadId !== "string" || typeof ref.downloadPath !== "string" || typeof ref.bytes !== "number" || !Number.isFinite(ref.bytes) || ref.bytes < 0) {
|
|
179
|
+
throw new ExportArtifactContractError("The export artifact container has a malformed asset-reference row.");
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
path: entry.path,
|
|
183
|
+
uploadId: ref.uploadId,
|
|
184
|
+
downloadPath: ref.downloadPath,
|
|
185
|
+
bytes: ref.bytes,
|
|
186
|
+
contentType: typeof ref.contentType === "string" ? ref.contentType : null
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function parseExportArtifactJson(json) {
|
|
190
|
+
let parsed;
|
|
191
|
+
try {
|
|
192
|
+
parsed = JSON.parse(json);
|
|
193
|
+
} catch {
|
|
194
|
+
throw new ExportArtifactContractError("The export artifact container is not valid JSON.");
|
|
195
|
+
}
|
|
196
|
+
const container = parsed;
|
|
197
|
+
const version = container.shippp_artifact;
|
|
198
|
+
if (version !== "v1" && version !== "v2" || !Array.isArray(container.files)) {
|
|
199
|
+
throw new ExportArtifactContractError(
|
|
200
|
+
`Unknown export artifact container shape (shippp_artifact: ${JSON.stringify(version ?? null)}) \u2014 this reader supports v1 and v2.`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
const files = [];
|
|
204
|
+
const refs = [];
|
|
205
|
+
for (const raw of container.files) {
|
|
206
|
+
if (raw?.encoding === "ref") {
|
|
207
|
+
if (version === "v1") {
|
|
208
|
+
throw new ExportArtifactContractError("A v1 export artifact container cannot carry asset-reference rows.");
|
|
209
|
+
}
|
|
210
|
+
refs.push(refOf(raw));
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
files.push(inlineFileOf(raw ?? {}));
|
|
214
|
+
}
|
|
215
|
+
return { version, files, refs };
|
|
216
|
+
}
|
|
217
|
+
|
|
21
218
|
// src/client.ts
|
|
219
|
+
var REQUEST_TIMEOUT_MS = 6e4;
|
|
220
|
+
var GENERATE_TIMEOUT_MS = 12e4;
|
|
221
|
+
var ASSET_FETCH_TIMEOUT_MS = 18e4;
|
|
222
|
+
var ASSET_FETCH_CONCURRENCY = 4;
|
|
22
223
|
var ShipppApiError = class extends Error {
|
|
23
224
|
constructor(status, message) {
|
|
24
225
|
super(message);
|
|
@@ -26,20 +227,16 @@ var ShipppApiError = class extends Error {
|
|
|
26
227
|
this.name = "ShipppApiError";
|
|
27
228
|
}
|
|
28
229
|
};
|
|
29
|
-
function decodeFiles(files) {
|
|
30
|
-
return files.map(
|
|
31
|
-
(f) => f.encoding === "base64" ? { path: f.path, content: new Uint8Array(Buffer.from(f.content, "base64")) } : { path: f.path, content: f.content }
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
230
|
var ShipppClient = class {
|
|
35
231
|
constructor(config) {
|
|
36
232
|
this.config = config;
|
|
37
233
|
}
|
|
38
|
-
async request(path2, init) {
|
|
234
|
+
async request(path2, init, timeoutMs = REQUEST_TIMEOUT_MS) {
|
|
39
235
|
let res;
|
|
40
236
|
try {
|
|
41
237
|
res = await fetch(`${this.config.apiUrl}${path2}`, {
|
|
42
238
|
...init,
|
|
239
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
43
240
|
headers: {
|
|
44
241
|
Authorization: `Bearer ${this.config.apiKey}`,
|
|
45
242
|
"Content-Type": "application/json",
|
|
@@ -47,7 +244,11 @@ var ShipppClient = class {
|
|
|
47
244
|
}
|
|
48
245
|
});
|
|
49
246
|
} catch (e) {
|
|
50
|
-
|
|
247
|
+
const err = e;
|
|
248
|
+
if (err.name === "TimeoutError") {
|
|
249
|
+
throw new ShipppApiError(0, `Shippp did not answer ${path2} within ${Math.round(timeoutMs / 1e3)}s.`);
|
|
250
|
+
}
|
|
251
|
+
throw new ShipppApiError(0, `Could not reach Shippp at ${this.config.apiUrl}: ${err.message}`);
|
|
51
252
|
}
|
|
52
253
|
const body = await res.json().catch(() => ({}));
|
|
53
254
|
if (!res.ok) throw new ShipppApiError(res.status, body?.error || `HTTP ${res.status}`);
|
|
@@ -57,42 +258,98 @@ var ShipppClient = class {
|
|
|
57
258
|
manifest(fileId2) {
|
|
58
259
|
return this.request(`/api/mcp/manifest?fileId=${encodeURIComponent(fileId2)}`, { method: "GET" });
|
|
59
260
|
}
|
|
60
|
-
/**
|
|
261
|
+
/** Resolves the versioned (v2) asset references of a generate response:
|
|
262
|
+
* each referenced binary is fetched from its authorized download path with
|
|
263
|
+
* the same Bearer credential, under bounded parallelism and a per-asset
|
|
264
|
+
* deadline, and must match the reference's byte size exactly — a partial
|
|
265
|
+
* result is never a success; the failure names every affected asset. */
|
|
266
|
+
async downloadAssetRefs(refs) {
|
|
267
|
+
const out = /* @__PURE__ */ new Map();
|
|
268
|
+
const failures = [];
|
|
269
|
+
let next = 0;
|
|
270
|
+
const worker = async () => {
|
|
271
|
+
for (; ; ) {
|
|
272
|
+
const i = next++;
|
|
273
|
+
if (i >= refs.length) return;
|
|
274
|
+
const ref = refs[i];
|
|
275
|
+
try {
|
|
276
|
+
const res = await fetch(`${this.config.apiUrl}${ref.downloadPath}`, {
|
|
277
|
+
headers: { Authorization: `Bearer ${this.config.apiKey}` },
|
|
278
|
+
signal: AbortSignal.timeout(ASSET_FETCH_TIMEOUT_MS)
|
|
279
|
+
});
|
|
280
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
281
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
282
|
+
if (bytes.byteLength !== ref.bytes) {
|
|
283
|
+
throw new Error(`size mismatch: expected ${ref.bytes} bytes, got ${bytes.byteLength}`);
|
|
284
|
+
}
|
|
285
|
+
out.set(ref.path, bytes);
|
|
286
|
+
} catch (e) {
|
|
287
|
+
const err = e;
|
|
288
|
+
failures.push(`${ref.path} (${err.name === "TimeoutError" ? "timed out" : err.message})`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
await Promise.all(Array.from({ length: Math.min(ASSET_FETCH_CONCURRENCY, refs.length) }, worker));
|
|
293
|
+
if (failures.length > 0) {
|
|
294
|
+
throw new ShipppApiError(
|
|
295
|
+
0,
|
|
296
|
+
`Failed to download ${failures.length} of ${refs.length} referenced export assets: ${failures.join("; ")}`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
return out;
|
|
300
|
+
}
|
|
301
|
+
/** Generate code for a unit (or a diff against a baseline). This client
|
|
302
|
+
* declares the versioned asset-reference capability (artifact contract v2):
|
|
303
|
+
* above the server's inline budget the image mass arrives as authorized
|
|
304
|
+
* download references, which are resolved here into byte-complete files and
|
|
305
|
+
* a hash-complete receipt — identical to what an inline delivery would have
|
|
306
|
+
* produced, so the sync core and every consumer see one artifact shape. */
|
|
61
307
|
async generate(fileId2, request, baseline) {
|
|
62
308
|
const body = await this.request(
|
|
63
309
|
"/api/mcp/generate",
|
|
64
310
|
{
|
|
65
311
|
method: "POST",
|
|
66
|
-
body: JSON.stringify(
|
|
67
|
-
|
|
312
|
+
body: JSON.stringify(
|
|
313
|
+
baseline ? { fileId: fileId2, request, baseline, acceptAssetRefs: true } : { fileId: fileId2, request, acceptAssetRefs: true }
|
|
314
|
+
)
|
|
315
|
+
},
|
|
316
|
+
GENERATE_TIMEOUT_MS
|
|
68
317
|
);
|
|
69
|
-
if ("files" in body) return
|
|
70
|
-
|
|
318
|
+
if (!("files" in body)) return body;
|
|
319
|
+
const { shippp_artifact, files: rawFiles, receipt: rawReceipt, ...rest } = body;
|
|
320
|
+
const parsed = parseExportArtifactJson(JSON.stringify({ shippp_artifact: shippp_artifact ?? "v1", files: rawFiles }));
|
|
321
|
+
if (parsed.refs.length === 0) {
|
|
322
|
+
return { ...rest, files: parsed.files, receipt: rawReceipt };
|
|
323
|
+
}
|
|
324
|
+
const bytesByPath = await this.downloadAssetRefs(parsed.refs);
|
|
325
|
+
const receipt = completeWireReceipt(parseWireSyncReceipt(JSON.stringify(rawReceipt)), bytesByPath);
|
|
326
|
+
const resolved = parsed.refs.map((r) => ({ path: r.path, content: bytesByPath.get(r.path) }));
|
|
327
|
+
return { ...rest, files: [...parsed.files, ...resolved], receipt };
|
|
71
328
|
}
|
|
72
329
|
};
|
|
73
330
|
|
|
74
331
|
// src/tools.ts
|
|
75
|
-
import { z as
|
|
332
|
+
import { z as z4 } from "zod";
|
|
76
333
|
|
|
77
334
|
// src/runner.ts
|
|
78
335
|
import { exec } from "node:child_process";
|
|
79
336
|
import { promisify } from "node:util";
|
|
80
337
|
|
|
81
338
|
// ../../src/lib/repoSync/applySync.ts
|
|
82
|
-
import { createHash } from "node:crypto";
|
|
339
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
83
340
|
|
|
84
341
|
// ../../src/lib/repoSync/targetConfig.ts
|
|
85
|
-
import { z } from "zod";
|
|
342
|
+
import { z as z3 } from "zod";
|
|
86
343
|
var TARGET_CONFIG_PATH = "shippp.target.json";
|
|
87
344
|
var TARGET_CONFIG_SCHEMA_VERSION = 1;
|
|
88
345
|
var DEFAULT_SYNC_UNIT = "project";
|
|
89
|
-
var targetConfigSchema =
|
|
90
|
-
schemaVersion:
|
|
91
|
-
profile:
|
|
92
|
-
profileSchemaVersion:
|
|
93
|
-
generatedRoot:
|
|
94
|
-
project:
|
|
95
|
-
unit:
|
|
346
|
+
var targetConfigSchema = z3.object({
|
|
347
|
+
schemaVersion: z3.literal(TARGET_CONFIG_SCHEMA_VERSION),
|
|
348
|
+
profile: z3.string().min(1),
|
|
349
|
+
profileSchemaVersion: z3.number().int().positive(),
|
|
350
|
+
generatedRoot: z3.string().min(1),
|
|
351
|
+
project: z3.object({ fileId: z3.string().min(1), name: z3.string().min(1) }).optional(),
|
|
352
|
+
unit: z3.enum(["project", "design-system"]).optional()
|
|
96
353
|
});
|
|
97
354
|
var TargetConfigError = class extends Error {
|
|
98
355
|
};
|
|
@@ -120,72 +377,6 @@ function parseTargetConfig(raw) {
|
|
|
120
377
|
return parsed.data;
|
|
121
378
|
}
|
|
122
379
|
|
|
123
|
-
// ../../src/lib/repoSync/receiptContract.ts
|
|
124
|
-
import { z as z2 } from "zod";
|
|
125
|
-
var hash = z2.string().regex(/^sha256:[0-9a-f]{64}$/);
|
|
126
|
-
var receiptFileEntrySchema = z2.object({
|
|
127
|
-
path: z2.string().min(1),
|
|
128
|
-
hash,
|
|
129
|
-
bytes: z2.number().int().nonnegative()
|
|
130
|
-
});
|
|
131
|
-
var syncReceiptSchema = z2.object({
|
|
132
|
-
shippp_receipt: z2.literal("v2"),
|
|
133
|
-
source: z2.object({ file: z2.string(), unit: z2.string(), version: hash }),
|
|
134
|
-
target: z2.object({
|
|
135
|
-
outputProfile: z2.string().min(1),
|
|
136
|
-
profileSchemaVersion: z2.number().int().positive(),
|
|
137
|
-
adapterVersion: z2.string().min(1),
|
|
138
|
-
packagerVersion: z2.string().min(1),
|
|
139
|
-
packages: z2.object({
|
|
140
|
-
dependencies: z2.record(z2.string(), z2.string()),
|
|
141
|
-
devDependencies: z2.record(z2.string(), z2.string())
|
|
142
|
-
})
|
|
143
|
-
}),
|
|
144
|
-
config: z2.object({ engine: z2.string().min(1), irVersion: z2.string().min(1), tokens: hash }),
|
|
145
|
-
output: z2.object({ hash, files: z2.array(receiptFileEntrySchema) }),
|
|
146
|
-
reproducible: z2.literal(true),
|
|
147
|
-
generated_at: z2.string()
|
|
148
|
-
});
|
|
149
|
-
var ReceiptParseError = class extends Error {
|
|
150
|
-
};
|
|
151
|
-
function parseSyncReceipt(raw) {
|
|
152
|
-
let json;
|
|
153
|
-
try {
|
|
154
|
-
json = JSON.parse(raw);
|
|
155
|
-
} catch {
|
|
156
|
-
throw new ReceiptParseError("A receipt nem \xE9rv\xE9nyes JSON.");
|
|
157
|
-
}
|
|
158
|
-
const parsed = syncReceiptSchema.safeParse(json);
|
|
159
|
-
if (!parsed.success) {
|
|
160
|
-
throw new ReceiptParseError(
|
|
161
|
-
`A receipt nem felel meg a v2 kontraktusnak: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
return parsed.data;
|
|
165
|
-
}
|
|
166
|
-
function serializeSyncReceipt(receipt) {
|
|
167
|
-
return `${JSON.stringify(receipt, null, 2)}
|
|
168
|
-
`;
|
|
169
|
-
}
|
|
170
|
-
var compilerEnvOfReceipt = (r) => ({
|
|
171
|
-
engine: r.config.engine,
|
|
172
|
-
irVersion: r.config.irVersion,
|
|
173
|
-
adapterVersion: r.target.adapterVersion,
|
|
174
|
-
packagerVersion: r.target.packagerVersion
|
|
175
|
-
});
|
|
176
|
-
function compilerDeltaOf(previous, next) {
|
|
177
|
-
const delta = [];
|
|
178
|
-
if (previous.config.engine !== next.engine) delta.push(`engine: ${previous.config.engine} \u2192 ${next.engine}`);
|
|
179
|
-
if (previous.config.irVersion !== next.irVersion) delta.push(`IR: ${previous.config.irVersion} \u2192 ${next.irVersion}`);
|
|
180
|
-
if (previous.target.adapterVersion !== next.adapterVersion) {
|
|
181
|
-
delta.push(`adapter: ${previous.target.adapterVersion} \u2192 ${next.adapterVersion}`);
|
|
182
|
-
}
|
|
183
|
-
if (previous.target.packagerVersion !== next.packagerVersion) {
|
|
184
|
-
delta.push(`packager: ${previous.target.packagerVersion} \u2192 ${next.packagerVersion}`);
|
|
185
|
-
}
|
|
186
|
-
return delta;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
380
|
// ../../src/lib/repoSync/syncPlan.ts
|
|
190
381
|
function computeSyncPlan(input) {
|
|
191
382
|
const entries = [];
|
|
@@ -234,7 +425,7 @@ function computeSyncPlan(input) {
|
|
|
234
425
|
|
|
235
426
|
// ../../src/lib/repoSync/applySync.ts
|
|
236
427
|
var RECEIPT_FILENAME = "shippp.receipt.json";
|
|
237
|
-
var sha256 = (content) => `sha256:${
|
|
428
|
+
var sha256 = (content) => `sha256:${createHash2("sha256").update(content).digest("hex")}`;
|
|
238
429
|
var refusal = (code, message, conflicts) => ({
|
|
239
430
|
status: "refused",
|
|
240
431
|
code,
|
|
@@ -588,7 +779,7 @@ ${changed}
|
|
|
588
779
|
--- RECEIPT ---
|
|
589
780
|
${JSON.stringify(result.receipt, null, 2)}`;
|
|
590
781
|
}
|
|
591
|
-
var fileId =
|
|
782
|
+
var fileId = z4.string().min(1).describe("The Shippp file id (from the file's URL, or shippp_list_units).");
|
|
592
783
|
function registerTools(server, client) {
|
|
593
784
|
server.registerTool(
|
|
594
785
|
"shippp_list_units",
|
|
@@ -613,8 +804,8 @@ function registerTools(server, client) {
|
|
|
613
804
|
description: "Get ONE component's deterministic Chakra UI code + a reproducibility receipt. `unitType` is 'master' (a design-system component) or 'screen' (a depth-0 artboard). Get ids from shippp_list_units.",
|
|
614
805
|
inputSchema: {
|
|
615
806
|
fileId,
|
|
616
|
-
unitId:
|
|
617
|
-
unitType:
|
|
807
|
+
unitId: z4.string().min(1).describe("The master/screen id from shippp_list_units."),
|
|
808
|
+
unitType: z4.enum(["master", "screen"])
|
|
618
809
|
}
|
|
619
810
|
},
|
|
620
811
|
async ({ fileId: fileId2, unitId, unitType }) => {
|
|
@@ -677,10 +868,10 @@ function registerTools(server, client) {
|
|
|
677
868
|
description: "Diff your CURRENT code against a fresh deterministic generation of a unit \u2014 shows exactly what a designer changed. Pass the same unit params as the get tools, plus `baseline` (the files you currently have for that unit).",
|
|
678
869
|
inputSchema: {
|
|
679
870
|
fileId,
|
|
680
|
-
unit:
|
|
681
|
-
unitId:
|
|
682
|
-
unitType:
|
|
683
|
-
baseline:
|
|
871
|
+
unit: z4.enum(["tokens", "design-system", "project", "component"]),
|
|
872
|
+
unitId: z4.string().optional().describe("Required when unit='component'."),
|
|
873
|
+
unitType: z4.enum(["master", "screen"]).optional().describe("Required when unit='component'."),
|
|
874
|
+
baseline: z4.array(z4.object({ path: z4.string(), content: z4.string() })).describe("Your current files for this unit (path + content).")
|
|
684
875
|
}
|
|
685
876
|
},
|
|
686
877
|
async ({ fileId: fileId2, unit, unitId, unitType, baseline }) => {
|
|
@@ -721,10 +912,10 @@ function registerTools(server, client) {
|
|
|
721
912
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
722
913
|
description: "Run the full repository sync with the bundled deterministic runner: fetch the bound unit's artifact, plan against the real on-disk state, apply atomically with rollback, run the repo's checks (SHIPPP_SYNC_CHECKS env), and write the receipt ONLY on full success. Writes are confined to the generated root of the repo's own binding; hand-modified generated files are structured conflicts and are never overwritten. If the plan or a refusal reports a compilerDelta, present it to the user and re-call with approvedCompilerDelta set to EXACTLY those lines ONLY after their explicit approval. After an approved re-bind, pass acceptReboundBaseline to adopt the previous binding's receipt as baseline.",
|
|
723
914
|
inputSchema: {
|
|
724
|
-
approvedCompilerDelta:
|
|
915
|
+
approvedCompilerDelta: z4.array(z4.string().min(1).max(500)).max(20).optional().describe(
|
|
725
916
|
"The exact compilerDelta lines the user explicitly approved \u2014 a mismatch with the current delta refuses."
|
|
726
917
|
),
|
|
727
|
-
acceptReboundBaseline:
|
|
918
|
+
acceptReboundBaseline: z4.boolean().optional().describe("Explicit adoption of a previous binding's leftover receipt as hash baseline after a re-bind.")
|
|
728
919
|
}
|
|
729
920
|
},
|
|
730
921
|
async ({ approvedCompilerDelta, acceptReboundBaseline }) => {
|
|
@@ -801,7 +992,7 @@ async function main() {
|
|
|
801
992
|
process.exit(1);
|
|
802
993
|
}
|
|
803
994
|
const config = loadConfig();
|
|
804
|
-
const server = new McpServer({ name: "shippp", version: "0.
|
|
995
|
+
const server = new McpServer({ name: "shippp", version: "0.3.0" }, { instructions: INSTRUCTIONS });
|
|
805
996
|
registerTools(server, new ShipppClient(config));
|
|
806
997
|
await server.connect(new StdioServerTransport());
|
|
807
998
|
console.error(`[shippp-mcp] connected via stdio \u2192 ${config.apiUrl}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shippp/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"mcpName": "ai.shippp/mcp",
|
|
5
5
|
"description": "Shippp MCP server + local sync runner — pull deterministic, production-perfect UI code from your Shippp design files, and keep your repository current over the receipt-tracked sync.",
|
|
6
6
|
"type": "module",
|