meguro-mcp 0.2.13 → 0.2.14
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/CHANGELOG.md +9 -0
- package/README.md +11 -10
- package/package.json +1 -1
- package/src/docs.mjs +1533 -98
- package/src/protocol.mjs +1 -1
- package/src/server.mjs +1 -1
- package/src/tools.mjs +318 -79
package/src/tools.mjs
CHANGED
|
@@ -28,6 +28,7 @@ const DOCUMENTATION_TOOL_CONTRACT = documentationToolContract();
|
|
|
28
28
|
const DOCUMENTATION_TOPICS = new Set(DOCUMENTATION_TOOL_CONTRACT.topics);
|
|
29
29
|
const FLEET_TOOL_NAMES = new Set([
|
|
30
30
|
'templates_list', 'template_get', 'stores_list', 'store_create', 'store_delete', 'store_passport',
|
|
31
|
+
'sample_store_reset',
|
|
31
32
|
'practice_runs_list',
|
|
32
33
|
'workspaces_list', 'workspace_create', 'workspace_archive', 'workspace_unarchive',
|
|
33
34
|
'catalog_slice_read', 'catalog_slice_snapshot', 'catalog_slices_saved',
|
|
@@ -40,6 +41,7 @@ const TOOL_PRESENTATION = Object.freeze({
|
|
|
40
41
|
store_create: { title: 'Create a practice store', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
41
42
|
store_delete: { title: 'Delete a practice store', readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false },
|
|
42
43
|
store_passport: { title: 'Read Store Passport', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
44
|
+
sample_store_reset: { title: 'Repair the sample practice store', readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
43
45
|
workspaces_list: { title: 'List account workspaces', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
44
46
|
workspace_create: { title: 'Create a workspace', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
45
47
|
workspace_archive: { title: 'Archive a workspace', readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
@@ -63,7 +65,7 @@ const TOOL_PRESENTATION = Object.freeze({
|
|
|
63
65
|
twin_diff: { title: 'Read a twin impact receipt', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
64
66
|
docs_read: { title: 'Read Meguro product and evidence documentation', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
65
67
|
exam_preflight: { title: 'Check Shopify Exam readiness', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
66
|
-
exam_start: { title: 'Start or continue a Shopify Exam', readOnlyHint: false, destructiveHint: false, idempotentHint:
|
|
68
|
+
exam_start: { title: 'Start or continue a Shopify Exam', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
|
|
67
69
|
exam_status: { title: 'Read Shopify Exam status', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
68
70
|
exam_report: { title: 'Read a Shopify Exam receipt', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
69
71
|
practice_run_start: { title: 'Start a practice run', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
@@ -72,7 +74,7 @@ const TOOL_PRESENTATION = Object.freeze({
|
|
|
72
74
|
practice_run_checkpoint: { title: 'Capture a practice-run checkpoint', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
73
75
|
practice_run_advance: { title: 'Advance store time', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
74
76
|
practice_run_finish: { title: 'Finish a practice run', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
75
|
-
practice_run_report: { title: 'Read a
|
|
77
|
+
practice_run_report: { title: 'Read a practice-run receipt', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
76
78
|
practice_run_impact: { title: 'Read a practice-run impact receipt', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
77
79
|
get_connection_details: { title: 'Get practice-store connection details', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
78
80
|
admin_probe: { title: 'Run an Admin API probe', readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
@@ -204,7 +206,42 @@ function storeDeletePreviewProjection(value) {
|
|
|
204
206
|
const safe = secretSafe(value);
|
|
205
207
|
if (!safe || typeof safe !== 'object' || typeof value?.confirmToken !== 'string') return safe;
|
|
206
208
|
const confirmToken = value.confirmToken.trim();
|
|
207
|
-
|
|
209
|
+
if (!/^mcpdel_[A-Za-z0-9_-]{20,128}$/u.test(confirmToken)) return safe;
|
|
210
|
+
const nextStep = value?.nextStep;
|
|
211
|
+
const calls = Array.isArray(nextStep?.validNextCalls) ? nextStep.validNextCalls : [];
|
|
212
|
+
const confirmationCall = calls.length === 1 ? calls[0] : null;
|
|
213
|
+
const confirmationArguments = confirmationCall?.arguments;
|
|
214
|
+
const workspaceId = typeof confirmationArguments?.workspaceId === 'string'
|
|
215
|
+
&& /^[a-z0-9][a-z0-9-]{0,127}$/u.test(confirmationArguments.workspaceId)
|
|
216
|
+
? confirmationArguments.workspaceId
|
|
217
|
+
: undefined;
|
|
218
|
+
const projectedNextStep = nextStep?.schemaVersion === 'meguro.admin-execution-next-step.v1'
|
|
219
|
+
&& Number.isInteger(nextStep.order)
|
|
220
|
+
&& typeof nextStep.id === 'string'
|
|
221
|
+
&& ['required', 'ready', 'blocked'].includes(nextStep.status)
|
|
222
|
+
&& confirmationCall?.plane === 'mcp-control'
|
|
223
|
+
&& confirmationCall.tool === 'store_delete'
|
|
224
|
+
&& typeof confirmationCall.purpose === 'string'
|
|
225
|
+
&& typeof confirmationArguments?.storeId === 'string'
|
|
226
|
+
&& confirmationArguments.confirmToken === confirmToken
|
|
227
|
+
? {
|
|
228
|
+
schemaVersion: nextStep.schemaVersion,
|
|
229
|
+
order: nextStep.order,
|
|
230
|
+
id: nextStep.id,
|
|
231
|
+
status: nextStep.status,
|
|
232
|
+
validNextCalls: [{
|
|
233
|
+
plane: 'mcp-control',
|
|
234
|
+
tool: 'store_delete',
|
|
235
|
+
arguments: {
|
|
236
|
+
storeId: confirmationArguments.storeId,
|
|
237
|
+
confirmToken,
|
|
238
|
+
...(workspaceId ? { workspaceId } : {}),
|
|
239
|
+
},
|
|
240
|
+
purpose: confirmationCall.purpose,
|
|
241
|
+
}],
|
|
242
|
+
}
|
|
243
|
+
: undefined;
|
|
244
|
+
return { ...safe, confirmToken, ...(projectedNextStep ? { nextStep: projectedNextStep } : {}) };
|
|
208
245
|
}
|
|
209
246
|
|
|
210
247
|
// A catalog snapshot token is the explicit output capability of the snapshot routes: Console passes
|
|
@@ -315,6 +352,9 @@ function storeTemplateDetailProjection(value) {
|
|
|
315
352
|
storeTemplate: Object.fromEntries(STORE_TEMPLATE_FIELDS
|
|
316
353
|
.filter((field) => Object.hasOwn(storeTemplate, field))
|
|
317
354
|
.map((field) => [field, secretSafe(storeTemplate[field], 1)])),
|
|
355
|
+
...(value?.nextStep && typeof value.nextStep === 'object'
|
|
356
|
+
? { nextStep: secretSafe(value.nextStep, 1) }
|
|
357
|
+
: {}),
|
|
318
358
|
};
|
|
319
359
|
}
|
|
320
360
|
|
|
@@ -471,6 +511,42 @@ function storeTemplatesProjection(value) {
|
|
|
471
511
|
...(recommendationUnavailable ? { recommendationUnavailable } : {}),
|
|
472
512
|
},
|
|
473
513
|
storeTemplates,
|
|
514
|
+
...(value?.nextStep && typeof value.nextStep === 'object'
|
|
515
|
+
? { nextStep: secretSafe(value.nextStep, 1) }
|
|
516
|
+
: {}),
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Anonymous catalog callers do not have an account whose tier or current metering allowance can
|
|
522
|
+
* truthfully be named. Keep this as the one exclusion authority for the anonymous projection so a
|
|
523
|
+
* new account-derived plan field fails visibly into the authenticated shape until it is classified
|
|
524
|
+
* here, rather than being removed by scattered response rewrites.
|
|
525
|
+
*/
|
|
526
|
+
const ANONYMOUS_TEMPLATE_ACCOUNT_FIELDS = Object.freeze([
|
|
527
|
+
'tier',
|
|
528
|
+
'authorization',
|
|
529
|
+
'usage',
|
|
530
|
+
'runStartAllowed',
|
|
531
|
+
'runStartAllowedMeaning',
|
|
532
|
+
'runStartAllowance',
|
|
533
|
+
'remainingSimulationRuns',
|
|
534
|
+
'remainingAuthorizedDaysAfterFirstSegment',
|
|
535
|
+
]);
|
|
536
|
+
const ANONYMOUS_ACCOUNT_ALLOWANCES = 'Account-specific allowances appear after sign-in.';
|
|
537
|
+
|
|
538
|
+
function withoutAnonymousTemplateAccountFields(value) {
|
|
539
|
+
if (Array.isArray(value)) return value.map(withoutAnonymousTemplateAccountFields);
|
|
540
|
+
if (!value || typeof value !== 'object') return value;
|
|
541
|
+
return Object.fromEntries(Object.entries(value)
|
|
542
|
+
.filter(([field]) => !ANONYMOUS_TEMPLATE_ACCOUNT_FIELDS.includes(field))
|
|
543
|
+
.map(([field, child]) => [field, withoutAnonymousTemplateAccountFields(child)]));
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function anonymousStoreTemplatesProjection(value) {
|
|
547
|
+
return {
|
|
548
|
+
...withoutAnonymousTemplateAccountFields(storeTemplatesProjection(value)),
|
|
549
|
+
accountAllowances: ANONYMOUS_ACCOUNT_ALLOWANCES,
|
|
474
550
|
};
|
|
475
551
|
}
|
|
476
552
|
|
|
@@ -505,7 +581,12 @@ function practiceErrorResult(status, value, retryAfterSeconds, teaching, declare
|
|
|
505
581
|
if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds >= 0) stable.retryAfterSeconds = retryAfterSeconds;
|
|
506
582
|
else if (Number.isFinite(bodyRetryAfter) && bodyRetryAfter >= 0) stable.retryAfterSeconds = bodyRetryAfter;
|
|
507
583
|
if (typeof body.retryable === 'boolean') stable.retryable = body.retryable;
|
|
508
|
-
|
|
584
|
+
const routeTeaching = body.teaching && typeof body.teaching === 'object' ? body.teaching : undefined;
|
|
585
|
+
if (teaching ?? routeTeaching) stable.teaching = secretSafe(teaching ?? routeTeaching);
|
|
586
|
+
if (Array.isArray(body.validNextCalls)) stable.validNextCalls = secretSafe(body.validNextCalls);
|
|
587
|
+
const briefField = railBrief(body.brief);
|
|
588
|
+
if (briefField.brief !== undefined) stable.brief = secretSafe(briefField.brief);
|
|
589
|
+
if (body.nextStep && typeof body.nextStep === 'object') stable.nextStep = secretSafe(body.nextStep);
|
|
509
590
|
const result = { content: [{ type: 'text', text: JSON.stringify(stable, null, 2) }], isError: true };
|
|
510
591
|
// MEG-1057: an attempt-identity 404 is pre-commit by construction — nothing can have applied
|
|
511
592
|
// against an attempt that does not exist. Without this the mutating routes fell through to the
|
|
@@ -520,7 +601,7 @@ function practiceErrorResult(status, value, retryAfterSeconds, teaching, declare
|
|
|
520
601
|
|
|
521
602
|
function practiceRunUnavailableTeaching(toolName, attemptId) {
|
|
522
603
|
return {
|
|
523
|
-
meaning: `No practice simulation
|
|
604
|
+
meaning: `No practice simulation run with attemptId ${attemptId} is available to this account. ${toolName} accepts only a pa-* attemptId returned by practice_run_start.`,
|
|
524
605
|
nextStep: `Call stores_list({}), choose an exact current storeId, call practice_run_start({ storeId: "<exact storeId>" }), use the returned attemptId, then call ${toolName}({ attemptId: "<returned attemptId>" }).`,
|
|
525
606
|
stopCondition: `If stores_list({}) returns no usable practice store, stop. Do not retry the unavailable attemptId ${attemptId}.`,
|
|
526
607
|
};
|
|
@@ -566,6 +647,17 @@ function validatedStoreId(value, key) {
|
|
|
566
647
|
return value;
|
|
567
648
|
}
|
|
568
649
|
|
|
650
|
+
/**
|
|
651
|
+
* MEG-1571: `detail` selects the bounded receipt comparison card (default) or the full document.
|
|
652
|
+
* An unknown value fails closed here rather than reaching the API as a silent card request.
|
|
653
|
+
*/
|
|
654
|
+
function requiredReceiptDetail(args) {
|
|
655
|
+
const detail = args?.detail;
|
|
656
|
+
if (detail === undefined) return 'card';
|
|
657
|
+
if (detail !== 'card' && detail !== 'full') throw new Error("detail must be 'card' or 'full'");
|
|
658
|
+
return detail;
|
|
659
|
+
}
|
|
660
|
+
|
|
569
661
|
function optionalWorkspaceId(args) {
|
|
570
662
|
if (args?.workspaceId === undefined) return undefined;
|
|
571
663
|
const workspaceId = requiredString(args, 'workspaceId');
|
|
@@ -631,6 +723,8 @@ const CATALOG_SNAPSHOT_INPUT_PROPERTIES = Object.freeze({
|
|
|
631
723
|
const CATALOG_SAVED_SLICES_INPUT_PROPERTIES = Object.freeze({
|
|
632
724
|
workspaceId: { type: 'string', minLength: 1, description: 'Optional account-owned workspace selector. Omit for Default.' },
|
|
633
725
|
action: { type: 'string', enum: ['list', 'get', 'save', 'delete', 'refresh', 'changes'] },
|
|
726
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, description: 'Maximum saved catalog-slice rows in this page. Defaults to 100 for action=list.' },
|
|
727
|
+
cursor: { type: 'string', minLength: 1, description: 'Opaque nextCursor returned by the preceding action=list page.' },
|
|
634
728
|
shopDomain: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*\\.myshopify\\.com$', description: 'Required for save.' },
|
|
635
729
|
label: { type: 'string', minLength: 1, maxLength: 80, description: 'Required for save.' },
|
|
636
730
|
variantIds: { type: 'array', minItems: 1, maxItems: 25, items: { type: 'string', minLength: 1 }, description: 'Required for save.' },
|
|
@@ -699,7 +793,7 @@ const CATALOG_SAVED_SLICES_BRANCHES = Object.freeze([
|
|
|
699
793
|
action: 'save',
|
|
700
794
|
mutates: true,
|
|
701
795
|
required: ['action', 'shopDomain', 'label', 'variantIds'],
|
|
702
|
-
forbidden: ['sliceId'],
|
|
796
|
+
forbidden: ['limit', 'cursor', 'sliceId'],
|
|
703
797
|
example: {
|
|
704
798
|
action: 'save',
|
|
705
799
|
shopDomain: 'merchant.myshopify.com',
|
|
@@ -712,7 +806,7 @@ const CATALOG_SAVED_SLICES_BRANCHES = Object.freeze([
|
|
|
712
806
|
action,
|
|
713
807
|
mutates: action === 'delete' || action === 'refresh',
|
|
714
808
|
required: ['action', 'sliceId'],
|
|
715
|
-
forbidden: ['shopDomain', 'label', 'variantIds'],
|
|
809
|
+
forbidden: ['limit', 'cursor', 'shopDomain', 'label', 'variantIds'],
|
|
716
810
|
example: { action, sliceId: 'csl_saved_01234567' },
|
|
717
811
|
})),
|
|
718
812
|
]);
|
|
@@ -851,7 +945,6 @@ const PRACTICE_RUN_UNTIL_INPUT_SCHEMA = Object.freeze({
|
|
|
851
945
|
}, ['sku', 'threshold']),
|
|
852
946
|
practiceRunUntilConditionInputSchema('order-recorded'),
|
|
853
947
|
practiceRunUntilConditionInputSchema('return-recorded'),
|
|
854
|
-
practiceRunUntilConditionInputSchema('return-request-pending'),
|
|
855
948
|
practiceRunUntilConditionInputSchema('incoming-inventory-landed', {
|
|
856
949
|
sku: { type: 'string', minLength: 1, maxLength: 100 },
|
|
857
950
|
}, ['sku']),
|
|
@@ -1111,18 +1204,7 @@ function receiptProjection(value) {
|
|
|
1111
1204
|
const practiceRun = attempt.practiceRun ?? value?.practiceRun ?? null;
|
|
1112
1205
|
const calls = Array.isArray(value?.events?.calls) ? value.events.calls : [];
|
|
1113
1206
|
const actions = Array.isArray(value?.events?.actions) ? value.events.actions : [];
|
|
1114
|
-
const
|
|
1115
|
-
? value.receiptEvidence.effectiveAssertions.slice(0, 50).map((assertion) => ({
|
|
1116
|
-
id: assertion.id,
|
|
1117
|
-
status: assertion.statusLabel,
|
|
1118
|
-
observedStatus: assertion.observedStatus,
|
|
1119
|
-
evaluationSubject: assertion.evaluationSubject,
|
|
1120
|
-
rejectionResponsibleLayers: assertion.rejectionResponsibleLayers ?? [],
|
|
1121
|
-
ownershipStatement: assertion.ownershipStatement ?? null,
|
|
1122
|
-
label: assertion.label,
|
|
1123
|
-
detail: assertion.detail,
|
|
1124
|
-
}))
|
|
1125
|
-
: [];
|
|
1207
|
+
const assertionOutcomes = value?.assertionOutcomes;
|
|
1126
1208
|
const verdict = value?.report?.verdict && typeof value.report.verdict === 'object'
|
|
1127
1209
|
? Object.fromEntries(['status', 'outcome', 'grade', 'score', 'headline', 'horizonDays']
|
|
1128
1210
|
.filter((key) => value.report.verdict[key] !== undefined)
|
|
@@ -1169,8 +1251,21 @@ function receiptProjection(value) {
|
|
|
1169
1251
|
const gate = value?.gate ?? value?.report?.gate;
|
|
1170
1252
|
const rejectionAttribution = value?.rejectionAttribution ?? value?.report?.rejectionAttribution;
|
|
1171
1253
|
const reportBack = value?.reportBack ?? value?.report?.reportBack;
|
|
1254
|
+
// MEG-1386: selected from the server-compiled Conduct plane the report-back already carries. MCP
|
|
1255
|
+
// resolves nothing here and derives nothing; it selects the one object or publishes its absence.
|
|
1256
|
+
const conductAuthority = Array.isArray(reportBack?.planes)
|
|
1257
|
+
? reportBack.planes.find((plane) => plane?.plane === 'conduct')?.facts?.authority
|
|
1258
|
+
?? reportBack.planes.find((plane) => plane?.id === 'conduct')?.facts?.authority
|
|
1259
|
+
: undefined;
|
|
1172
1260
|
const takeHome = value?.takeHome ?? value?.report?.takeHome;
|
|
1173
1261
|
const eventResolution = value?.eventResolution ?? value?.report?.eventResolution;
|
|
1262
|
+
// MEG-1364: the declared modeled mechanisms and what this run measured about each one, carried
|
|
1263
|
+
// whole. This projection used to drop the only per-mechanism values in the payload, so a caller's
|
|
1264
|
+
// model was handed a headline about a bundle store with nothing about bundles in it — and no way
|
|
1265
|
+
// to tell a mechanism that measured zero from one that is not modeled. Read from the report's one
|
|
1266
|
+
// mechanism location, which the server fills for graded and ungraded conclusions alike; selecting
|
|
1267
|
+
// members here would be a second authority over the same table.
|
|
1268
|
+
const modeledMechanisms = value?.report?.modeledMechanisms;
|
|
1174
1269
|
return secretSafe({
|
|
1175
1270
|
schemaVersion: value?.schemaVersion,
|
|
1176
1271
|
storeIdentity: value.storeIdentity,
|
|
@@ -1183,25 +1278,40 @@ function receiptProjection(value) {
|
|
|
1183
1278
|
summary: {
|
|
1184
1279
|
calls: footprint?.callCount ?? calls.length,
|
|
1185
1280
|
actions: footprint?.recordedActionCount ?? actions.length,
|
|
1186
|
-
|
|
1281
|
+
// MEG-1386: the server's one compiled Conduct authority, not a second count. This line used
|
|
1282
|
+
// to re-scan the call rows here, so a rejected READ arrived at a caller's model as an
|
|
1283
|
+
// undifferentiated rejected call while the receipt beside it said something else. Absent
|
|
1284
|
+
// authority publishes null rather than a locally reconstructed number.
|
|
1285
|
+
rejectedCalls: conductAuthority ? conductAuthority.rejections.recorded : null,
|
|
1286
|
+
rejectedReads: conductAuthority ? conductAuthority.rejections.rejectedReads : null,
|
|
1287
|
+
blockedWrites: conductAuthority ? conductAuthority.rejections.rejectedMutations : null,
|
|
1187
1288
|
acceptedWrites: footprint?.acceptedWrites ?? calls.filter((call) => Number(call?.actionCount ?? 0) > 0).length,
|
|
1188
1289
|
...(footprint?.unmeasuredActionCount ? { unmeasuredActions: footprint.unmeasuredActionCount } : {}),
|
|
1189
1290
|
},
|
|
1291
|
+
// The complete compiled object, passed through unchanged: exact rejected-read and blocked-write
|
|
1292
|
+
// counts, explicit recovery links, and the compact named behavior and blocked-attempt rows with
|
|
1293
|
+
// their occurrence counts and recorder call references.
|
|
1294
|
+
conductAuthority: conductAuthority ?? null,
|
|
1190
1295
|
receipt: {
|
|
1191
1296
|
summary: value?.receiptEvidence?.summary,
|
|
1192
|
-
assertions,
|
|
1193
1297
|
...(value?.receiptEvidence?.expectedRefusals ? { expectedRefusals: value.receiptEvidence.expectedRefusals } : {}),
|
|
1194
1298
|
},
|
|
1299
|
+
...(assertionOutcomes && typeof assertionOutcomes === 'object' ? { assertionOutcomes } : {}),
|
|
1195
1300
|
changes: Array.isArray(value?.changes) ? value.changes : [],
|
|
1196
1301
|
...(defaultVerdict && typeof defaultVerdict === 'object' ? { defaultVerdict } : {}),
|
|
1197
1302
|
...(resultSemantics && typeof resultSemantics === 'object' ? { resultSemantics } : {}),
|
|
1198
1303
|
...(gate && typeof gate === 'object' ? { gate } : {}),
|
|
1199
1304
|
...(rejectionAttribution && typeof rejectionAttribution === 'object' ? { rejectionAttribution } : {}),
|
|
1200
|
-
...(reportBack?.schemaVersion === 'meguro.receipt-report-back.
|
|
1201
|
-
...(takeHome?.schemaVersion === 'meguro.receipt-take-home.
|
|
1305
|
+
...(reportBack?.schemaVersion === 'meguro.receipt-report-back.v5' ? { reportBack } : {}),
|
|
1306
|
+
...(takeHome?.schemaVersion === 'meguro.receipt-take-home.v3' ? { takeHome } : {}),
|
|
1202
1307
|
...(eventResolution?.schemaVersion === 'meguro.template-event-resolution.v1' ? { eventResolution } : {}),
|
|
1308
|
+
// Absence is PUBLISHED as null rather than dropped: a silently missing table reads to a model
|
|
1309
|
+
// exactly like a store that models no mechanism, which is the confusion this field exists to end.
|
|
1310
|
+
modeledMechanisms: modeledMechanisms && typeof modeledMechanisms === 'object' ? modeledMechanisms : null,
|
|
1203
1311
|
...(verdict ? { verdict } : {}),
|
|
1204
1312
|
...(measurement ? { measurement } : {}),
|
|
1313
|
+
...railBrief(value?.brief),
|
|
1314
|
+
...(value?.nextStep && typeof value.nextStep === 'object' ? { nextStep: value.nextStep } : {}),
|
|
1205
1315
|
...(value?.publicReceipts?.run ? {
|
|
1206
1316
|
publicReceipt: value.publicReceipts.run,
|
|
1207
1317
|
deployGate: 'Gate a deploy by fetching publicReceipt.canonicalPath as the complete unauthenticated HTTPS URL and verifying the SHA-256 of its exact response bytes equals publicReceipt.sha256. Repeat the same check for impactPublicReceipt when present.',
|
|
@@ -1241,8 +1351,8 @@ function runsListProjection(value, lifecycle) {
|
|
|
1241
1351
|
}),
|
|
1242
1352
|
...(runs.length === 0 ? {
|
|
1243
1353
|
emptyState: {
|
|
1244
|
-
message: `No ${lifecycle ?? 'matching'} Shopify dev-store history runs were found. This says nothing about practice simulation
|
|
1245
|
-
practiceAttemptRecovery: 'Call practice_runs_list to discover practice simulation
|
|
1354
|
+
message: `No ${lifecycle ?? 'matching'} Shopify dev-store history runs were found. This says nothing about practice simulation runs.`,
|
|
1355
|
+
practiceAttemptRecovery: 'Call practice_runs_list to discover practice simulation runs, then pass one exact attemptId to practice_run_report or practice_run_impact.',
|
|
1246
1356
|
},
|
|
1247
1357
|
} : {}),
|
|
1248
1358
|
clockDiscipline: 'runClock names lifecycle timestamps; worldClock names scenario coordinates and does not invent an unreported Store-time.',
|
|
@@ -1311,13 +1421,25 @@ function practiceRunsProjection(value, storeId, storeSelection, workspaceId) {
|
|
|
1311
1421
|
if (!Number.isSafeInteger(value?.total) || value.total < 0) {
|
|
1312
1422
|
throw new Error('Meguro practice runs response did not contain a valid total');
|
|
1313
1423
|
}
|
|
1424
|
+
if (value?.nextCursor !== null && value?.nextCursor !== undefined && typeof value.nextCursor !== 'string') {
|
|
1425
|
+
throw new Error('Meguro practice runs response did not contain a valid nextCursor');
|
|
1426
|
+
}
|
|
1314
1427
|
return secretSafe({
|
|
1315
1428
|
schemaVersion: 'meguro.practice-run-list.v1',
|
|
1316
1429
|
storeId,
|
|
1317
1430
|
storeSelection,
|
|
1318
1431
|
total: value.total,
|
|
1432
|
+
nextCursor: value?.nextCursor ?? null,
|
|
1319
1433
|
attempts: attempts
|
|
1320
|
-
|
|
1434
|
+
// MEG-1571: a completed run's item IS the server-owned receipt comparison card, so comparing
|
|
1435
|
+
// N runs is this one call. MCP passes it through and derives no comparison fact of its own.
|
|
1436
|
+
.map((attempt) => (attempt.comparisonCard ? attempt.comparisonCard
|
|
1437
|
+
: typeof attempt.comparisonCardUnavailableReason === 'string' ? {
|
|
1438
|
+
attemptId: attempt.attemptId,
|
|
1439
|
+
code: 'receipt-comparison-card-unavailable',
|
|
1440
|
+
reason: attempt.comparisonCardUnavailableReason,
|
|
1441
|
+
}
|
|
1442
|
+
: {
|
|
1321
1443
|
attemptId: attempt.attemptId,
|
|
1322
1444
|
state: typeof attempt.practiceRun?.state === 'string' ? attempt.practiceRun.state : null,
|
|
1323
1445
|
clock: {
|
|
@@ -1332,6 +1454,8 @@ function practiceRunsProjection(value, storeId, storeSelection, workspaceId) {
|
|
|
1332
1454
|
attemptId: attempt.attemptId,
|
|
1333
1455
|
...(workspaceId ? { workspaceId } : {}),
|
|
1334
1456
|
},
|
|
1457
|
+
...railBrief(attempt.brief),
|
|
1458
|
+
...(attempt.nextStep && typeof attempt.nextStep === 'object' ? { nextStep: attempt.nextStep } : {}),
|
|
1335
1459
|
})),
|
|
1336
1460
|
});
|
|
1337
1461
|
}
|
|
@@ -1347,10 +1471,10 @@ function practiceAttemptComparisonGuidanceFor(toolName, a, b) {
|
|
|
1347
1471
|
type: 'text',
|
|
1348
1472
|
text: JSON.stringify({
|
|
1349
1473
|
code: 'practice-attempt-comparator-unavailable',
|
|
1350
|
-
message: `${toolName} compares history-run identities that are not exposed by hosted MCP. A pa-* id names a practice simulation
|
|
1474
|
+
message: `${toolName} compares history-run identities that are not exposed by hosted MCP. A pa-* id names a practice simulation run, and Meguro has no cross-run comparator today.`,
|
|
1351
1475
|
attemptIds,
|
|
1352
1476
|
nextAction: calls.join('; '),
|
|
1353
|
-
stopCondition: `Use the listed practice receipt reads instead. Do not retry ${toolName} with pa-* practice
|
|
1477
|
+
stopCondition: `Use the listed practice receipt reads instead. Do not retry ${toolName} with pa-* practice run ids.`,
|
|
1354
1478
|
workingMethod: {
|
|
1355
1479
|
calls,
|
|
1356
1480
|
receiptFacts: 'Read both immutable run receipts and impact receipts. Align like-named impact effectLines by key, preserve each receipt moneyBasis, and subtract the recorded values only after checking the comparison basis below.',
|
|
@@ -1375,16 +1499,27 @@ function practiceAttemptHistoryReadGuidance(toolName, attemptId) {
|
|
|
1375
1499
|
type: 'text',
|
|
1376
1500
|
text: JSON.stringify({
|
|
1377
1501
|
code: 'practice-attempt-not-history-run',
|
|
1378
|
-
message: `${toolName} reads Shopify dev-store history runs created by run_start. ${attemptId} is a practice simulation
|
|
1502
|
+
message: `${toolName} reads Shopify dev-store history runs created by run_start. ${attemptId} is a practice simulation run returned by practice_run_start, not a history run.`,
|
|
1379
1503
|
nextAction: nextCall,
|
|
1380
|
-
stopCondition: `Call ${nextCall} instead. Do not retry ${toolName} with a pa-* practice simulation
|
|
1504
|
+
stopCondition: `Call ${nextCall} instead. Do not retry ${toolName} with a pa-* practice simulation run id.`,
|
|
1381
1505
|
}, null, 2),
|
|
1382
1506
|
}],
|
|
1383
1507
|
isError: true,
|
|
1384
1508
|
};
|
|
1385
1509
|
}
|
|
1386
1510
|
|
|
1387
|
-
|
|
1511
|
+
/**
|
|
1512
|
+
* MEG-1563: the served brief is a declaration, not a bare list.
|
|
1513
|
+
*
|
|
1514
|
+
* A run that cannot state its pinned mission now says so with a reason. Projecting only arrays
|
|
1515
|
+
* dropped that reason silently, which is the unexplained empty brief this ticket removes.
|
|
1516
|
+
*/
|
|
1517
|
+
function railBrief(value) {
|
|
1518
|
+
if (value && typeof value === 'object' && typeof value.declaration === 'string') return { brief: value };
|
|
1519
|
+
return {};
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
function practiceStoreConnectionNotFoundGuidance(storeId, nextStep) {
|
|
1388
1523
|
return {
|
|
1389
1524
|
content: [{
|
|
1390
1525
|
type: 'text',
|
|
@@ -1393,6 +1528,7 @@ function practiceStoreConnectionNotFoundGuidance(storeId) {
|
|
|
1393
1528
|
message: `get_connection_details could not find practice store ${storeId} in this account or the store is no longer active.`,
|
|
1394
1529
|
nextAction: 'Call stores_list({}) and use an exact current storeId from the returned stores array.',
|
|
1395
1530
|
stopCondition: `If stores_list({}) returns no current stores, stop: there is no practice store to connect. Do not retry ${JSON.stringify(storeId)}.`,
|
|
1531
|
+
...(nextStep && typeof nextStep === 'object' ? { nextStep: secretSafe(nextStep) } : {}),
|
|
1396
1532
|
}, null, 2),
|
|
1397
1533
|
}],
|
|
1398
1534
|
isError: true,
|
|
@@ -1438,7 +1574,7 @@ function legacyGateArtifactNotFoundGuidance(runId) {
|
|
|
1438
1574
|
type: 'text',
|
|
1439
1575
|
text: JSON.stringify({
|
|
1440
1576
|
code: 'legacy-gate-artifact-not-found',
|
|
1441
|
-
message: `No retained legacy Configured Receipt Gate artifact exists for ${runId}. It is not a history-run id or pa-* practice simulation
|
|
1577
|
+
message: `No retained legacy Configured Receipt Gate artifact exists for ${runId}. It is not a history-run id or pa-* practice simulation run id.`,
|
|
1442
1578
|
nextAction: 'Call gate_verdict({}) to read the latest Configured Receipt Gate verdict.',
|
|
1443
1579
|
stopCondition: `Do not retry ${JSON.stringify(runId)}. The legacy artifact cannot be created from a customer surface; stop after gate_verdict({}).`,
|
|
1444
1580
|
}, null, 2),
|
|
@@ -1680,6 +1816,7 @@ export function createTools(config) {
|
|
|
1680
1816
|
const apiBaseUrl = String(config.apiBaseUrl ?? '').replace(/\/+$/, '');
|
|
1681
1817
|
const dashboardUrl = String(config.dashboardUrl ?? '').replace(/\/+$/, '');
|
|
1682
1818
|
const practiceApiToken = String(config.practiceApiToken ?? '').trim();
|
|
1819
|
+
const anonymousTemplateCatalog = config.anonymousTemplateCatalog ?? null;
|
|
1683
1820
|
const fetchImpl = config.fetchImpl ?? fetch;
|
|
1684
1821
|
let cachedBuildSha = typeof config.buildSha === 'string' && config.buildSha.trim() ? config.buildSha.trim() : '';
|
|
1685
1822
|
|
|
@@ -1949,6 +2086,7 @@ export function createTools(config) {
|
|
|
1949
2086
|
errors,
|
|
1950
2087
|
...(catalogConnection ? { allowedShopDomains } : {}),
|
|
1951
2088
|
...(Array.isArray(body.fleet) ? { fleet: body.fleet } : {}),
|
|
2089
|
+
...(body.nextStep && typeof body.nextStep === 'object' ? { nextStep: body.nextStep } : {}),
|
|
1952
2090
|
supportCode: await fleetSupportCode(supportRouteForFleetFailure(toolName, path), requestId),
|
|
1953
2091
|
});
|
|
1954
2092
|
const result = { content: [{ type: 'text', text: JSON.stringify(stable, null, 2) }], isError: true };
|
|
@@ -1987,7 +2125,7 @@ export function createTools(config) {
|
|
|
1987
2125
|
httpStatus: response.status,
|
|
1988
2126
|
error: { message: rawMessage },
|
|
1989
2127
|
teaching: {
|
|
1990
|
-
meaning: `${requestedId} was not found as a current Shopify dev-store history run in this account. run_resume does not accept a practice simulation
|
|
2128
|
+
meaning: `${requestedId} was not found as a current Shopify dev-store history run in this account. run_resume does not accept a practice simulation run id or an invented id.`,
|
|
1991
2129
|
nextStep: 'Call runs_list({}), choose an exact runId whose lifecycle is paused or failed, then call run_resume({ runId: "<exact runId>" }).',
|
|
1992
2130
|
stopCondition: 'If runs_list({}) has no paused or failed history run, stop. Do not retry the missing runId.',
|
|
1993
2131
|
},
|
|
@@ -2110,7 +2248,7 @@ export function createTools(config) {
|
|
|
2110
2248
|
} } : {}),
|
|
2111
2249
|
teaching: {
|
|
2112
2250
|
meaning: startPairNotFound || preflightPairNotFound
|
|
2113
|
-
? 'This tenant-safe not-found refusal means the requested practice
|
|
2251
|
+
? 'This tenant-safe not-found refusal means the requested practice run and connected development-store domain pair is unavailable to this account; Meguro does not reveal which member of the pair was absent.'
|
|
2114
2252
|
: examIdentityNotFound
|
|
2115
2253
|
? 'No Shopify Exam with this examId is available to this account. Exam identities are created only by exam_start.'
|
|
2116
2254
|
: source.code === 'exam-ineligible'
|
|
@@ -2199,6 +2337,22 @@ export function createTools(config) {
|
|
|
2199
2337
|
};
|
|
2200
2338
|
}
|
|
2201
2339
|
|
|
2340
|
+
async function practiceRunConsoleUrlFields(attemptId, workspaceId) {
|
|
2341
|
+
if (typeof attemptId !== 'string' || !PRACTICE_ATTEMPT_ID.test(attemptId)) return consoleUrlFields();
|
|
2342
|
+
try {
|
|
2343
|
+
const response = await practiceApi(
|
|
2344
|
+
'POST',
|
|
2345
|
+
`/practice/playbacks/${encodeURIComponent(attemptId)}/console-reference`,
|
|
2346
|
+
{},
|
|
2347
|
+
{ workspaceId },
|
|
2348
|
+
);
|
|
2349
|
+
if (!response.ok) return consoleUrlFields();
|
|
2350
|
+
return consoleUrlFields(response.json?.ref, response.json?.expiresAt);
|
|
2351
|
+
} catch {
|
|
2352
|
+
return consoleUrlFields();
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2202
2356
|
const rawDefinitions = [
|
|
2203
2357
|
{
|
|
2204
2358
|
name: 'docs_read',
|
|
@@ -2228,7 +2382,7 @@ export function createTools(config) {
|
|
|
2228
2382
|
},
|
|
2229
2383
|
{
|
|
2230
2384
|
name: 'templates_list',
|
|
2231
|
-
description: 'List the complete ordered, public-safe practice-store template catalog available to store_create; it is not filtered by stores already in the fleet. Each row carries only what a cold agent needs to choose — key, label, category, recommended, bestFor and recommendedRunDays. Call template_get({ templateKey }) for one template\'s complete record: supported reads and writes, operation contracts, unsupported contracts, capability boundaries, modeled evidence and physics, and rubric fields. starterRecommendation names every server-recommended template and provides a server-owned starterPlans entry for every selectable template. Each plan states the authenticated account tier, ideal horizon, exact first practice_run_start clock, run-start availability, and continuation. While the
|
|
2385
|
+
description: 'List the complete ordered, public-safe practice-store template catalog available to store_create; it is not filtered by stores already in the fleet. Each row carries only what a cold agent needs to choose — key, label, category, recommended, bestFor and recommendedRunDays. Call template_get({ templateKey }) for one template\'s complete record: supported reads and writes, operation contracts, unsupported contracts, capability boundaries, modeled evidence and physics, and rubric fields. starterRecommendation names every server-recommended template and provides a server-owned starterPlans entry for every selectable template. Each plan states the authenticated account tier, ideal horizon, exact first practice_run_start clock, run-start availability, and continuation. While the included evaluation is available, every template is recommendable and its plan carries that authorization; afterward the same plans apply ordinary tier policy. If no executable recommendation is available, the catalog remains complete and starterRecommendation.recommendationUnavailable explains why. The matching recommended boolean follows the server declaration, and recommendedRunDays remains each template\'s ideal simulation length. The response excludes customer profiles, scenario presets, credentials, and private route payload. An OAuth grant may select an owned non-default workspace; an API key remains bound to its own workspace.',
|
|
2232
2386
|
inputSchema: {
|
|
2233
2387
|
type: 'object',
|
|
2234
2388
|
additionalProperties: false,
|
|
@@ -2290,7 +2444,7 @@ export function createTools(config) {
|
|
|
2290
2444
|
},
|
|
2291
2445
|
{
|
|
2292
2446
|
name: 'store_passport',
|
|
2293
|
-
description: 'Read a machine-shaped Store Passport in the selected workspace: template identity, exact history span, Store time, catalog and inventory counts, commerce totals, demand profile, run summary, and disposal consequences. OAuth may select an owned workspace; an API key remains workspace-bound.',
|
|
2447
|
+
description: 'Read a machine-shaped Store Passport in the selected workspace: template identity, exact history span, Store time, catalog and inventory counts, commerce totals, demand profile, the effective modeled supplier lead time in Store days at generation.replenishment.leadTimeDays, run summary, and disposal consequences. OAuth may select an owned workspace; an API key remains workspace-bound.',
|
|
2294
2448
|
inputSchema: {
|
|
2295
2449
|
type: 'object', additionalProperties: false,
|
|
2296
2450
|
properties: {
|
|
@@ -2300,10 +2454,26 @@ export function createTools(config) {
|
|
|
2300
2454
|
required: ['storeId'],
|
|
2301
2455
|
},
|
|
2302
2456
|
},
|
|
2457
|
+
{
|
|
2458
|
+
name: 'sample_store_reset',
|
|
2459
|
+
description: 'Repair the one sample practice store this account already owns and return it ready to use under the same store id. This account-scoped call takes no arguments: the account\'s single sample claim names the target, so it can never create a second sample store, a second store id, or a second included evaluation. A sample store that is already usable is returned unchanged with reset:false; a sample store that is archived or has lost its simulation state is repaired and returned with reset:true. Its onboardingEvaluation field reports whether an in-progress or already-consumed evaluation was preserved, or whether a half-written evaluation was cleared and the included evaluation made available again. An account that owns no sample practice store gets a typed teaching error and nothing is created.',
|
|
2460
|
+
inputSchema: {
|
|
2461
|
+
type: 'object', additionalProperties: false,
|
|
2462
|
+
properties: {},
|
|
2463
|
+
required: [],
|
|
2464
|
+
},
|
|
2465
|
+
},
|
|
2303
2466
|
{
|
|
2304
2467
|
name: 'workspaces_list',
|
|
2305
|
-
description: 'List
|
|
2306
|
-
inputSchema: {
|
|
2468
|
+
description: 'List a deterministic page of workspaces owned by the token account plus the authoritative MEG-592 capacity projection. The response carries full-account total and an opaque nextCursor; pass that cursor unchanged to recover the next page. The OAuth grant is account-bound, so this account-scoped tool needs no workspace selector and does not re-derive tier limits.',
|
|
2469
|
+
inputSchema: {
|
|
2470
|
+
type: 'object', additionalProperties: false,
|
|
2471
|
+
properties: {
|
|
2472
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, description: 'Maximum workspace rows in this page. Defaults to 100.' },
|
|
2473
|
+
cursor: { type: 'string', minLength: 1, description: 'Opaque nextCursor returned by the preceding workspaces_list page. Omit to start at the first page.' },
|
|
2474
|
+
},
|
|
2475
|
+
required: [],
|
|
2476
|
+
},
|
|
2307
2477
|
},
|
|
2308
2478
|
{
|
|
2309
2479
|
name: 'workspace_create',
|
|
@@ -2359,7 +2529,7 @@ export function createTools(config) {
|
|
|
2359
2529
|
},
|
|
2360
2530
|
{
|
|
2361
2531
|
name: 'catalog_slices_saved',
|
|
2362
|
-
description: 'Manage the existing saved-catalog-slice lifecycle with action=list, get, save, delete, refresh, or changes. save/refresh read the connected Shopify store; delete removes only the exact saved slice; list/get/changes are Meguro reads. The worst-case annotation is intentionally destructive and outward because this grouped tool includes delete and external refresh.',
|
|
2532
|
+
description: 'Manage the existing saved-catalog-slice lifecycle with action=list, get, save, delete, refresh, or changes. list accepts limit and opaque cursor, and returns the server-owned full total plus nextCursor. save/refresh read the connected Shopify store; delete removes only the exact saved slice; list/get/changes are Meguro reads. The worst-case annotation is intentionally destructive and outward because this grouped tool includes delete and external refresh.',
|
|
2363
2533
|
inputSchema: CATALOG_SAVED_SLICES_INPUT_SCHEMA,
|
|
2364
2534
|
},
|
|
2365
2535
|
{
|
|
@@ -2466,7 +2636,7 @@ export function createTools(config) {
|
|
|
2466
2636
|
},
|
|
2467
2637
|
{
|
|
2468
2638
|
name: 'runs_diff',
|
|
2469
|
-
description: 'Legacy receipt comparison for two Shopify dev-store history runs created by run_start. It does not compare practice simulation
|
|
2639
|
+
description: 'Legacy receipt comparison for two Shopify dev-store history runs created by run_start. It does not compare practice simulation runs returned by practice_run_start; a pa-* id receives a teaching error with the terminating receipt-fact method. For history runs it verifies the shared prefix, finds the fork, and returns impact receipt deltas. Prefer twin_diff for the named history-run impact receipt vocabulary. "Not comparable" is an honest answer, not an error in your usage. Receipt availability and expiry guidance are server-owned and returned by the receipt response.',
|
|
2470
2640
|
inputSchema: {
|
|
2471
2641
|
type: 'object',
|
|
2472
2642
|
additionalProperties: false,
|
|
@@ -2519,7 +2689,7 @@ export function createTools(config) {
|
|
|
2519
2689
|
},
|
|
2520
2690
|
{
|
|
2521
2691
|
name: 'runs_list',
|
|
2522
|
-
description: 'Discover the owned Shopify dev-store history runs readable through hosted OAuth. A completed server-owned twin relation is projected as twinPair with pairId, baseline/treated role, and exact counterpartRunId; pass those returned identities to run_status, run_report, and twin_diff. It does not list practice simulation
|
|
2692
|
+
description: 'Discover the owned Shopify dev-store history runs readable through hosted OAuth. A completed server-owned twin relation is projected as twinPair with pairId, baseline/treated role, and exact counterpartRunId; pass those returned identities to run_status, run_report, and twin_diff. It does not list practice simulation runs returned by practice_run_start. Optional lifecycle filtering supports running, paused, completed, failed, or cleaning.',
|
|
2523
2693
|
inputSchema: {
|
|
2524
2694
|
type: 'object',
|
|
2525
2695
|
additionalProperties: false,
|
|
@@ -2559,7 +2729,7 @@ export function createTools(config) {
|
|
|
2559
2729
|
},
|
|
2560
2730
|
{
|
|
2561
2731
|
name: 'exam_preflight',
|
|
2562
|
-
description: 'Read-only Shopify Exam readiness for one immutable completed practice
|
|
2732
|
+
description: 'Read-only Shopify Exam readiness for one immutable completed practice run and one exact connected Shopify development-store domain. Creates no Exam row and makes zero Shopify mutations. An ineligible result teaches the exact captured-evidence recovery; transient evidence reads remain undetermined and retryable rather than becoming negative evidence.',
|
|
2563
2733
|
inputSchema: {
|
|
2564
2734
|
type: 'object', additionalProperties: false,
|
|
2565
2735
|
properties: {
|
|
@@ -2571,7 +2741,7 @@ export function createTools(config) {
|
|
|
2571
2741
|
},
|
|
2572
2742
|
{
|
|
2573
2743
|
name: 'exam_start',
|
|
2574
|
-
description: 'Start or safely continue a Shopify Exam from one immutable practice
|
|
2744
|
+
description: 'Start or safely continue a Shopify Exam from one immutable practice run on the exact typed development-store domain. The tool performs idempotent create/preview/typed-confirm setup, then advances at most one bounded execution or cleanup checkpoint. Call exam_status after every invocation; while nextAction names exam_start, repeat the same arguments. GET polling never advances or cleans the Exam and ambiguous delivery is never retried blindly.',
|
|
2575
2745
|
inputSchema: {
|
|
2576
2746
|
type: 'object', additionalProperties: false,
|
|
2577
2747
|
properties: {
|
|
@@ -2601,14 +2771,14 @@ export function createTools(config) {
|
|
|
2601
2771
|
},
|
|
2602
2772
|
{
|
|
2603
2773
|
name: 'practice_run_start',
|
|
2604
|
-
description: 'Start an external-agent practice simulation run on an existing Meguro practice store, or continue one completed
|
|
2774
|
+
description: 'Start an external-agent practice simulation run on an existing Meguro practice store, or continue one completed prior run on the same aging store when server authorization allows it. The returned lifecycleContract explains that accepted writes are disposable run-local state, actions remain in receipt and Impact evidence, the store returns to baseline after completion, terminal reads remain available while writes are refused, and ordinary completed runs meter once. Your account includes one evaluation on the practice store you choose. The first accepted run reserves it for that store and its planned Store-day horizon. Runs inside that horizon don\'t use monthly simulation runs; after it, plan limits apply. A fresh run replays the practice store\'s original scenario baseline as run 1 by default; continued runs are explicit through continueFromAttemptId. An optional immutable assertionPlan may predeclare exact expected negative-control refusals before call sequence zero; only exact server-matched reason, layer, target or request, and cardinality can prevent that rejection from becoming an unhandled Configured Receipt Gate failure. When clock.simulationDays is omitted, Meguro derives the full remaining scenario arc. Without active included-evaluation authorization, Builder plans a capped legal run while Free or Solo refuses an above-limit arc. For an executable included-evaluation call, choose the matching starterRecommendation.starterPlans entry returned by templates_list and use its exact practiceRunStart.clock; otherwise use the returned ordinary starterPlan. Every accepted start returns a machine-readable segmentPlan with the exact Store-day range, authorization, and run-plan narration. An advance target that reaches or crosses the planned end requires explicit terminal confirmation before the run ends; state-changing Admin calls require a running run. The returned machine-readable simulationRunClock announces every exit: 1 hour for an agent that never executes, 48 hours with neither agent contact nor Store-time advance, and a 7-day run age (2 days on Free). Takes the canonical practice-store id (`storeId`) exactly as returned by get_connection_details, and returns the run identity (`attemptId`) every later practice_run_* tool uses — the store id and the run id are different identifiers. Meguro Console is quick proof, while the agency agent still runs in the caller\'s own environment. Returns the same server-authoritative practice-run contract without connection credentials.',
|
|
2605
2775
|
inputSchema: {
|
|
2606
2776
|
type: 'object',
|
|
2607
2777
|
additionalProperties: false,
|
|
2608
2778
|
properties: {
|
|
2609
2779
|
workspaceId: OPTIONAL_WORKSPACE_SELECTOR_INPUT_SCHEMA,
|
|
2610
2780
|
storeId: { ...PRACTICE_STORE_ID_INPUT_SCHEMA, description: 'Canonical Meguro practice-store id (tenant-owned), as returned by get_connection_details.' },
|
|
2611
|
-
continueFromAttemptId: { type: 'string', minLength: 4, maxLength: 128, pattern: '^pa-[a-z0-9][a-z0-9-]{0,124}$', description: 'Completed prior
|
|
2781
|
+
continueFromAttemptId: { type: 'string', minLength: 4, maxLength: 128, pattern: '^pa-[a-z0-9][a-z0-9-]{0,124}$', description: 'Completed prior run to continue on the same aging practice store. Available only when the server tier policy allows continuation. On Builder, omit clock.simulationDays to let Meguro plan the next legal run automatically.' },
|
|
2612
2782
|
clock: {
|
|
2613
2783
|
type: 'object',
|
|
2614
2784
|
additionalProperties: false,
|
|
@@ -2669,7 +2839,7 @@ export function createTools(config) {
|
|
|
2669
2839
|
},
|
|
2670
2840
|
{
|
|
2671
2841
|
name: 'practice_runs_list',
|
|
2672
|
-
description: 'List
|
|
2842
|
+
description: 'List recoverable pages of practice simulation runs for one exact practice store. A completed comparable row is `meguro.receipt-comparison-card.v1`: use `identities.attemptId`, with this call\'s workspace context, in practice_run_report or practice_run_impact; it contains `counts`, `dimensions`, `gate`, `links`, `sourceReceipt`, and `evidenceBoundary`, and has no `receiptRef`. A non-completed row instead has `attemptId`, lifecycle `state`, `clock`, `receiptRef`, `brief`, and `nextStep`; a completed run without a comparable card has `attemptId`, `code: "receipt-comparison-card-unavailable"`, and `reason`. `total` is the complete authorized store-filtered count; copy `nextCursor` unchanged into the next call until it is null. Store selection follows one fail-closed ladder: receiptId derives its owning store; otherwise an explicit storeId wins; omission is allowed only when the workspace has exactly one current store; zero or multiple stores return a structured store-selection-required error and never choose silently. An OAuth grant may select an owned workspace; an account API key remains bound to its own workspace.',
|
|
2673
2843
|
inputSchema: {
|
|
2674
2844
|
type: 'object',
|
|
2675
2845
|
additionalProperties: false,
|
|
@@ -2677,13 +2847,15 @@ export function createTools(config) {
|
|
|
2677
2847
|
workspaceId: { type: 'string', minLength: 1, description: 'Optional OAuth selector for an account-owned non-default workspace. An account API key can name only its bound workspace.' },
|
|
2678
2848
|
storeId: { ...PRACTICE_STORE_ID_INPUT_SCHEMA, description: 'Optional exact practice-store id from stores_list. Omit only with receiptId or when the workspace has exactly one current store.' },
|
|
2679
2849
|
receiptId: { type: 'string', minLength: 4, maxLength: 128, pattern: '^pa-[a-z0-9][a-z0-9-]{0,124}$', description: 'Optional known pa-* practice receipt identity. Meguro derives its owning store and refuses a conflicting storeId.' },
|
|
2850
|
+
limit: { type: 'integer', minimum: 1, maximum: 100, description: 'Optional page size. Defaults to 100; never request more than 100.' },
|
|
2851
|
+
cursor: { type: 'string', minLength: 1, maxLength: 1024, description: 'Optional opaque nextCursor returned by the preceding practice_runs_list page. Copy unchanged with the same store selection.' },
|
|
2680
2852
|
},
|
|
2681
2853
|
required: [],
|
|
2682
2854
|
},
|
|
2683
2855
|
},
|
|
2684
2856
|
{
|
|
2685
2857
|
name: 'practice_run_status',
|
|
2686
|
-
description: 'Read the server-authoritative state of an external-agent practice simulation run, including its announced simulation-run deadlines, immutable simulation run receipt after any exit, and lifecycleContract. That contract explains that accepted writes are disposable
|
|
2858
|
+
description: 'Read the server-authoritative state of an external-agent practice simulation run, including its announced simulation-run deadlines, immutable simulation run receipt after any exit, and lifecycleContract. That contract explains that accepted writes are disposable run-local state, actions remain in receipt and Impact evidence, the store returns to baseline after completion, terminal reads remain available while writes are refused, and an ordinary completed run is metered once while the explicitly provisioned sample is exempt. Immediately before advancing, copy practiceRun.advanceCursor unchanged into practice_run_advance; do not choose between currentDay and watermarkDay or assemble call-sequence fields. If the next target reaches or crosses endDay, follow the exact terminal-confirmation retry returned by practice_run_advance. A running run authorizes state-changing Admin calls; after completion later writes receive the stable run-complete refusal. Use Console for quick proof; the tested agent remains in the caller\'s environment.',
|
|
2687
2859
|
inputSchema: {
|
|
2688
2860
|
type: 'object', additionalProperties: false,
|
|
2689
2861
|
properties: {
|
|
@@ -2707,7 +2879,7 @@ export function createTools(config) {
|
|
|
2707
2879
|
},
|
|
2708
2880
|
{
|
|
2709
2881
|
name: 'practice_run_advance',
|
|
2710
|
-
description: 'Advance an external-agent practice run by whole days or until one closed-set commerce condition, using the server-owned optimistic-concurrency advanceCursor. Accepted writes are disposable
|
|
2882
|
+
description: 'Advance an external-agent practice run by whole days or until one closed-set commerce condition, using the server-owned optimistic-concurrency advanceCursor. Accepted writes are disposable run-local state: they remain in receipt and Impact evidence, while the underlying practice store returns to baseline after completion. Terminal reads remain available and terminal writes are refused. An advance that reaches or crosses planned endDay refuses without changing Store time unless the caller explicitly retries the returned shape with confirmConclusion: true; that confirmed retry concludes the run and closes its write lane. If an advance reaches the planned end, the explicit confirmation path ends in the stable run-complete refusal for later writes. State-changing Admin calls require a running run. Immediately before every advance, call practice_run_status and copy practiceRun.advanceCursor unchanged. If it is stale, Meguro refuses without moving Store time and returns a fresh advanceCursor for one exact retry; do not infer from currentDay, watermarkDay, or call-sequence fields. Before durable agentExecutionStartedAt exists, at most 7 Store days may be advanced; connect your agent — any call unlocks further time. Console is quick proof; agent execution remains in the caller\'s environment.',
|
|
2711
2883
|
inputSchema: {
|
|
2712
2884
|
type: 'object',
|
|
2713
2885
|
additionalProperties: false,
|
|
@@ -2717,7 +2889,7 @@ export function createTools(config) {
|
|
|
2717
2889
|
days: { type: 'integer', minimum: 1, description: 'Whole simulated days to advance.' },
|
|
2718
2890
|
until: PRACTICE_RUN_UNTIL_INPUT_SCHEMA,
|
|
2719
2891
|
advanceCursor: PRACTICE_RUN_ADVANCE_CURSOR_INPUT_SCHEMA,
|
|
2720
|
-
confirmConclusion: { type: 'boolean', description: 'Set true only after reading the terminal-boundary teaching refusal and choosing to conclude the
|
|
2892
|
+
confirmConclusion: { type: 'boolean', description: 'Set true only after reading the terminal-boundary teaching refusal and choosing to conclude the run and close its write lane.' },
|
|
2721
2893
|
},
|
|
2722
2894
|
required: ['attemptId', 'advanceCursor'],
|
|
2723
2895
|
oneOf: [
|
|
@@ -2728,14 +2900,14 @@ export function createTools(config) {
|
|
|
2728
2900
|
},
|
|
2729
2901
|
{
|
|
2730
2902
|
name: 'practice_run_finish',
|
|
2731
|
-
description: 'End an external-agent practice
|
|
2903
|
+
description: 'End an external-agent practice run without inventing time or evidence. A run at its planned end completes; an under-measured run stops and remains explicitly incomplete. For a stopped included-evaluation run, the server-owned practiceRun.recovery object gives the exact practice_run_start call that resumes the same first included evaluation at its authoritative watermark. To abandon that incomplete included evaluation permanently, set both permanentStop and acknowledgeIncompleteOnboardingEvaluation true; Meguro preserves evidence and consumed included-evaluation value. The response includes the immutable run receipt and announced deadlines. Console is quick proof; agent execution remains in the caller\'s environment.',
|
|
2732
2904
|
inputSchema: {
|
|
2733
2905
|
type: 'object', additionalProperties: false,
|
|
2734
2906
|
properties: {
|
|
2735
2907
|
workspaceId: OPTIONAL_WORKSPACE_SELECTOR_INPUT_SCHEMA,
|
|
2736
2908
|
attemptId: { type: 'string', minLength: 4, maxLength: 128, pattern: '^pa-[a-z0-9][a-z0-9-]{0,124}$', description: 'Run identity returned by practice_run_start — not the practice-store id (storeId).' },
|
|
2737
|
-
permanentStop: { type: 'boolean', description: 'Set true only to abandon an under-measured
|
|
2738
|
-
acknowledgeIncompleteOnboardingEvaluation: { type: 'boolean', description: 'Set true with permanentStop to acknowledge that the
|
|
2909
|
+
permanentStop: { type: 'boolean', description: 'Set true only to abandon an under-measured included evaluation permanently. Requires acknowledgeIncompleteOnboardingEvaluation=true.' },
|
|
2910
|
+
acknowledgeIncompleteOnboardingEvaluation: { type: 'boolean', description: 'Set true with permanentStop to acknowledge that the included evaluation will remain incomplete while its evidence and consumed value remain preserved.' },
|
|
2739
2911
|
},
|
|
2740
2912
|
required: ['attemptId'],
|
|
2741
2913
|
dependentRequired: {
|
|
@@ -2746,24 +2918,26 @@ export function createTools(config) {
|
|
|
2746
2918
|
},
|
|
2747
2919
|
{
|
|
2748
2920
|
name: 'practice_run_report',
|
|
2749
|
-
description: 'Read
|
|
2921
|
+
description: 'Read a completed practice run\'s receipt. Omit `detail` (or pass `"card"`) for `meguro.receipt-comparison-card.v1`: `identities`, `counts`, `dimensions`, `gate`, `links`, `sourceReceipt`, and `evidenceBoundary`. Take `identities.attemptId` from a completed practice_runs_list card and preserve that list call\'s workspaceId here; do not invent a completed-row `receiptRef`. `links.publicReceipt` and `links.impactReceipt` are the artifact paths; their corresponding receipt digests are `sourceReceipt.runReceiptDigest` and `sourceReceipt.impactDigest` (each can name an unavailable reason). Respect the card\'s `evidenceBoundary`. Pass `detail: "full"` for the full receipt document: `reportBack`, `takeHome`, and `resultSemantics` are full-detail fields; `reportBack` is the server-owned Mechanics, Conduct, Scenario outcome summary. The public receipt URL is `publicReceipt.canonicalPath` (with `publicReceipt.sha256`), not a top-level `canonicalPath`. Raw private request and response payloads stay out of MCP; receipt availability and expiry guidance are server-owned.',
|
|
2750
2922
|
inputSchema: {
|
|
2751
2923
|
type: 'object', additionalProperties: false,
|
|
2752
2924
|
properties: {
|
|
2753
2925
|
attemptId: { type: 'string', minLength: 4, maxLength: 128, pattern: '^pa-[a-z0-9][a-z0-9-]{0,124}$', description: 'Run identity returned by practice_run_start — not the practice-store id (storeId).' },
|
|
2754
2926
|
workspaceId: { type: 'string', minLength: 1, description: 'Optional account-owned workspace coordinate returned by practice_runs_list. Omit for Default.' },
|
|
2927
|
+
detail: { type: 'string', enum: ['card', 'full'], description: "Optional reply detail. Omit or pass 'card' for the bounded receipt comparison card; pass 'full' for the complete receipt document." },
|
|
2755
2928
|
},
|
|
2756
2929
|
required: ['attemptId'],
|
|
2757
2930
|
},
|
|
2758
2931
|
},
|
|
2759
2932
|
{
|
|
2760
2933
|
name: 'practice_run_impact',
|
|
2761
|
-
description: 'Read
|
|
2934
|
+
description: 'Read a completed practice run\'s Impact receipt. Omit `detail` (or pass `"card"`) for the same bounded `meguro.receipt-comparison-card.v1` served by practice_run_report: `identities`, `counts`, `dimensions`, `gate`, `links`, `sourceReceipt`, and `evidenceBoundary`. Start with `identities.attemptId` from a completed practice_runs_list card and preserve that list call\'s workspaceId; completed cards have no `receiptRef`. `links.publicReceipt` and `links.impactReceipt` name the artifacts, while `sourceReceipt.runReceiptDigest` and `sourceReceipt.impactDigest` name their corresponding digests (or unavailable reasons). Pass `detail: "full"` for the full Impact document, whose public artifact is `publicReceipt.canonicalPath` with `publicReceipt.sha256`; it is not a top-level `canonicalPath`. Read-only; never mutates the run and returns no credentials. Receipt availability and expiry guidance are server-owned.',
|
|
2762
2935
|
inputSchema: {
|
|
2763
2936
|
type: 'object', additionalProperties: false,
|
|
2764
2937
|
properties: {
|
|
2765
2938
|
attemptId: { type: 'string', minLength: 4, maxLength: 128, pattern: '^pa-[a-z0-9][a-z0-9-]{0,124}$', description: 'Run identity returned by practice_run_start — not the practice-store id (storeId).' },
|
|
2766
2939
|
workspaceId: { type: 'string', minLength: 1, description: 'Optional account-owned workspace coordinate returned by practice_runs_list. Omit for Default.' },
|
|
2940
|
+
detail: { type: 'string', enum: ['card', 'full'], description: "Optional reply detail. Omit or pass 'card' for the bounded receipt comparison card; pass 'full' for the complete receipt document." },
|
|
2767
2941
|
},
|
|
2768
2942
|
required: ['attemptId'],
|
|
2769
2943
|
},
|
|
@@ -2801,13 +2975,14 @@ export function createTools(config) {
|
|
|
2801
2975
|
},
|
|
2802
2976
|
{
|
|
2803
2977
|
name: 'admin_schema',
|
|
2804
|
-
description: `Look up an exact, bounded slice of the Shopify Admin GraphQL surface Meguro models for supported versions ${ADMIN_API_SUPPORTED_VERSION_LABEL}; the default is ${ADMIN_API_DEFAULT_VERSION}. Returns a type and its direct fields, a Type.field or root mutation with its arguments and return type, a shared commerce recipe, or an authored valid-not-modeled teaching boundary. A recipe match carries its bindingPlan, and every published mutation recipe carries match.recipe.evidencePlan: the eligibility read to run first (its exact query, variables, a selection sentence naming which returned row to use, and bindings from each response path to the mutation variable it fills), then the mutation, then the existing independent readback. mutation.documentOperationName is the GraphQL document label; mutation.rootField is the Shopify mutation coordinate. Where the mutation has a read-before-write cursor the plan also carries expectedRefusalOperationName — the exact root-field matcher key — plus staleCursorExpectedRefusal reason and layer facts to copy unchanged into an assertionPlan. Mutation example values that only the eligibility read can supply appear as <from-eligibility:VARIABLE_PATH> placeholders, never as plausible literals. Deterministic — it never returns the full schema, tenant data, catalog, or credentials, and on a miss it suggests only exact prefix/substring alternatives. Alternatively, submit query instead of lookup + name — with optional variables, operationName, and apiVersion — to ask what this exact Admin document would receive before you create a practice store or spend credit. It parses the document and classifies the selected operation and every supplied coordinate through the same served schema, versioned compatibility registry, and required-argument recovery authority the served Admin surface uses, returning exactly one state: "served" — no compatibility teaching refusal would be produced; "unsupported-with-recovery" — the exact refusal plus the registry remedy naming the supported coordinate or the exact admin_schema call that answers a required-argument refusal; "out-of-plane" — outside the claimed Admin plane, or no exact recovery authority exists. A compatibility withdrawal also carries teachingRefusal: the same refusal the served Admin surface returns, with its code, coordinate, reason, and next step. A document that will not parse, or whose operation cannot be selected without guessing, comes back with that as its own reason rather than guessed compatibility.
|
|
2978
|
+
description: `Look up an exact, bounded slice of the Shopify Admin GraphQL surface Meguro models for supported versions ${ADMIN_API_SUPPORTED_VERSION_LABEL}; the default is ${ADMIN_API_DEFAULT_VERSION}. Returns a type and its direct fields, a Type.field or root mutation with its arguments and return type, a shared commerce recipe, or an authored valid-not-modeled teaching boundary. A recipe match carries its bindingPlan, and every published mutation recipe carries match.recipe.evidencePlan: the eligibility read to run first (its exact query, variables, a selection sentence naming which returned row to use, and bindings from each response path to the mutation variable it fills), then the mutation, then the existing independent readback. mutation.documentOperationName is the GraphQL document label; mutation.rootField is the Shopify mutation coordinate. Where the mutation has a read-before-write cursor the plan also carries expectedRefusalOperationName — the exact root-field matcher key — plus staleCursorExpectedRefusal reason and layer facts to copy unchanged into an assertionPlan. Mutation example values that only the eligibility read can supply appear as <from-eligibility:VARIABLE_PATH> placeholders, never as plausible literals. Deterministic — it never returns the full schema, tenant data, catalog, or credentials, and on a miss it suggests only exact prefix/substring alternatives. Give an exact current storeId only to replace a static schema-stage next step with that Store's current run-guide step; it returns no connection material. Alternatively, submit query instead of lookup + name — with optional variables, operationName, and apiVersion — to ask what this exact Admin document would receive before you create a practice store or spend credit. It parses the document and classifies the selected operation and every supplied coordinate through the same served schema, versioned compatibility registry, and required-argument recovery authority the served Admin surface uses, returning exactly one state: "served" — no compatibility teaching refusal would be produced; "unsupported-with-recovery" — the exact refusal plus the registry remedy naming the supported coordinate or the exact admin_schema call that answers a required-argument refusal; "out-of-plane" — outside the claimed Admin plane, or no exact recovery authority exists. A compatibility withdrawal also carries teachingRefusal: the same refusal the served Admin surface returns, with its code, coordinate, reason, and next step. A document that will not parse, or whose operation cannot be selected without guessing, comes back with that as its own reason rather than guessed compatibility. The two modes are exclusive: pass lookup + name or query, never both.`,
|
|
2805
2979
|
inputSchema: {
|
|
2806
2980
|
type: 'object',
|
|
2807
2981
|
additionalProperties: false,
|
|
2808
2982
|
properties: {
|
|
2809
2983
|
lookup: { type: 'string', enum: ['type', 'field', 'mutation', 'recipe'], description: 'Lookup mode: what to look up — a type, a Type.field, a root mutation, or a commerce recipe. Requires name.' },
|
|
2810
2984
|
name: { type: 'string', minLength: 1, maxLength: 200, description: 'Lookup mode: exact name, e.g. ProductVariant, ProductVariant.inventoryQuantity, inventoryAdjustQuantities, or a recipe id/operationName like low-inventory or LowInventory.' },
|
|
2985
|
+
storeId: { ...PRACTICE_STORE_ID_INPUT_SCHEMA, description: 'Optional exact current Store id. On lookup mode only, returns that Store run\'s current next step rather than a static schema-stage call.' },
|
|
2811
2986
|
query: { type: 'string', minLength: 1, maxLength: 30000, description: 'Refusal mode: the exact Admin GraphQL document you intend to send. Classified, never executed.' },
|
|
2812
2987
|
variables: { type: 'object', description: 'Refusal mode: optional GraphQL variables object (max 20,000 serialized characters). Supplied input-object fields are classified, not executed.' },
|
|
2813
2988
|
operationName: { type: 'string', minLength: 1, maxLength: 200, description: 'Refusal mode: which operation to classify when the document defines more than one. Without it, a multi-operation document is refused rather than guessed.' },
|
|
@@ -2816,7 +2991,7 @@ export function createTools(config) {
|
|
|
2816
2991
|
required: [],
|
|
2817
2992
|
oneOf: [
|
|
2818
2993
|
{ type: 'object', required: ['lookup', 'name'], not: { anyOf: [{ required: ['query'] }, { required: ['variables'] }, { required: ['operationName'] }] } },
|
|
2819
|
-
{ type: 'object', required: ['query'], not: { anyOf: [{ required: ['lookup'] }, { required: ['name'] }] } },
|
|
2994
|
+
{ type: 'object', required: ['query'], not: { anyOf: [{ required: ['lookup'] }, { required: ['name'] }, { required: ['storeId'] }] } },
|
|
2820
2995
|
],
|
|
2821
2996
|
},
|
|
2822
2997
|
},
|
|
@@ -2886,7 +3061,10 @@ export function createTools(config) {
|
|
|
2886
3061
|
const error = schemaValidationError(definition.inputSchema, args);
|
|
2887
3062
|
if (error) {
|
|
2888
3063
|
const correction = conditionalToolCorrection(name, args);
|
|
2889
|
-
|
|
3064
|
+
const message = name === 'practice_run_advance' && args.advanceCursor === undefined
|
|
3065
|
+
? 'practice_run_advance arguments require advanceCursor. Call practice_run_status immediately before advancing, then copy practiceRun.advanceCursor unchanged.'
|
|
3066
|
+
: `${name} arguments do not match the declared input schema: ${error}${correction ? `. Corrected call: ${correction}` : ''}`;
|
|
3067
|
+
throw new Error(message);
|
|
2890
3068
|
}
|
|
2891
3069
|
return;
|
|
2892
3070
|
}
|
|
@@ -2918,6 +3096,9 @@ export function createTools(config) {
|
|
|
2918
3096
|
}
|
|
2919
3097
|
case 'templates_list': {
|
|
2920
3098
|
const workspaceId = optionalWorkspaceId(args);
|
|
3099
|
+
if (anonymousTemplateCatalog) {
|
|
3100
|
+
return textResult(JSON.stringify(anonymousStoreTemplatesProjection(anonymousTemplateCatalog)));
|
|
3101
|
+
}
|
|
2921
3102
|
const response = await fleetRequest(name, 'GET', '/practice/profiles', undefined, { workspaceId });
|
|
2922
3103
|
return 'error' in response
|
|
2923
3104
|
? response.error
|
|
@@ -2926,6 +3107,14 @@ export function createTools(config) {
|
|
|
2926
3107
|
case 'template_get': {
|
|
2927
3108
|
const workspaceId = optionalWorkspaceId(args);
|
|
2928
3109
|
const templateKey = requiredString(args, 'templateKey');
|
|
3110
|
+
if (anonymousTemplateCatalog) {
|
|
3111
|
+
const storeTemplate = Array.isArray(anonymousTemplateCatalog.storeTemplates)
|
|
3112
|
+
? anonymousTemplateCatalog.storeTemplates.find((template) => template?.key === templateKey)
|
|
3113
|
+
: null;
|
|
3114
|
+
return storeTemplate
|
|
3115
|
+
? textResult(storeTemplateDetailProjection({ storeTemplate }))
|
|
3116
|
+
: errorResult(`Practice template not found: ${templateKey}`);
|
|
3117
|
+
}
|
|
2929
3118
|
const response = await fleetRequest(
|
|
2930
3119
|
name,
|
|
2931
3120
|
'GET',
|
|
@@ -2969,8 +3158,15 @@ export function createTools(config) {
|
|
|
2969
3158
|
const response = await fleetRequest(name, 'GET', path, undefined, { workspaceId });
|
|
2970
3159
|
return 'error' in response ? response.error : textResult(secretSafe(response.value));
|
|
2971
3160
|
}
|
|
3161
|
+
case 'sample_store_reset': {
|
|
3162
|
+
const response = await fleetRequest(name, 'POST', '/practice/worlds/sample/reset', {});
|
|
3163
|
+
return 'error' in response ? response.error : textResult(secretSafe(response.value));
|
|
3164
|
+
}
|
|
2972
3165
|
case 'workspaces_list': {
|
|
2973
|
-
const
|
|
3166
|
+
const limit = args.limit === undefined ? 100 : optionalPositiveInteger(args, 'limit');
|
|
3167
|
+
if (limit > 100) throw new Error('limit must be 100 or fewer');
|
|
3168
|
+
const cursor = args.cursor === undefined ? undefined : requiredString(args, 'cursor');
|
|
3169
|
+
const response = await fleetRequest(name, 'GET', queryPath('/workspaces', { limit, cursor }));
|
|
2974
3170
|
return 'error' in response ? response.error : textResult(secretSafe(response.value));
|
|
2975
3171
|
}
|
|
2976
3172
|
case 'workspace_create': {
|
|
@@ -3023,7 +3219,10 @@ export function createTools(config) {
|
|
|
3023
3219
|
const action = branch.action;
|
|
3024
3220
|
let response;
|
|
3025
3221
|
if (action === 'list') {
|
|
3026
|
-
|
|
3222
|
+
const limit = args.limit === undefined ? 100 : optionalPositiveInteger(args, 'limit');
|
|
3223
|
+
if (limit > 100) throw new Error('limit must be 100 or fewer');
|
|
3224
|
+
const cursor = args.cursor === undefined ? undefined : requiredString(args, 'cursor');
|
|
3225
|
+
response = await fleetRequest(name, 'GET', queryPath('/catalog-slices', { limit, cursor }), undefined, {
|
|
3027
3226
|
workspaceId, invocationMutates: branch.mutates,
|
|
3028
3227
|
});
|
|
3029
3228
|
} else if (action === 'save') {
|
|
@@ -3386,11 +3585,16 @@ export function createTools(config) {
|
|
|
3386
3585
|
);
|
|
3387
3586
|
}
|
|
3388
3587
|
const started = response.json ?? {};
|
|
3588
|
+
const attemptId = started.attemptId ?? started.practiceRun?.attemptId;
|
|
3589
|
+
const consoleFields = await practiceRunConsoleUrlFields(attemptId, workspaceId);
|
|
3389
3590
|
return textResult(secretSafe({
|
|
3390
3591
|
operation: 'start',
|
|
3391
|
-
attemptId
|
|
3592
|
+
attemptId,
|
|
3593
|
+
...(started.teaching ? { teaching: started.teaching } : {}),
|
|
3392
3594
|
...(started.segmentPlan ? { segmentPlan: started.segmentPlan } : {}),
|
|
3393
3595
|
...(started.assertionPlan ? { assertionPlan: started.assertionPlan } : {}),
|
|
3596
|
+
...railBrief(started.brief),
|
|
3597
|
+
...(started.nextStep && typeof started.nextStep === 'object' ? { nextStep: started.nextStep } : {}),
|
|
3394
3598
|
practiceRun: practiceRunProjection(started.practiceRun),
|
|
3395
3599
|
paths: {
|
|
3396
3600
|
state: started.statePath,
|
|
@@ -3398,11 +3602,14 @@ export function createTools(config) {
|
|
|
3398
3602
|
advance: started.advancePath,
|
|
3399
3603
|
report: started.reportPath,
|
|
3400
3604
|
},
|
|
3401
|
-
...
|
|
3605
|
+
...consoleFields,
|
|
3402
3606
|
}));
|
|
3403
3607
|
}
|
|
3404
3608
|
case 'practice_runs_list': {
|
|
3405
3609
|
const workspaceId = optionalWorkspaceId(args);
|
|
3610
|
+
const limit = optionalPositiveInteger(args, 'limit') ?? 100;
|
|
3611
|
+
if (limit > 100) throw new Error('limit must be 100 or less');
|
|
3612
|
+
const cursor = args.cursor === undefined ? undefined : requiredString(args, 'cursor');
|
|
3406
3613
|
const requestedStoreId = args.storeId === undefined
|
|
3407
3614
|
? undefined
|
|
3408
3615
|
: validatedStoreId(requiredString(args, 'storeId'), 'storeId');
|
|
@@ -3423,7 +3630,7 @@ export function createTools(config) {
|
|
|
3423
3630
|
if (requestedStoreId && requestedStoreId !== receiptStoreId) {
|
|
3424
3631
|
return practiceRunReceiptStoreMismatch(receiptId, receiptStoreId, requestedStoreId);
|
|
3425
3632
|
}
|
|
3426
|
-
const listPath = queryPath('/practice/playbacks', { worldId: receiptStoreId });
|
|
3633
|
+
const listPath = queryPath('/practice/playbacks', { worldId: receiptStoreId, limit, cursor, detail: 'card' });
|
|
3427
3634
|
const listed = await fleetRequest(name, 'GET', listPath, undefined, { workspaceId });
|
|
3428
3635
|
if ('error' in listed) return listed.error;
|
|
3429
3636
|
return textResult(practiceRunsProjection(listed.value, receiptStoreId, 'receipt-derived', workspaceId));
|
|
@@ -3442,7 +3649,8 @@ export function createTools(config) {
|
|
|
3442
3649
|
return practiceRunsSelectionError(storeIds);
|
|
3443
3650
|
}
|
|
3444
3651
|
|
|
3445
|
-
|
|
3652
|
+
// MEG-1571: the completed rows of this one call carry the server-owned comparison cards.
|
|
3653
|
+
const listPath = queryPath('/practice/playbacks', { worldId: requestedStoreId ?? storeIds[0], limit, cursor, detail: 'card' });
|
|
3446
3654
|
const listed = await fleetRequest(name, 'GET', listPath, undefined, { workspaceId });
|
|
3447
3655
|
if ('error' in listed) return listed.error;
|
|
3448
3656
|
const selectedStoreId = requestedStoreId ?? storeIds[0];
|
|
@@ -3464,11 +3672,14 @@ export function createTools(config) {
|
|
|
3464
3672
|
return practiceErrorResult(response.status, response.json, response.retryAfterSeconds, teaching);
|
|
3465
3673
|
}
|
|
3466
3674
|
const state = response.json ?? {};
|
|
3675
|
+
const consoleFields = await practiceRunConsoleUrlFields(attemptId, workspaceId);
|
|
3467
3676
|
return textResult(secretSafe({
|
|
3468
3677
|
operation: 'status',
|
|
3469
3678
|
attemptId,
|
|
3679
|
+
...railBrief(state.brief),
|
|
3680
|
+
...(state.nextStep && typeof state.nextStep === 'object' ? { nextStep: state.nextStep } : {}),
|
|
3470
3681
|
practiceRun: practiceRunProjection(state.practiceRun),
|
|
3471
|
-
...
|
|
3682
|
+
...consoleFields,
|
|
3472
3683
|
}));
|
|
3473
3684
|
}
|
|
3474
3685
|
case 'practice_run_checkpoint': {
|
|
@@ -3481,13 +3692,16 @@ export function createTools(config) {
|
|
|
3481
3692
|
: undefined;
|
|
3482
3693
|
return practiceErrorResult(response.status, response.json, response.retryAfterSeconds, teaching);
|
|
3483
3694
|
}
|
|
3695
|
+
const consoleFields = await practiceRunConsoleUrlFields(attemptId, workspaceId);
|
|
3484
3696
|
return textResult(secretSafe({
|
|
3485
3697
|
operation: 'checkpoint',
|
|
3486
3698
|
attemptId,
|
|
3487
3699
|
alreadyCaptured: response.json?.alreadyCaptured === true,
|
|
3488
3700
|
advanceCursor: response.json?.advanceCursor,
|
|
3489
3701
|
checkpoint: checkpointProjection(response.json?.checkpoint),
|
|
3490
|
-
...
|
|
3702
|
+
...railBrief(response.json?.brief),
|
|
3703
|
+
...(response.json?.nextStep && typeof response.json.nextStep === 'object' ? { nextStep: response.json.nextStep } : {}),
|
|
3704
|
+
...consoleFields,
|
|
3491
3705
|
}));
|
|
3492
3706
|
}
|
|
3493
3707
|
case 'practice_run_advance': {
|
|
@@ -3511,6 +3725,7 @@ export function createTools(config) {
|
|
|
3511
3725
|
return practiceErrorResult(response.status, response.json, response.retryAfterSeconds, teaching);
|
|
3512
3726
|
}
|
|
3513
3727
|
const advanced = response.json ?? {};
|
|
3728
|
+
const consoleFields = await practiceRunConsoleUrlFields(attemptId, workspaceId);
|
|
3514
3729
|
return textResult(secretSafe({
|
|
3515
3730
|
operation: 'advance',
|
|
3516
3731
|
attemptId,
|
|
@@ -3518,8 +3733,10 @@ export function createTools(config) {
|
|
|
3518
3733
|
isComplete: advanced.isComplete,
|
|
3519
3734
|
alreadyApplied: advanced.alreadyApplied === true,
|
|
3520
3735
|
...(advanced.until ? { until: advanced.until } : {}),
|
|
3736
|
+
...railBrief(advanced.brief),
|
|
3737
|
+
...(advanced.nextStep && typeof advanced.nextStep === 'object' ? { nextStep: advanced.nextStep } : {}),
|
|
3521
3738
|
practiceRun: practiceRunProjection(advanced.practiceRun),
|
|
3522
|
-
...
|
|
3739
|
+
...consoleFields,
|
|
3523
3740
|
}));
|
|
3524
3741
|
}
|
|
3525
3742
|
case 'practice_run_finish': {
|
|
@@ -3537,33 +3754,42 @@ export function createTools(config) {
|
|
|
3537
3754
|
: undefined;
|
|
3538
3755
|
return practiceErrorResult(response.status, response.json, response.retryAfterSeconds, teaching);
|
|
3539
3756
|
}
|
|
3757
|
+
const consoleFields = await practiceRunConsoleUrlFields(attemptId, workspaceId);
|
|
3540
3758
|
return textResult(secretSafe({
|
|
3541
3759
|
operation: 'finish',
|
|
3542
3760
|
attemptId,
|
|
3761
|
+
...railBrief(response.json?.brief),
|
|
3762
|
+
...(response.json?.nextStep && typeof response.json.nextStep === 'object' ? { nextStep: response.json.nextStep } : {}),
|
|
3543
3763
|
practiceRun: practiceRunProjection(response.json),
|
|
3544
|
-
...
|
|
3764
|
+
...consoleFields,
|
|
3545
3765
|
}));
|
|
3546
3766
|
}
|
|
3547
3767
|
case 'practice_run_report': {
|
|
3548
3768
|
const attemptId = requiredPracticeAttemptId(args);
|
|
3549
3769
|
const workspaceId = optionalWorkspaceId(args);
|
|
3550
|
-
const
|
|
3770
|
+
const detail = requiredReceiptDetail(args);
|
|
3771
|
+
const response = await practiceApi('GET', `/practice/playbacks/${encodeURIComponent(attemptId)}/report?v=2&detail=${detail}`, undefined, { workspaceId });
|
|
3551
3772
|
if (!response.ok) {
|
|
3552
3773
|
const teaching = isPracticeRunUnavailable(response.status, response.json)
|
|
3553
3774
|
? practiceRunUnavailableTeaching(name, attemptId)
|
|
3554
3775
|
: undefined;
|
|
3555
3776
|
return practiceErrorResult(response.status, response.json, response.retryAfterSeconds, teaching);
|
|
3556
3777
|
}
|
|
3778
|
+
// MEG-1571: the default reply is the server-owned comparison card, passed through exactly
|
|
3779
|
+
// as the API emitted it. Only the full document keeps the MCP receipt projection.
|
|
3780
|
+
if (detail === 'card') return textResult(response.json ?? {});
|
|
3781
|
+
const consoleFields = await practiceRunConsoleUrlFields(attemptId, workspaceId);
|
|
3557
3782
|
return textResult({
|
|
3558
3783
|
operation: 'report',
|
|
3559
3784
|
...receiptProjection(response.json ?? {}),
|
|
3560
|
-
...
|
|
3785
|
+
...consoleFields,
|
|
3561
3786
|
});
|
|
3562
3787
|
}
|
|
3563
3788
|
case 'practice_run_impact': {
|
|
3564
3789
|
const attemptId = requiredPracticeAttemptId(args);
|
|
3565
3790
|
const workspaceId = optionalWorkspaceId(args);
|
|
3566
|
-
const
|
|
3791
|
+
const detail = requiredReceiptDetail(args);
|
|
3792
|
+
const response = await practiceApi('GET', `/practice/playbacks/${encodeURIComponent(attemptId)}/impact?detail=${detail}`, undefined, { workspaceId });
|
|
3567
3793
|
if (!response.ok) {
|
|
3568
3794
|
const teaching = isPracticeRunUnavailable(response.status, response.json)
|
|
3569
3795
|
? practiceRunUnavailableTeaching(name, attemptId)
|
|
@@ -3572,21 +3798,30 @@ export function createTools(config) {
|
|
|
3572
3798
|
}
|
|
3573
3799
|
// T1 owns the shape and the API already ran the public-safe scan; pass it through verbatim
|
|
3574
3800
|
// (no array truncation) rather than re-projecting.
|
|
3801
|
+
if (detail === 'card') return textResult(response.json ?? {});
|
|
3575
3802
|
return textResult({ operation: 'impact', ...(response.json ?? {}) });
|
|
3576
3803
|
}
|
|
3577
3804
|
case 'get_connection_details': {
|
|
3578
3805
|
const requestedStoreId = requiredPracticeStoreId(args);
|
|
3579
3806
|
const workspaceId = optionalWorkspaceId(args);
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3807
|
+
const response = await apiRaw(
|
|
3808
|
+
'GET',
|
|
3809
|
+
`/practice/${encodeURIComponent(requestedStoreId)}/connection`,
|
|
3810
|
+
undefined,
|
|
3811
|
+
{ workspaceId, cacheBustRead: true },
|
|
3812
|
+
);
|
|
3813
|
+
if (!response.ok) {
|
|
3814
|
+
const authenticationError = authenticationErrorResult(response.status, response.json);
|
|
3815
|
+
if (authenticationError) return authenticationError;
|
|
3816
|
+
if (response.status === 404) {
|
|
3817
|
+
return practiceStoreConnectionNotFoundGuidance(requestedStoreId, response.json?.nextStep);
|
|
3587
3818
|
}
|
|
3588
|
-
|
|
3819
|
+
return {
|
|
3820
|
+
content: [{ type: 'text', text: JSON.stringify(secretSafe(response.json), null, 2) }],
|
|
3821
|
+
isError: true,
|
|
3822
|
+
};
|
|
3589
3823
|
}
|
|
3824
|
+
const details = response.json;
|
|
3590
3825
|
const storeId = details.storeId ?? requestedStoreId;
|
|
3591
3826
|
const shopDomain = details.host ?? `${requestedStoreId}.meguro.io`;
|
|
3592
3827
|
return textResult({
|
|
@@ -3601,6 +3836,9 @@ export function createTools(config) {
|
|
|
3601
3836
|
adminVersions: details.admin?.versions,
|
|
3602
3837
|
adminVersionPolicy: details.adminVersionPolicy ?? details.admin?.supportPolicy,
|
|
3603
3838
|
adminExecutionGuide: details.adminExecutionGuide,
|
|
3839
|
+
...(details.nextStep && typeof details.nextStep === 'object'
|
|
3840
|
+
? { nextStep: details.nextStep }
|
|
3841
|
+
: {}),
|
|
3604
3842
|
env: {
|
|
3605
3843
|
SHOPIFY_ADMIN_GRAPHQL_URL: details.adminUrl,
|
|
3606
3844
|
SHOPIFY_ADMIN_ACCESS_TOKEN: details.token,
|
|
@@ -3647,6 +3885,7 @@ export function createTools(config) {
|
|
|
3647
3885
|
lookup: requiredString(args, 'lookup'),
|
|
3648
3886
|
name: requiredString(args, 'name'),
|
|
3649
3887
|
...(args.apiVersion !== undefined ? { apiVersion: args.apiVersion } : {}),
|
|
3888
|
+
...(args.storeId !== undefined ? { storeId: requiredString(args, 'storeId') } : {}),
|
|
3650
3889
|
};
|
|
3651
3890
|
return textResult(await api('POST', '/practice/admin-schema', body, { auth: true }));
|
|
3652
3891
|
}
|