meguro-mcp 0.2.0 → 0.2.1

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 CHANGED
@@ -2,6 +2,14 @@
2
2
 
3
3
  All notable changes to `meguro-mcp` are recorded here. Versions follow Semantic Versioning.
4
4
 
5
+ ## 0.2.1 — 2026-08-01
6
+
7
+ - Preserves the API's approved wrong/revoked workspace-key code and actionable Connection settings
8
+ guidance through MCP tool errors while keeping malformed authentication responses fail-closed.
9
+ - Keeps authentication failures secret-free, single-attempt, and explicitly classified as HTTP 401
10
+ or 403 without changing unrelated tool-error projections.
11
+ - Pins Console and customer scenario fixtures to the exact `meguro-mcp@0.2.1` artifact.
12
+
5
13
  ## 0.2.0 — 2026-08-01
6
14
 
7
15
  - 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.0
17
+ npx -y meguro-mcp@0.2.1
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.0
164
+ -- npx -y meguro-mcp@0.2.1
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.0"],
174
+ "args": ["-y", "meguro-mcp@0.2.1"],
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meguro-mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Meguro control-plane MCP server: drive worlds, runs, and verdicts from your own AI tools.",
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.0' };
10
+ const SERVER_INFO = { name: 'meguro', version: '0.2.1' };
11
11
  const PROTOCOL_VERSION = '2025-03-26';
12
12
 
