meguro-mcp 0.2.1 → 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 +9 -0
- package/README.md +3 -3
- package/package.json +1 -1
- package/src/server.mjs +1 -1
- package/src/tools.mjs +43 -18
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
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
|
+
|
|
5
14
|
## 0.2.1 — 2026-08-01
|
|
6
15
|
|
|
7
16
|
- Preserves the API's approved wrong/revoked workspace-key code and actionable Connection settings
|
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,7 @@ 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;
|
|
14
15
|
const AUTH_TEACHING = Object.freeze({
|
|
15
16
|
'workspace-api-key-invalid': 'This workspace API key is invalid or revoked. Create or replace it in Connection settings.',
|
|
16
17
|
'workspace-api-key-revoked': 'This workspace API key is revoked. Create or replace it in Connection settings.',
|
|
@@ -281,6 +282,14 @@ function requiredPracticeStoreId(args) {
|
|
|
281
282
|
return validatedStoreId(canonical, provided[0]);
|
|
282
283
|
}
|
|
283
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
|
+
|
|
284
293
|
function optionalPositiveInteger(args, key) {
|
|
285
294
|
const value = args?.[key];
|
|
286
295
|
if (value === undefined) return undefined;
|
|
@@ -1358,7 +1367,7 @@ export function createTools(config) {
|
|
|
1358
1367
|
inputSchema: {
|
|
1359
1368
|
type: 'object', additionalProperties: false,
|
|
1360
1369
|
properties: {
|
|
1361
|
-
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.' },
|
|
1362
1371
|
shopDomain: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*\\.myshopify\\.com$', description: 'Exact lowercase connected Shopify development-store domain.' },
|
|
1363
1372
|
},
|
|
1364
1373
|
required: ['attemptId', 'shopDomain'],
|
|
@@ -1370,7 +1379,7 @@ export function createTools(config) {
|
|
|
1370
1379
|
inputSchema: {
|
|
1371
1380
|
type: 'object', additionalProperties: false,
|
|
1372
1381
|
properties: {
|
|
1373
|
-
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.' },
|
|
1374
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.' },
|
|
1375
1384
|
},
|
|
1376
1385
|
required: ['attemptId', 'shopDomain'],
|
|
@@ -1403,7 +1412,7 @@ export function createTools(config) {
|
|
|
1403
1412
|
properties: {
|
|
1404
1413
|
storeId: { type: 'string', minLength: 1, description: 'Canonical Meguro practice-store id (tenant-owned), as returned by get_connection_details.' },
|
|
1405
1414
|
worldId: { type: 'string', minLength: 1, description: 'Legacy alias for storeId — the same practice-store id under its old name. Prefer storeId.' },
|
|
1406
|
-
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.' },
|
|
1407
1416
|
clock: { type: 'object', description: 'Existing hosted practice-run clock contract.' },
|
|
1408
1417
|
externalAgent: { type: 'object', description: 'Optional bounded external-agent identity metadata.' },
|
|
1409
1418
|
subject: {
|
|
@@ -1429,7 +1438,7 @@ export function createTools(config) {
|
|
|
1429
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.',
|
|
1430
1439
|
inputSchema: {
|
|
1431
1440
|
type: 'object', additionalProperties: false,
|
|
1432
|
-
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).' } },
|
|
1433
1442
|
required: ['attemptId'],
|
|
1434
1443
|
},
|
|
1435
1444
|
},
|
|
@@ -1438,7 +1447,7 @@ export function createTools(config) {
|
|
|
1438
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.',
|
|
1439
1448
|
inputSchema: {
|
|
1440
1449
|
type: 'object', additionalProperties: false,
|
|
1441
|
-
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).' } },
|
|
1442
1451
|
required: ['attemptId'],
|
|
1443
1452
|
},
|
|
1444
1453
|
},
|
|
@@ -1449,7 +1458,7 @@ export function createTools(config) {
|
|
|
1449
1458
|
type: 'object',
|
|
1450
1459
|
additionalProperties: false,
|
|
1451
1460
|
properties: {
|
|
1452
|
-
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).' },
|
|
1453
1462
|
days: { type: 'integer', minimum: 1, description: 'Whole simulated days to advance.' },
|
|
1454
1463
|
until: { type: 'object', description: 'Existing meguro.practice-run-until.v1 event-gated request.' },
|
|
1455
1464
|
expectedDay: { type: 'integer', minimum: 0, description: 'Current practiceRun.time.watermarkDay.' },
|
|
@@ -1464,7 +1473,7 @@ export function createTools(config) {
|
|
|
1464
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.',
|
|
1465
1474
|
inputSchema: {
|
|
1466
1475
|
type: 'object', additionalProperties: false,
|
|
1467
|
-
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).' } },
|
|
1468
1477
|
required: ['attemptId'],
|
|
1469
1478
|
},
|
|
1470
1479
|
},
|
|
@@ -1473,7 +1482,7 @@ export function createTools(config) {
|
|
|
1473
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.',
|
|
1474
1483
|
inputSchema: {
|
|
1475
1484
|
type: 'object', additionalProperties: false,
|
|
1476
|
-
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).' } },
|
|
1477
1486
|
required: ['attemptId'],
|
|
1478
1487
|
},
|
|
1479
1488
|
},
|
|
@@ -1482,7 +1491,7 @@ export function createTools(config) {
|
|
|
1482
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.',
|
|
1483
1492
|
inputSchema: {
|
|
1484
1493
|
type: 'object', additionalProperties: false,
|
|
1485
|
-
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).' } },
|
|
1486
1495
|
required: ['attemptId'],
|
|
1487
1496
|
},
|
|
1488
1497
|
},
|
|
@@ -1535,9 +1544,25 @@ export function createTools(config) {
|
|
|
1535
1544
|
const { title, ...annotations } = presentation;
|
|
1536
1545
|
return { ...definition, title, annotations };
|
|
1537
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
|
+
}
|
|
1538
1562
|
|
|
1539
1563
|
async function call(name, args = {}) {
|
|
1540
1564
|
try {
|
|
1565
|
+
validateTopLevelArguments(name, args);
|
|
1541
1566
|
switch (name) {
|
|
1542
1567
|
case 'docs_read': {
|
|
1543
1568
|
const topic = requiredString(args, 'topic');
|
|
@@ -1819,13 +1844,13 @@ export function createTools(config) {
|
|
|
1819
1844
|
)));
|
|
1820
1845
|
}
|
|
1821
1846
|
case 'exam_preflight': {
|
|
1822
|
-
const attemptId =
|
|
1847
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1823
1848
|
const shopDomain = requiredShopifyDevDomain(args);
|
|
1824
1849
|
const response = await examRequest(name, 'POST', '/practice-exams/preflight', { attemptId, shopDomain }, attemptId);
|
|
1825
1850
|
return 'error' in response ? response.error : textResult(secretSafe(response.value));
|
|
1826
1851
|
}
|
|
1827
1852
|
case 'exam_start': {
|
|
1828
|
-
const attemptId =
|
|
1853
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1829
1854
|
const shopDomain = requiredShopifyDevDomain(args);
|
|
1830
1855
|
const created = await examRequest(name, 'POST', '/practice-exams', { attemptId, shopDomain }, attemptId);
|
|
1831
1856
|
if ('error' in created) return created.error;
|
|
@@ -1910,7 +1935,7 @@ export function createTools(config) {
|
|
|
1910
1935
|
const storeId = requiredPracticeStoreId(args);
|
|
1911
1936
|
const body = {
|
|
1912
1937
|
purpose: 'external-agent',
|
|
1913
|
-
...(args.continueFromAttemptId !== undefined ? { continueFromAttemptId: args
|
|
1938
|
+
...(args.continueFromAttemptId !== undefined ? { continueFromAttemptId: requiredPracticeAttemptId(args, 'continueFromAttemptId') } : {}),
|
|
1914
1939
|
...(args.clock !== undefined ? { clock: args.clock } : {}),
|
|
1915
1940
|
...(args.externalAgent !== undefined ? { externalAgent: args.externalAgent } : {}),
|
|
1916
1941
|
...(args.subject !== undefined ? { subject: args.subject } : {}),
|
|
@@ -1934,7 +1959,7 @@ export function createTools(config) {
|
|
|
1934
1959
|
}));
|
|
1935
1960
|
}
|
|
1936
1961
|
case 'practice_run_status': {
|
|
1937
|
-
const attemptId =
|
|
1962
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1938
1963
|
const response = await practiceApi('GET', `/practice/playbacks/${encodeURIComponent(attemptId)}/state`);
|
|
1939
1964
|
if (!response.ok) return practiceErrorResult(response.status, response.json, response.retryAfterSeconds);
|
|
1940
1965
|
const state = response.json ?? {};
|
|
@@ -1946,7 +1971,7 @@ export function createTools(config) {
|
|
|
1946
1971
|
}));
|
|
1947
1972
|
}
|
|
1948
1973
|
case 'practice_run_checkpoint': {
|
|
1949
|
-
const attemptId =
|
|
1974
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1950
1975
|
const response = await practiceApi('POST', `/practice/playbacks/${encodeURIComponent(attemptId)}/checkpoint`, {});
|
|
1951
1976
|
if (!response.ok) return practiceErrorResult(response.status, response.json, response.retryAfterSeconds);
|
|
1952
1977
|
return textResult(secretSafe({
|
|
@@ -1958,7 +1983,7 @@ export function createTools(config) {
|
|
|
1958
1983
|
}));
|
|
1959
1984
|
}
|
|
1960
1985
|
case 'practice_run_advance': {
|
|
1961
|
-
const attemptId =
|
|
1986
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1962
1987
|
const hasDays = args.days !== undefined;
|
|
1963
1988
|
const hasUntil = args.until !== undefined;
|
|
1964
1989
|
if (hasDays === hasUntil) throw new Error('practice_run_advance requires exactly one of days or until');
|
|
@@ -1983,7 +2008,7 @@ export function createTools(config) {
|
|
|
1983
2008
|
}));
|
|
1984
2009
|
}
|
|
1985
2010
|
case 'practice_run_finish': {
|
|
1986
|
-
const attemptId =
|
|
2011
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1987
2012
|
const response = await practiceApi('POST', `/practice/playbacks/${encodeURIComponent(attemptId)}/finish`, {});
|
|
1988
2013
|
if (!response.ok) return practiceErrorResult(response.status, response.json, response.retryAfterSeconds);
|
|
1989
2014
|
return textResult(secretSafe({
|
|
@@ -1994,7 +2019,7 @@ export function createTools(config) {
|
|
|
1994
2019
|
}));
|
|
1995
2020
|
}
|
|
1996
2021
|
case 'practice_run_report': {
|
|
1997
|
-
const attemptId =
|
|
2022
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
1998
2023
|
const response = await practiceApi('GET', `/practice/playbacks/${encodeURIComponent(attemptId)}/report?v=2`);
|
|
1999
2024
|
if (!response.ok) return practiceErrorResult(response.status, response.json, response.retryAfterSeconds);
|
|
2000
2025
|
return textResult({
|
|
@@ -2004,7 +2029,7 @@ export function createTools(config) {
|
|
|
2004
2029
|
});
|
|
2005
2030
|
}
|
|
2006
2031
|
case 'practice_run_impact': {
|
|
2007
|
-
const attemptId =
|
|
2032
|
+
const attemptId = requiredPracticeAttemptId(args);
|
|
2008
2033
|
const response = await practiceApi('GET', `/practice/playbacks/${encodeURIComponent(attemptId)}/impact`);
|
|
2009
2034
|
if (!response.ok) return practiceErrorResult(response.status, response.json, response.retryAfterSeconds);
|
|
2010
2035
|
// T1 owns the shape and the API already ran the public-safe scan; pass it through verbatim
|