meguro-mcp 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/server.mjs +1 -1
- package/src/tools.mjs +90 -21
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `meguro-mcp` are recorded here. Versions follow Semantic Versioning.
|
|
4
4
|
|
|
5
|
+
## 0.2.2 — 2026-08-01
|
|
6
|
+
|
|
7
|
+
- Enforces each tool's declared top-level argument boundary before API transport, with bounded errors
|
|
8
|
+
that do not echo attacker-controlled property names.
|
|
9
|
+
- Rejects practice-store ids and oversized or malformed practice-run ids locally across run,
|
|
10
|
+
continuation, and Exam entry tools while preserving supported historical run-id shapes.
|
|
11
|
+
- Keeps well-formed missing and cross-workspace run ids on the same generic
|
|
12
|
+
`practice-run-unavailable` response so validation does not create an existence oracle.
|
|
13
|
+
|
|
14
|
+
## 0.2.1 — 2026-08-01
|
|
15
|
+
|
|
16
|
+
- Preserves the API's approved wrong/revoked workspace-key code and actionable Connection settings
|
|
17
|
+
guidance through MCP tool errors while keeping malformed authentication responses fail-closed.
|
|
18
|
+
- Keeps authentication failures secret-free, single-attempt, and explicitly classified as HTTP 401
|
|
19
|
+
or 403 without changing unrelated tool-error projections.
|
|
20
|
+
- Pins Console and customer scenario fixtures to the exact `meguro-mcp@0.2.1` artifact.
|
|
21
|
+
|
|
5
22
|
## 0.2.0 — 2026-08-01
|
|
6
23
|
|
|
7
24
|
- Publishes the frozen 43-tool control-plane registry already present in the repository, without
|
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Dependency-free, no build step: the server is plain Node ≥ 20.
|
|
|
14
14
|
Customer MCP clients run the exact public version directly from npm:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npx -y meguro-mcp@0.2.
|
|
17
|
+
npx -y meguro-mcp@0.2.2
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
Pin the version in client configuration. A pinned quickstart stays reproducible and never changes its
|
|
@@ -161,7 +161,7 @@ claude mcp add meguro \
|
|
|
161
161
|
-e MEGURO_API_BASE_URL=https://api-dev.meguro.io \
|
|
162
162
|
-e MEGURO_API_TOKEN=meg_sk_... \
|
|
163
163
|
-e MEGURO_DASHBOARD_URL=https://... \
|
|
164
|
-
-- npx -y meguro-mcp@0.2.
|
|
164
|
+
-- npx -y meguro-mcp@0.2.2
|
|
165
165
|
```
|
|
166
166
|
|
|
167
167
|
## Register — Cursor (`.cursor/mcp.json`)
|
|
@@ -171,7 +171,7 @@ claude mcp add meguro \
|
|
|
171
171
|
"mcpServers": {
|
|
172
172
|
"meguro": {
|
|
173
173
|
"command": "npx",
|
|
174
|
-
"args": ["-y", "meguro-mcp@0.2.
|
|
174
|
+
"args": ["-y", "meguro-mcp@0.2.2"],
|
|
175
175
|
"env": {
|
|
176
176
|
"MEGURO_API_BASE_URL": "https://api-dev.meguro.io",
|
|
177
177
|
"MEGURO_API_TOKEN": "meg_sk_...",
|
package/package.json
CHANGED
package/src/server.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { createInterface } from 'node:readline';
|
|
|
7
7
|
import { documentationResource, documentationResources } from './docs.mjs';
|
|
8
8
|
import { createTools, redactSecrets } from './tools.mjs';
|
|
9
9
|
|
|
10
|
-
const SERVER_INFO = { name: 'meguro', version: '0.2.
|
|
10
|
+
const SERVER_INFO = { name: 'meguro', version: '0.2.2' };
|
|
11
11
|
const PROTOCOL_VERSION = '2025-03-26';
|
|
12
12
|
|
|
13
13
|
const tools = createTools({
|
package/src/tools.mjs
CHANGED
|
@@ -11,6 +11,11 @@ const SECRET_HEADER = /\b(?:authorization|cookie|set-cookie|x-shopify-access-tok
|
|
|
11
11
|
const SECRET_KEY = /(?:authorization|cookie|password|secret|token|api[_-]?key|signedsnapshot)/iu;
|
|
12
12
|
const PRIVATE_RESPONSE_KEY = /^(?:raw|rawBody|rawBodyPreview|responsePayload|requestHeaders|responseHeaders|headers|cookies)$/iu;
|
|
13
13
|
const RESERVED_WORLD_IDS = new Set(['api', 'api-dev', 'www', 'dev', 'hooks', 'control']);
|
|
14
|
+
const PRACTICE_ATTEMPT_ID = /^pa-[a-z0-9][a-z0-9-]{0,124}$/u;
|
|
15
|
+
const AUTH_TEACHING = Object.freeze({
|
|
16
|
+
'workspace-api-key-invalid': 'This workspace API key is invalid or revoked. Create or replace it in Connection settings.',
|
|
17
|
+
'workspace-api-key-revoked': 'This workspace API key is revoked. Create or replace it in Connection settings.',
|
|
18
|
+
});
|
|
14
19
|
const FLEET_TOOL_NAMES = new Set([
|
|
15
20
|
'stores_list', 'store_create', 'store_delete', 'store_passport',
|
|
16
21
|
'workspaces_list', 'workspace_create', 'workspace_archive', 'workspace_unarchive',
|
|
@@ -93,6 +98,33 @@ function secretSafe(value, depth = 0) {
|
|
|
93
98
|
return value;
|
|
94
99
|
}
|
|
95
100
|
|
|
101
|
+
function authenticationErrorResult(status, value) {
|
|
102
|
+
if (status !== 401 && status !== 403) return null;
|
|
103
|
+
const body = value && typeof value === 'object' ? value : {};
|
|
104
|
+
const candidates = [body.practiceRunError, body.error, body];
|
|
105
|
+
const source = candidates.find((candidate) => candidate && typeof candidate === 'object'
|
|
106
|
+
&& typeof candidate.code === 'string' && typeof candidate.message === 'string');
|
|
107
|
+
const routeCode = source?.code
|
|
108
|
+
?? (typeof body.code === 'string' ? body.code : undefined)
|
|
109
|
+
?? (typeof body.error === 'string' && body.error !== 'unauthorized' ? body.error : undefined);
|
|
110
|
+
if (routeCode && !Object.hasOwn(AUTH_TEACHING, routeCode)) return null;
|
|
111
|
+
const expectedMessage = source ? AUTH_TEACHING[source.code] : undefined;
|
|
112
|
+
const error = expectedMessage && source.message === expectedMessage
|
|
113
|
+
? { code: source.code, message: expectedMessage }
|
|
114
|
+
: { message: 'Meguro practice request failed.' };
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: 'text', text: JSON.stringify({ httpStatus: status, error }, null, 2) }],
|
|
117
|
+
isError: true,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
class ToolResultError extends Error {
|
|
122
|
+
constructor(result) {
|
|
123
|
+
super('Meguro request failed.');
|
|
124
|
+
this.result = result;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
96
128
|
// A store-deletion preview intentionally returns one narrow capability for the caller to
|
|
97
129
|
// present back on the second step. Keep every other response field behind the normal secret
|
|
98
130
|
// scrubber and opt this single, format-checked value back in explicitly.
|
|
@@ -122,6 +154,8 @@ function catalogSnapshotProjection(value) {
|
|
|
122
154
|
}
|
|
123
155
|
|
|
124
156
|
function practiceErrorResult(status, value, retryAfterSeconds) {
|
|
157
|
+
const authenticationError = authenticationErrorResult(status, value);
|
|
158
|
+
if (authenticationError) return authenticationError;
|
|
125
159
|
const body = value && typeof value === 'object' ? value : {};
|
|
126
160
|
const stable = { httpStatus: status };
|
|
127
161
|
if (body.practiceRunError && typeof body.practiceRunError === 'object') {
|
|
@@ -248,6 +282,14 @@ function requiredPracticeStoreId(args) {
|
|
|
248
282
|
return validatedStoreId(canonical, provided[0]);
|
|
249
283
|
}
|
|
250
284
|
|
|
285
|
+
function requiredPracticeAttemptId(args, key = 'attemptId') {
|
|
286
|
+
const attemptId = requiredString(args, key);
|
|
287
|
+
if (!PRACTICE_ATTEMPT_ID.test(attemptId)) {
|
|
288
|
+
throw new Error(`${key} must be the pa-... run identity returned by practice_run_start, not a practice-store id (storeId)`);
|
|
289
|
+
}
|
|
290
|
+
return attemptId;
|
|
291
|
+
}
|
|
292
|
+
|
|
251
293
|
function optionalPositiveInteger(args, key) {
|
|
252
294
|
const value = args?.[key];
|
|
253
295
|
if (value === undefined) return undefined;
|
|
@@ -664,6 +706,8 @@ export function createTools(config) {
|
|
|
664
706
|
let json;
|
|
665
707
|
try { json = JSON.parse(text); } catch { json = { raw: text }; }
|
|
666
708
|
if (!response.ok) {
|
|
709
|
+
const authenticationError = authenticationErrorResult(response.status, json);
|
|
710
|
+
if (authenticationError) throw new ToolResultError(authenticationError);
|
|
667
711
|
const message = json.errors?.[0]?.message ?? json.error ?? text.slice(0, 300);
|
|
668
712
|
throw new Error(`${response.status} ${path}: ${message}`);
|
|
669
713
|
}
|
|
@@ -786,7 +830,10 @@ export function createTools(config) {
|
|
|
786
830
|
|
|
787
831
|
async function fleetRequest(toolName, method, path, body, options = {}) {
|
|
788
832
|
const response = await apiRaw(method, path, body, options);
|
|
789
|
-
if (!response.ok)
|
|
833
|
+
if (!response.ok) {
|
|
834
|
+
const authenticationError = authenticationErrorResult(response.status, response.json);
|
|
835
|
+
return { error: authenticationError ?? await fleetErrorResult(toolName, path, response.status, response.json, response.requestId) };
|
|
836
|
+
}
|
|
790
837
|
return { value: response.json };
|
|
791
838
|
}
|
|
792
839
|
|
|
@@ -883,7 +930,8 @@ export function createTools(config) {
|
|
|
883
930
|
const response = await practiceApi(method, path, body);
|
|
884
931
|
return response.ok
|
|
885
932
|
? { value: response.json ?? {} }
|
|
886
|
-
: { error:
|
|
933
|
+
: { error: authenticationErrorResult(response.status, response.json)
|
|
934
|
+
?? await examErrorResult(toolName, path, response, attemptId) };
|
|
887
935
|
}
|
|
888
936
|
|
|
889
937
|
async function examReceiptArtifact(examId, kind, expectedDigest) {
|
|
@@ -894,6 +942,8 @@ export function createTools(config) {
|
|
|
894
942
|
const text = await response.text();
|
|
895
943
|
let json;
|
|
896
944
|
try { json = text ? JSON.parse(text) : null; } catch { json = { examError: { message: 'Meguro returned a non-JSON receipt error.' } }; }
|
|
945
|
+
const authenticationError = authenticationErrorResult(response.status, json);
|
|
946
|
+
if (authenticationError) return { error: authenticationError };
|
|
897
947
|
const error = await examErrorResult('exam_report', path, {
|
|
898
948
|
status: response.status,
|
|
899
949
|
json,
|
|
@@ -1317,7 +1367,7 @@ export function createTools(config) {
|
|
|
1317
1367
|
inputSchema: {
|
|
1318
1368
|
type: 'object', additionalProperties: false,
|
|
1319
1369
|
properties: {
|
|
1320
|
-
attemptId: { type: 'string', minLength:
|
|
1370
|
+
attemptId: { type: 'string', minLength: 4, maxLength: 128, pattern: '^pa-[a-z0-9][a-z0-9-]{0,124}$', description: 'Completed practice-run attempt id carrying immutable Exam capture.' },
|
|
1321
1371
|
shopDomain: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*\\.myshopify\\.com$', description: 'Exact lowercase connected Shopify development-store domain.' },
|
|
1322
1372
|
},
|
|
1323
1373
|
required: ['attemptId', 'shopDomain'],
|
|
@@ -1329,7 +1379,7 @@ export function createTools(config) {
|
|
|
1329
1379
|
inputSchema: {
|
|
1330
1380
|
type: 'object', additionalProperties: false,
|
|
1331
1381
|
properties: {
|
|
1332
|
-
attemptId: { type: 'string', minLength:
|
|
1382
|
+
attemptId: { type: 'string', minLength: 4, maxLength: 128, pattern: '^pa-[a-z0-9][a-z0-9-]{0,124}$', description: 'Completed practice-run attempt id carrying immutable Exam capture.' },
|
|
1333
1383
|
shopDomain: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*\\.myshopify\\.com$', description: 'Exact lowercase connected Shopify development-store domain; this is the typed target confirmation.' },
|
|
1334
1384
|
},
|
|
1335
1385
|
required: ['attemptId', 'shopDomain'],
|
|
@@ -1362,7 +1412,7 @@ export function createTools(config) {
|
|
|
1362
1412
|
properties: {
|
|
1363
1413
|
storeId: { type: 'string', minLength: 1, description: 'Canonical Meguro practice-store id (tenant-owned), as returned by get_connection_details.' },
|
|
1364
1414
|
worldId: { type: 'string', minLength: 1, description: 'Legacy alias for storeId — the same practice-store id under its old name. Prefer storeId.' },
|
|
1365
|
-
continueFromAttemptId: { type: 'string', minLength:
|
|
1415
|
+
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.' },
|
|
1366
1416
|
clock: { type: 'object', description: 'Existing hosted practice-run clock contract.' },
|
|
1367
1417
|
externalAgent: { type: 'object', description: 'Optional bounded external-agent identity metadata.' },
|
|
1368
1418
|
subject: {
|
|
@@ -1388,7 +1438,7 @@ export function createTools(config) {
|
|
|
1388
1438
|
description: 'Read the server-authoritative state of an external-agent practice run, including its announced run-conclusion deadlines and immutable conclusion receipt after any exit. Use Console for quick proof; the tested agent remains in the caller\'s environment.',
|
|
1389
1439
|
inputSchema: {
|
|
1390
1440
|
type: 'object', additionalProperties: false,
|
|
1391
|
-
properties: { attemptId: { type: 'string', minLength:
|
|
1441
|
+
properties: { 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).' } },
|
|
1392
1442
|
required: ['attemptId'],
|
|
1393
1443
|
},
|
|
1394
1444
|
},
|
|
@@ -1397,7 +1447,7 @@ export function createTools(config) {
|
|
|
1397
1447
|
description: 'Capture an immutable checkpoint for an external-agent practice run and return a bounded evidence summary. Console is quick proof; agent execution remains in the caller\'s environment.',
|
|
1398
1448
|
inputSchema: {
|
|
1399
1449
|
type: 'object', additionalProperties: false,
|
|
1400
|
-
properties: { attemptId: { type: 'string', minLength:
|
|
1450
|
+
properties: { 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).' } },
|
|
1401
1451
|
required: ['attemptId'],
|
|
1402
1452
|
},
|
|
1403
1453
|
},
|
|
@@ -1408,7 +1458,7 @@ export function createTools(config) {
|
|
|
1408
1458
|
type: 'object',
|
|
1409
1459
|
additionalProperties: false,
|
|
1410
1460
|
properties: {
|
|
1411
|
-
attemptId: { type: 'string', minLength:
|
|
1461
|
+
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).' },
|
|
1412
1462
|
days: { type: 'integer', minimum: 1, description: 'Whole simulated days to advance.' },
|
|
1413
1463
|
until: { type: 'object', description: 'Existing meguro.practice-run-until.v1 event-gated request.' },
|
|
1414
1464
|
expectedDay: { type: 'integer', minimum: 0, description: 'Current practiceRun.time.watermarkDay.' },
|
|
@@ -1423,7 +1473,7 @@ export function createTools(config) {
|
|
|
1423
1473
|
description: 'Explicitly finish an external-agent practice run without inventing additional time or evidence. The response includes the immutable conclusion receipt and echoes the deadlines announced at start. Console is quick proof; agent execution remains in the caller\'s environment.',
|
|
1424
1474
|
inputSchema: {
|
|
1425
1475
|
type: 'object', additionalProperties: false,
|
|
1426
|
-
properties: { attemptId: { type: 'string', minLength:
|
|
1476
|
+
properties: { 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).' } },
|
|
1427
1477
|
required: ['attemptId'],
|
|
1428
1478
|
},
|
|
1429
1479
|
},
|
|
@@ -1432,7 +1482,7 @@ export function createTools(config) {
|
|
|
1432
1482
|
description: 'Read the run\'s receipt — a bounded receipt summary for a completed external-agent practice run. `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, canonical unauthenticated fetch path, and SHA-256: gate a deploy by fetching that id and verifying 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. Receipts remain fetchable for 7 days on Free, 90 days on Solo, and 365 days on Builder; an expired response teaches the applicable upgrade or a fresh run.',
|
|
1433
1483
|
inputSchema: {
|
|
1434
1484
|
type: 'object', additionalProperties: false,
|
|
1435
|
-
properties: { attemptId: { type: 'string', minLength:
|
|
1485
|
+
properties: { 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).' } },
|
|
1436
1486
|
required: ['attemptId'],
|
|
1437
1487
|
},
|
|
1438
1488
|
},
|
|
@@ -1441,7 +1491,7 @@ export function createTools(config) {
|
|
|
1441
1491
|
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. The payload includes an unguessable public receipt id, canonical unauthenticated fetch path, and SHA-256: gate a deploy by fetching that id and verifying 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). Receipts remain fetchable for 7 days on Free, 90 days on Solo, and 365 days on Builder; an expired response teaches the applicable upgrade or a fresh run.',
|
|
1442
1492
|
inputSchema: {
|
|
1443
1493
|
type: 'object', additionalProperties: false,
|
|
1444
|
-
properties: { attemptId: { type: 'string', minLength:
|
|
1494
|
+
properties: { 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).' } },
|
|
1445
1495
|
required: ['attemptId'],
|
|
1446
1496
|
},
|
|
1447
1497
|
},
|
|
@@ -1494,9 +1544,25 @@ export function createTools(config) {
|
|
|
1494
1544
|
const { title, ...annotations } = presentation;
|
|
1495
1545
|
return { ...definition, title, annotations };
|
|
1496
1546
|
});
|
|
1547
|
+
const definitionsByName = new Map(definitions.map((definition) => [definition.name, definition]));
|
|
1548
|
+
|
|
1549
|
+
function validateTopLevelArguments(name, args) {
|
|
1550
|
+
const definition = definitionsByName.get(name);
|
|
1551
|
+
if (!definition) return;
|
|
1552
|
+
if (!args || typeof args !== 'object' || Array.isArray(args)) {
|
|
1553
|
+
throw new Error(`${name} arguments must be an object`);
|
|
1554
|
+
}
|
|
1555
|
+
if (definition.inputSchema.additionalProperties !== false) return;
|
|
1556
|
+
const allowed = new Set(Object.keys(definition.inputSchema.properties ?? {}));
|
|
1557
|
+
const unexpected = Object.keys(args).filter((key) => !allowed.has(key)).sort();
|
|
1558
|
+
if (unexpected.length > 0) {
|
|
1559
|
+
throw new Error(`${name} arguments must match the declared top-level properties: ${[...allowed].join(', ') || '(none)'}`);
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1497
1562
|
|
|
1498
1563
|
async function call(name, args = {}) {
|
|
1499
1564
|
try {
|
|
1565
|
+
validateTopLevelArguments(name, args);
|
|
1500
1566
|
switch (name) {
|
|
1501
1567
|
case 'docs_read': {
|
|
1502
1568
|
const topic = requiredString(args, 'topic');
|
|
@@ -1778,13 +1844,13 @@ export function createTools(config) {
|
|
|
1778
1844
|
)));
|
|
1779
1845
|
}
|
|
1780
1846
|
case 'exam_preflight': {
|
|
1781
|
-
const attemptId =
|
|
1847
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1782
1848
|
const shopDomain = requiredShopifyDevDomain(args);
|
|
1783
1849
|
const response = await examRequest(name, 'POST', '/practice-exams/preflight', { attemptId, shopDomain }, attemptId);
|
|
1784
1850
|
return 'error' in response ? response.error : textResult(secretSafe(response.value));
|
|
1785
1851
|
}
|
|
1786
1852
|
case 'exam_start': {
|
|
1787
|
-
const attemptId =
|
|
1853
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1788
1854
|
const shopDomain = requiredShopifyDevDomain(args);
|
|
1789
1855
|
const created = await examRequest(name, 'POST', '/practice-exams', { attemptId, shopDomain }, attemptId);
|
|
1790
1856
|
if ('error' in created) return created.error;
|
|
@@ -1869,7 +1935,7 @@ export function createTools(config) {
|
|
|
1869
1935
|
const storeId = requiredPracticeStoreId(args);
|
|
1870
1936
|
const body = {
|
|
1871
1937
|
purpose: 'external-agent',
|
|
1872
|
-
...(args.continueFromAttemptId !== undefined ? { continueFromAttemptId: args
|
|
1938
|
+
...(args.continueFromAttemptId !== undefined ? { continueFromAttemptId: requiredPracticeAttemptId(args, 'continueFromAttemptId') } : {}),
|
|
1873
1939
|
...(args.clock !== undefined ? { clock: args.clock } : {}),
|
|
1874
1940
|
...(args.externalAgent !== undefined ? { externalAgent: args.externalAgent } : {}),
|
|
1875
1941
|
...(args.subject !== undefined ? { subject: args.subject } : {}),
|
|
@@ -1893,7 +1959,7 @@ export function createTools(config) {
|
|
|
1893
1959
|
}));
|
|
1894
1960
|
}
|
|
1895
1961
|
case 'practice_run_status': {
|
|
1896
|
-
const attemptId =
|
|
1962
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1897
1963
|
const response = await practiceApi('GET', `/practice/playbacks/${encodeURIComponent(attemptId)}/state`);
|
|
1898
1964
|
if (!response.ok) return practiceErrorResult(response.status, response.json, response.retryAfterSeconds);
|
|
1899
1965
|
const state = response.json ?? {};
|
|
@@ -1905,7 +1971,7 @@ export function createTools(config) {
|
|
|
1905
1971
|
}));
|
|
1906
1972
|
}
|
|
1907
1973
|
case 'practice_run_checkpoint': {
|
|
1908
|
-
const attemptId =
|
|
1974
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1909
1975
|
const response = await practiceApi('POST', `/practice/playbacks/${encodeURIComponent(attemptId)}/checkpoint`, {});
|
|
1910
1976
|
if (!response.ok) return practiceErrorResult(response.status, response.json, response.retryAfterSeconds);
|
|
1911
1977
|
return textResult(secretSafe({
|
|
@@ -1917,7 +1983,7 @@ export function createTools(config) {
|
|
|
1917
1983
|
}));
|
|
1918
1984
|
}
|
|
1919
1985
|
case 'practice_run_advance': {
|
|
1920
|
-
const attemptId =
|
|
1986
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1921
1987
|
const hasDays = args.days !== undefined;
|
|
1922
1988
|
const hasUntil = args.until !== undefined;
|
|
1923
1989
|
if (hasDays === hasUntil) throw new Error('practice_run_advance requires exactly one of days or until');
|
|
@@ -1942,7 +2008,7 @@ export function createTools(config) {
|
|
|
1942
2008
|
}));
|
|
1943
2009
|
}
|
|
1944
2010
|
case 'practice_run_finish': {
|
|
1945
|
-
const attemptId =
|
|
2011
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1946
2012
|
const response = await practiceApi('POST', `/practice/playbacks/${encodeURIComponent(attemptId)}/finish`, {});
|
|
1947
2013
|
if (!response.ok) return practiceErrorResult(response.status, response.json, response.retryAfterSeconds);
|
|
1948
2014
|
return textResult(secretSafe({
|
|
@@ -1953,7 +2019,7 @@ export function createTools(config) {
|
|
|
1953
2019
|
}));
|
|
1954
2020
|
}
|
|
1955
2021
|
case 'practice_run_report': {
|
|
1956
|
-
const attemptId =
|
|
2022
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1957
2023
|
const response = await practiceApi('GET', `/practice/playbacks/${encodeURIComponent(attemptId)}/report?v=2`);
|
|
1958
2024
|
if (!response.ok) return practiceErrorResult(response.status, response.json, response.retryAfterSeconds);
|
|
1959
2025
|
return textResult({
|
|
@@ -1963,7 +2029,7 @@ export function createTools(config) {
|
|
|
1963
2029
|
});
|
|
1964
2030
|
}
|
|
1965
2031
|
case 'practice_run_impact': {
|
|
1966
|
-
const attemptId =
|
|
2032
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1967
2033
|
const response = await practiceApi('GET', `/practice/playbacks/${encodeURIComponent(attemptId)}/impact`);
|
|
1968
2034
|
if (!response.ok) return practiceErrorResult(response.status, response.json, response.retryAfterSeconds);
|
|
1969
2035
|
// T1 owns the shape and the API already ran the public-safe scan; pass it through verbatim
|
|
@@ -1999,12 +2065,14 @@ export function createTools(config) {
|
|
|
1999
2065
|
// MEG-34: always ask the server to mint a durable Console reference for a successful probe.
|
|
2000
2066
|
createConsoleReference: true,
|
|
2001
2067
|
};
|
|
2002
|
-
const { ok, json } = await apiRaw('POST', `/practice/${encodeURIComponent(worldId)}/probe/admin`, body);
|
|
2068
|
+
const { ok, status, json } = await apiRaw('POST', `/practice/${encodeURIComponent(worldId)}/probe/admin`, body);
|
|
2003
2069
|
if (ok) {
|
|
2004
2070
|
// Replace the internal reference id with a ready-to-open Console URL (opaque ref only).
|
|
2005
2071
|
const { consoleRef, consoleRefExpiresAt, ...envelope } = json;
|
|
2006
2072
|
return textResult({ ...envelope, ...consoleUrlFields(consoleRef, consoleRefExpiresAt) });
|
|
2007
2073
|
}
|
|
2074
|
+
const authenticationError = authenticationErrorResult(status, json);
|
|
2075
|
+
if (authenticationError) return authenticationError;
|
|
2008
2076
|
// Preserve the structured meguro.admin-probe.v1 error body (code + message) with isError: true,
|
|
2009
2077
|
// instead of collapsing it to an unstructured thrown message.
|
|
2010
2078
|
return { content: [{ type: 'text', text: JSON.stringify(secretSafe(json), null, 2) }], isError: true };
|
|
@@ -2023,6 +2091,7 @@ export function createTools(config) {
|
|
|
2023
2091
|
return errorResult(`Unknown tool: ${name}`);
|
|
2024
2092
|
}
|
|
2025
2093
|
} catch (error) {
|
|
2094
|
+
if (error instanceof ToolResultError) return error.result;
|
|
2026
2095
|
if (FLEET_TOOL_NAMES.has(name)) {
|
|
2027
2096
|
return fleetErrorResult(name, `mcp:${name}`, 0, {
|
|
2028
2097
|
errors: [{
|