13
13
  const tools = createTools({
package/src/tools.mjs CHANGED
@@ -11,6 +11,10 @@ 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 AUTH_TEACHING = Object.freeze({
15
+ 'workspace-api-key-invalid': 'This workspace API key is invalid or revoked. Create or replace it in Connection settings.',
16
+ 'workspace-api-key-revoked': 'This workspace API key is revoked. Create or replace it in Connection settings.',
17
+ });
14
18
  const FLEET_TOOL_NAMES = new Set([
15
19
  'stores_list', 'store_create', 'store_delete', 'store_passport',
16
20
  'workspaces_list', 'workspace_create', 'workspace_archive', 'workspace_unarchive',
@@ -93,6 +97,33 @@ function secretSafe(value, depth = 0) {
93
97
  return value;
94
98
  }
95
99
 
100
+ function authenticationErrorResult(status, value) {
101
+ if (status !== 401 && status !== 403) return null;
102
+ const body = value && typeof value === 'object' ? value : {};
103
+ const candidates = [body.practiceRunError, body.error, body];
104
+ const source = candidates.find((candidate) => candidate && typeof candidate === 'object'
105
+ && typeof candidate.code === 'string' && typeof candidate.message === 'string');
106
+ const routeCode = source?.code
107
+ ?? (typeof body.code === 'string' ? body.code : undefined)
108
+ ?? (typeof body.error === 'string' && body.error !== 'unauthorized' ? body.error : undefined);
109
+ if (routeCode && !Object.hasOwn(AUTH_TEACHING, routeCode)) return null;
110
+ const expectedMessage = source ? AUTH_TEACHING[source.code] : undefined;
111
+ const error = expectedMessage && source.message === expectedMessage
112
+ ? { code: source.code, message: expectedMessage }
113
+ : { message: 'Meguro practice request failed.' };
114
+ return {
115
+ content: [{ type: 'text', text: JSON.stringify({ httpStatus: status, error }, null, 2) }],
116
+ isError: true,
117
+ };
118
+ }
119
+
120
+ class ToolResultError extends Error {
121
+ constructor(result) {
122
+ super('Meguro request failed.');
123
+ this.result = result;
124
+ }
125
+ }
126
+
96
127
  // A store-deletion preview intentionally returns one narrow capability for the caller to
97
128
  // present back on the second step. Keep every other response field behind the normal secret
98
129
  // scrubber and opt this single, format-checked value back in explicitly.
@@ -122,6 +153,8 @@ function catalogSnapshotProjection(value) {
122
153
  }
123
154
 
124
155
  function practiceErrorResult(status, value, retryAfterSeconds) {
156
+ const authenticationError = authenticationErrorResult(status, value);
157
+ if (authenticationError) return authenticationError;
125
158
  const body = value && typeof value === 'object' ? value : {};
126
159
  const stable = { httpStatus: status };
127
160
  if (body.practiceRunError && typeof body.practiceRunError === 'object') {
@@ -664,6 +697,8 @@ export function createTools(config) {
664
697
  let json;
665
698
  try { json = JSON.parse(text); } catch { json = { raw: text }; }
666
699
  if (!response.ok) {
700
+ const authenticationError = authenticationErrorResult(response.status, json);
701
+ if (authenticationError) throw new ToolResultError(authenticationError);
667
702
  const message = json.errors?.[0]?.message ?? json.error ?? text.slice(0, 300);
668
703
  throw new Error(`${response.status} ${path}: ${message}`);
669
704
  }
@@ -786,7 +821,10 @@ export function createTools(config) {
786
821
 
787
822
  async function fleetRequest(toolName, method, path, body, options = {}) {
788
823
  const response = await apiRaw(method, path, body, options);
789
- if (!response.ok) return { error: await fleetErrorResult(toolName, path, response.status, response.json, response.requestId) };
824
+ if (!response.ok) {
825
+ const authenticationError = authenticationErrorResult(response.status, response.json);
826
+ return { error: authenticationError ?? await fleetErrorResult(toolName, path, response.status, response.json, response.requestId) };
827
+ }
790
828
  return { value: response.json };
791
829
  }
792
830
 
@@ -883,7 +921,8 @@ export function createTools(config) {
883
921
  const response = await practiceApi(method, path, body);
884
922
  return response.ok
885
923
  ? { value: response.json ?? {} }
886
- : { error: await examErrorResult(toolName, path, response, attemptId) };
924
+ : { error: authenticationErrorResult(response.status, response.json)
925
+ ?? await examErrorResult(toolName, path, response, attemptId) };
887
926
  }
888
927
 
889
928
  async function examReceiptArtifact(examId, kind, expectedDigest) {
@@ -894,6 +933,8 @@ export function createTools(config) {
894
933
  const text = await response.text();
895
934
  let json;
896
935
  try { json = text ? JSON.parse(text) : null; } catch { json = { examError: { message: 'Meguro returned a non-JSON receipt error.' } }; }
936
+ const authenticationError = authenticationErrorResult(response.status, json);
937
+ if (authenticationError) return { error: authenticationError };
897
938
  const error = await examErrorResult('exam_report', path, {
898
939
  status: response.status,
899
940
  json,
@@ -1999,12 +2040,14 @@ export function createTools(config) {
1999
2040
  // MEG-34: always ask the server to mint a durable Console reference for a successful probe.
2000
2041
  createConsoleReference: true,
2001
2042
  };
2002
- const { ok, json } = await apiRaw('POST', `/practice/${encodeURIComponent(worldId)}/probe/admin`, body);
2043
+ const { ok, status, json } = await apiRaw('POST', `/practice/${encodeURIComponent(worldId)}/probe/admin`, body);
2003
2044
  if (ok) {
2004
2045
  // Replace the internal reference id with a ready-to-open Console URL (opaque ref only).
2005
2046
  const { consoleRef, consoleRefExpiresAt, ...envelope } = json;
2006
2047
  return textResult({ ...envelope, ...consoleUrlFields(consoleRef, consoleRefExpiresAt) });
2007
2048
  }
2049
+ const authenticationError = authenticationErrorResult(status, json);
2050
+ if (authenticationError) return authenticationError;
2008
2051
  // Preserve the structured meguro.admin-probe.v1 error body (code + message) with isError: true,
2009
2052
  // instead of collapsing it to an unstructured thrown message.
2010
2053
  return { content: [{ type: 'text', text: JSON.stringify(secretSafe(json), null, 2) }], isError: true };
@@ -2023,6 +2066,7 @@ export function createTools(config) {
2023
2066
  return errorResult(`Unknown tool: ${name}`);
2024
2067
  }
2025
2068
  } catch (error) {
2069
+ if (error instanceof ToolResultError) return error.result;
2026
2070
  if (FLEET_TOOL_NAMES.has(name)) {
2027
2071
  return fleetErrorResult(name, `mcp:${name}`, 0, {
2028
2072
  errors: [{