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/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: true, openWorldHint: true },
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 compatibility receipt', readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
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
- return /^mcpdel_[A-Za-z0-9_-]{20,128}$/u.test(confirmToken) ? { ...safe, confirmToken } : safe;
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
- if (teaching) stable.teaching = secretSafe(teaching);
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 attempt with attemptId ${attemptId} is available to this account. ${toolName} accepts only a pa-* attemptId returned by practice_run_start.`,
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 assertions = Array.isArray(value?.receiptEvidence?.effectiveAssertions)
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
- rejectedCalls: calls.filter((call) => call?.rejected === true || Number(call?.status) >= 400).length,
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.v1' ? { reportBack } : {}),
1201
- ...(takeHome?.schemaVersion === 'meguro.receipt-take-home.v1' ? { takeHome } : {}),
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 attempts.`,
1245
- practiceAttemptRecovery: 'Call practice_runs_list to discover practice simulation attempts, then pass one exact attemptId to practice_run_report or practice_run_impact.',
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
- .map((attempt) => ({
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 attempt, and Meguro has no cross-attempt comparator today.`,
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 attempt ids.`,
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 attempt returned by practice_run_start, not a history run.`,
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 attempt id.`,
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
- function practiceStoreConnectionNotFoundGuidance(storeId) {
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 attempt id.`,
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 attemptId or an invented id.`,
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 attempt and connected development-store domain pair is unavailable to this account; Meguro does not reveal which member of the pair was absent.'
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 one account onboarding grant 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.',
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 every workspace owned by the token account plus the authoritative MEG-592 capacity projection. The OAuth grant is account-bound, so this account-scoped tool needs no workspace selector and does not re-derive tier limits.',
2306
- inputSchema: { type: 'object', additionalProperties: false, properties: {}, required: [] },
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 attempts 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.',
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 attempts returned by practice_run_start. Optional lifecycle filtering supports running, paused, completed, failed, or cleaning.',
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 attempt 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.',
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 attempt 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.',
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 segment on the same aging store when server authorization allows it. The returned lifecycleContract explains that accepted writes are disposable attempt-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. A fresh account\'s first accepted start may bind its one account-owned onboarding evaluation grant to the chosen store/template/root and pinned ideal horizon; every authorized segment is unmetered, while every run after that horizon follows ordinary tier policy. A fresh start replays the practice store\'s original scenario baseline as segment 1 by default; chaining is 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 onboarding authorization, Builder plans a capped legal segment while Free or Solo refuses an above-cap arc. For an executable onboarding 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 continuation 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, so use return-request-pending to stop on an actionable return before the planned end. 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.',
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 segment 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 segment automatically.' },
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 bounded practice simulation attempt facts for one exact practice store: attemptId, lifecycle state, Store-clock summary, and a receiptRef that can be passed unchanged to practice_run_report. 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.',
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 attempt-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.',
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 attempt-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 attempt and closes its write lane. return-request-pending stops before planned end with the evaluated Store day, pending-request count, and one bounded opportunity for the next read; it never resolves the request automatically. If no opportunity exists before planned end, the explicit confirmation path ends in the stable run-complete refusal for later writes and offers no write suggestion. 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.',
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 attempt and close its write lane.' },
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 segment without inventing time or evidence. A segment at planned end completes; an under-measured segment stops and remains explicitly incomplete. For a stopped onboarding segment, the server-owned practiceRun.recovery object gives the exact practice_run_start call that resumes the same bound root at its authoritative watermark. To abandon that incomplete onboarding evaluation permanently, set both permanentStop and acknowledgeIncompleteOnboardingEvaluation true; Meguro preserves evidence and consumed onboarding value. The response includes the immutable segment receipt and announced deadlines. Console is quick proof; agent execution remains in the caller\'s environment.',
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 onboarding evaluation permanently. Requires acknowledgeIncompleteOnboardingEvaluation=true.' },
2738
- acknowledgeIncompleteOnboardingEvaluation: { type: 'boolean', description: 'Set true with permanentStop to acknowledge that the bound onboarding evaluation will remain incomplete while its evidence and consumed value remain preserved.' },
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 the run\'s receipt — a bounded receipt summary for a completed external-agent practice run. `reportBack` is the exact server-owned customer summary in the fixed order Mechanics, Conduct, Scenario outcome; MCP passes it through without re-deriving or reclassifying any plane, and modeled money closes its narrative. `takeHome` is the frozen take-home packet the issued receipt itself carries — the same ordered Mechanics, Conduct, Scenario outcome facts the public receipt view serves, plus the source URL and SHA-256, the claims/evidence boundary, the verification steps, and a copy-ready `memo`; MCP passes it through unchanged and compiles no part of it. `eventResolution` passes through the exact creation-pinned template identity/revision, evaluation horizon, measured horizon, and typed event-window result; it never recovers current catalog metadata for a historical run. `gate` is the exact server-owned final machine projection; `rejectionAttribution.gateStatus` is mechanically aligned with it, and neither value is re-derived by MCP. Immutable expected-refusal declarations appear with their exact server-emitted matched or failure status; a matched rejection remains in raw receipt counts while only its unhandled Configured Receipt Gate consequence changes. Interpret `resultSemantics.lifecycle`, `evidenceAssertions`, `technicalPolicyVerdict`, and `modeledScenarioOutcome` as independent named dimensions; there is no implicit overall status. Existing `summary`, `defaultVerdict`, `verdict`, and `measurement` fields are legacy compatibility fields. `report` is the protocol/route compatibility name for the receipt (the tool name and the `operation: "report"` field keep it for wire stability); user-facing language is "receipt". The payload includes an unguessable public receipt id, a complete environment-qualified HTTPS URL in `canonicalPath`, and SHA-256: fetch that URL without credentials and verify the exact bytes before promotion. Raw private request and response payloads stay out of MCP; use Console for quick proof while the agent remains in the caller\'s environment. Receipt availability and expiry guidance are server-owned and returned by the receipt response.',
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 the impact receipt for a completed external-agent practice run: the base year — the same store, same days, without your agent — compared with your agent\'s year, showing what changed because of your agent (orders, units, and revenue), a footprint of every write, and an integrity gate that reports a reason instead of numbers when the comparison is not clean. `eventResolution` is the exact creation-pinned template identity/revision, evaluation horizon, measured horizon, and typed event-window result; historical runs without that snapshot are explicitly unavailable and never consult current template metadata. The payload includes an unguessable public receipt id, a complete environment-qualified HTTPS URL in `canonicalPath`, and SHA-256: fetch that URL without credentials and verify the exact bytes before promotion. Read-only; never mutates the run and returns no credentials. Takes the run\'s attemptId (from practice_run_start), not the practice-store id (storeId). Receipt availability and expiry guidance are server-owned and returned by the receipt response.',
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. Store-free and pre-credit: it creates or reads no practice store, run, attempt, ledger, probe, or checkpoint, consumes no credit, and never executes the submitted document. The two modes are exclusive: pass lookup + name or query, never both.`,
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
- throw new Error(`${name} arguments do not match the declared input schema: ${error}${correction ? `. Corrected call: ${correction}` : ''}`);
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 response = await fleetRequest(name, 'GET', '/workspaces');
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
- response = await fleetRequest(name, 'GET', '/catalog-slices', undefined, {
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: started.attemptId ?? started.practiceRun?.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
- ...consoleUrlFields(started.consoleRef, started.consoleRefExpiresAt),
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
- const listPath = queryPath('/practice/playbacks', { worldId: requestedStoreId ?? storeIds[0] });
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
- ...consoleUrlFields(state.consoleRef, state.consoleRefExpiresAt),
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
- ...consoleUrlFields(response.json?.consoleRef, response.json?.consoleRefExpiresAt),
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
- ...consoleUrlFields(advanced.consoleRef, advanced.consoleRefExpiresAt),
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
- ...consoleUrlFields(response.json?.consoleRef, response.json?.consoleRefExpiresAt),
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 response = await practiceApi('GET', `/practice/playbacks/${encodeURIComponent(attemptId)}/report?v=2`, undefined, { workspaceId });
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
- ...consoleUrlFields(response.json?.consoleRef, response.json?.consoleRefExpiresAt),
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 response = await practiceApi('GET', `/practice/playbacks/${encodeURIComponent(attemptId)}/impact`, undefined, { workspaceId });
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
- let details;
3581
- try {
3582
- details = await api('GET', `/practice/${encodeURIComponent(requestedStoreId)}/connection`, undefined, { auth: true, workspaceId });
3583
- } catch (error) {
3584
- if (error instanceof ToolResultError) throw error;
3585
- if (error instanceof Error && error.message.startsWith('404 ')) {
3586
- return practiceStoreConnectionNotFoundGuidance(requestedStoreId);
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
- throw error;
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
  }