@cueai/omni-reader-mcp 1.5.3 → 1.5.4

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/README.md CHANGED
@@ -43,6 +43,14 @@ Bridge reads only these product configuration variables at runtime:
43
43
  authorizes; colon-separated on macOS/Linux, semicolon-separated on Windows. The current
44
44
  agent workspace remains the default allowed scope.
45
45
 
46
+ ## How parsing works
47
+
48
+ For a local file, Bridge requests a Cube parse grant and uses the returned controlled
49
+ granted-data-plane URL to upload the file and stream the result. A `BRIDGE_UPGRADE_REQUIRED`
50
+ error means the running Bridge release is not accepted for direct local-file parsing —
51
+ install `@cueai/omni-reader-mcp@1.5.4` using the official setup instructions, then retry the
52
+ same parse. Always run `doctor --json` first when diagnosing a parse failure.
53
+
46
54
  ## Free credits
47
55
 
48
56
  As of 2026-08-14:
@@ -64,7 +72,7 @@ differ from the numbers above, report the live values.
64
72
  Always use an audited exact version, never an implicit `latest`:
65
73
 
66
74
  ```sh
67
- npx -y @cueai/omni-reader-mcp@1.5.3 setup
75
+ npx -y @cueai/omni-reader-mcp@1.5.4 setup
68
76
  ```
69
77
 
70
78
  The interactive setup supports Hermes, Cursor, Claude Desktop, and generic stdio
@@ -72,9 +80,9 @@ configuration. Non-interactive installation uses the same argument parsing and w
72
80
  logic:
73
81
 
74
82
  ```sh
75
- npx -y @cueai/omni-reader-mcp@1.5.3 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
76
- npx -y @cueai/omni-reader-mcp@1.5.3 setup --client cursor --add-root /absolute/minimum/root --yes --json
77
- npx -y @cueai/omni-reader-mcp@1.5.3 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
83
+ npx -y @cueai/omni-reader-mcp@1.5.4 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
84
+ npx -y @cueai/omni-reader-mcp@1.5.4 setup --client cursor --add-root /absolute/minimum/root --yes --json
85
+ npx -y @cueai/omni-reader-mcp@1.5.4 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
78
86
  ```
79
87
 
80
88
  When an agent or script runs under a pty (stdin is still a TTY), declare non-interactive
@@ -82,7 +90,7 @@ mode explicitly with `--headless` (alias `--non-interactive`): no `--yes` is req
82
90
  stdin is never read:
83
91
 
84
92
  ```sh
85
- npx -y @cueai/omni-reader-mcp@1.5.3 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
93
+ npx -y @cueai/omni-reader-mcp@1.5.4 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
86
94
  ```
87
95
 
88
96
  ## Windows configuration
@@ -101,14 +109,14 @@ automatically; the second is the most robust when you configure by hand.
101
109
  1. **`cmd` + `npx` (what `setup` writes on Windows)** — `cmd.exe` is a real executable,
102
110
  so a client's no-shell spawn works:
103
111
  ```json
104
- { "command": "cmd", "args": ["/d", "/c", "npx", "-y", "@cueai/omni-reader-mcp@1.5.3"] }
112
+ { "command": "cmd", "args": ["/d", "/c", "npx", "-y", "@cueai/omni-reader-mcp@1.5.4"] }
105
113
  ```
106
114
  2. **`node` + the package entry (most robust)** — skips `npx` entirely; the absolute
107
115
  path must be a stable install you control:
108
116
  ```json
109
117
  { "command": "node", "args": ["C:\\path\\to\\node_modules\\@cueai\\omni-reader-mcp\\dist\\index.js"] }
110
118
  ```
111
- A deliberate local `npm install @cueai/omni-reader-mcp@1.5.3` gives you that stable
119
+ A deliberate local `npm install @cueai/omni-reader-mcp@1.5.4` gives you that stable
112
120
  path. Do not point the entry at an `_npx` cache directory — its hash subdirectory
113
121
  changes between installs.
114
122
  3. **Never use a session/timestamp directory** — such paths are cleaned up, and the
@@ -136,7 +144,9 @@ project-overlap check; mutual containment with a project directory remains forbi
136
144
  1. show the user-scope configuration target and changes;
137
145
  2. write a single, exact-version `omni-reader` facade;
138
146
  3. use a private lock, trusted backup, and atomic replacement;
139
- 4. verify the Omni secure parse environment;
147
+ 4. verify the Cue API Key, Cube control reachability, and client configuration
148
+ (setup does not claim to verify the granted data plane or the ability to parse —
149
+ only a real local-file parse proves that);
140
150
  5. restore the previous configuration when verification fails;
141
151
  6. prompt the corresponding agent to reload or restart.
142
152
 
@@ -251,40 +261,46 @@ satisfies the constraints.
251
261
  ## Commands
252
262
 
253
263
  ```sh
254
- npx -y @cueai/omni-reader-mcp@1.5.3 doctor
255
- npx -y @cueai/omni-reader-mcp@1.5.3 doctor --json
256
- npx -y @cueai/omni-reader-mcp@1.5.3 clean
257
- npx -y @cueai/omni-reader-mcp@1.5.3 uninstall --yes --json
264
+ npx -y @cueai/omni-reader-mcp@1.5.4 doctor
265
+ npx -y @cueai/omni-reader-mcp@1.5.4 doctor --json
266
+ npx -y @cueai/omni-reader-mcp@1.5.4 clean
267
+ npx -y @cueai/omni-reader-mcp@1.5.4 uninstall --yes --json
258
268
  ```
259
269
 
260
270
  Running the pinned version without a command starts the stdio MCP server:
261
271
 
262
272
  ```sh
263
- npx -y @cueai/omni-reader-mcp@1.5.3
273
+ npx -y @cueai/omni-reader-mcp@1.5.4
264
274
  ```
265
275
 
266
276
  `doctor --json` returns package/npm/client adapter, Key present/absent, allowed-root
267
- safety status, endpoint compatibility, artifacts, cache mode, onboarding, and reload
268
- status; it never prints the Key, private source paths, or content.
277
+ safety status, the authenticated Cube control/configuration facts, artifacts, cache
278
+ mode, onboarding, and reload status; it never prints the Key, private source paths,
279
+ or content. The `endpoints.direct_upload` field is a fact about the release, not a
280
+ probe: the granted data plane is reached only through a Cube grant and is never
281
+ definitively "available" until a real local-file parse validates the route.
282
+
269
283
 
270
284
  ## Network diagnostics
271
285
 
272
- Run `npx -y @cueai/omni-reader-mcp@1.5.3 doctor --json` first. Diagnose failures by
286
+ Run `npx -y @cueai/omni-reader-mcp@1.5.4 doctor --json` first. Diagnose failures by
273
287
  stage instead of guessing an endpoint:
274
288
 
275
289
  | Code | Stage | Meaning |
276
290
  |---|---|---|
277
291
  | `CUBE_UNAVAILABLE` | before upload | the control plane could not create a parse grant |
278
292
  | `PRINCIPAL_CONCURRENCY_LIMIT` | before upload | the principal already has the maximum active parses; wait for `retry_after`, then retry the same parse |
293
+ | `BRIDGE_UPGRADE_REQUIRED` | before upload | this Bridge release is too old; install `@cueai/omni-reader-mcp@1.5.4` via the official setup, then retry the same parse |
279
294
  | `IIIS_UNAVAILABLE` | after grant creation | the granted upload stage could not complete |
280
295
  | `CUBE_PROTOCOL_ERROR` | control-plane response | the response did not match the required contract |
281
296
  | `MCP error -32000: Connection closed` | before Bridge starts | the client launcher shape is invalid; see Windows configuration |
282
297
 
283
- Use only the compatibility and endpoint facts reported by `doctor`. Do not guess or
284
- publish service hosts or ports, hard-code IP addresses, modify hosts files, or probe an
285
- endpoint copied from logs. If an enterprise network policy blocks a required connection,
286
- provide the redacted `doctor --json` result to support for the applicable egress
287
- requirements.
298
+ Use only the control-plane and configuration facts reported by `doctor`. Do not guess
299
+ or publish service hosts or ports, hard-code IP addresses, modify hosts files, or probe
300
+ an endpoint copied from logs. `doctor` does not probe the granted data plane: only a
301
+ real local-file parse proves it end-to-end. If an enterprise network policy blocks a
302
+ required connection, provide the redacted `doctor --json` result to support for the
303
+ applicable egress requirements.
288
304
 
289
305
  If the secure parse environment is reachable but a specific public URL fails to parse
290
306
  (`parse_timeout` / parse failed) while it works in a browser, that is a fetch-layer
@@ -293,12 +309,12 @@ the same way from every client.
293
309
 
294
310
  ## Uninstall and rollback
295
311
 
296
- `uninstall --yes --json` removes only a trusted 1.5.2 or 1.5.3 Bridge entry, plus
312
+ `uninstall --yes --json` removes only a trusted 1.5.3 or 1.5.4 Bridge entry, plus
297
313
  the exact broken bare-`npx` Windows entry written by 1.5.1. When a matching trusted
298
314
  backup exists, it restores the original URL-only `omni-reader` entry. Uninstall never
299
315
  deletes user source files and never silently removes unexpired local results.
300
316
 
301
- To roll back from 1.5.3:
317
+ To roll back from 1.5.4:
302
318
 
303
319
  1. stop recommending or installing that version;
304
320
  2. run `uninstall --yes --json` to restore the trusted URL-only entry;
@@ -7,14 +7,13 @@ const PACKAGE_SPEC = `@cueai/omni-reader-mcp@${BRIDGE_RELEASE_VERSION}`;
7
7
  // The release that the current version is a trusted upgrade from: normal upgrade,
8
8
  // uninstall, and rollback recognize exactly {previous, current}. Bump this to the
9
9
  // version we just published when BRIDGE_RELEASE_VERSION advances.
10
- const PREVIOUS_RELEASE_VERSION = "1.5.2";
10
+ const PREVIOUS_RELEASE_VERSION = "1.5.3";
11
11
  const PREVIOUS_PACKAGE_SPEC = `@cueai/omni-reader-mcp@${PREVIOUS_RELEASE_VERSION}`;
12
12
  // Preserve the one evidenced two-release migration exception: 1.5.1 setup wrote a
13
13
  // bare `npx` launcher on Windows, so later setup/uninstall versions must still be able
14
14
  // to identify and replace that otherwise-unrunnable entry. 1.5.2 already writes `cmd`.
15
15
  const WINDOWS_BARE_NPX_MIGRATION_VERSION = "1.5.1";
16
16
  const WINDOWS_BARE_NPX_MIGRATION_SPEC = `@cueai/omni-reader-mcp@${WINDOWS_BARE_NPX_MIGRATION_VERSION}`;
17
- const LEGACY_PACKAGE_SPEC = "@cueai/omni-reader-mcp";
18
17
  const TRUSTED_EXACT_PACKAGE_SPECS = new Set([PREVIOUS_PACKAGE_SPEC, PACKAGE_SPEC]);
19
18
  function isRecord(value) {
20
19
  return value !== null && typeof value === "object" && !Array.isArray(value);
@@ -641,20 +640,13 @@ export function expectedBridgeVersion(target, value, platform) {
641
640
  return BRIDGE_RELEASE_VERSION;
642
641
  }
643
642
  function isLegacyBridgeEntry(target, value, platform) {
644
- // Keep the historical unpinned package form recognizable for setup migration;
645
- // every exact-version form delegates to the single platform policy above.
646
- const legacyUnpinnedNpx = (entry) => {
647
- const args = entry.args;
648
- return entry.command === "npx"
649
- && Array.isArray(args)
650
- && args.length === 2
651
- && args[0] === "-y"
652
- && args[1] === LEGACY_PACKAGE_SPEC;
653
- };
654
- if (!isRecord(value))
655
- return false;
656
- const shapeOk = legacyUnpinnedNpx(value) || entryInvocationMatches(value, platform);
657
- if (!shapeOk)
643
+ // Defer the entire invocation-shape decision to the single platform policy:
644
+ // exact pinned {1.5.3 previous, 1.5.4 current} plus the exact Windows 1.5.1
645
+ // bare-npx migration source. The historical platform-agnostic acceptance of a
646
+ // bare unpinned `npx -y @cueai/omni-reader-mcp` is removed: it widened the
647
+ // frozen trust set to a floating latest on every OS, which expectedBridgeVersion
648
+ // already rejects but the predecessor gate still admitted.
649
+ if (!isRecord(value) || !entryInvocationMatches(value, platform))
658
650
  return false;
659
651
  if (value.env === undefined)
660
652
  return target !== "hermes";
@@ -19,7 +19,7 @@ export declare function fetchLatestPublishedVersion(fetchImpl: typeof fetch, tim
19
19
  export declare function versionCheckFacts(installed: string, latest: string | undefined): VersionCheck;
20
20
  export interface HealthResult {
21
21
  readonly cubeProtocol: string;
22
- readonly iiisProtocol: string;
22
+ readonly relayStatus: "reporting-only";
23
23
  }
24
24
  export interface DoctorOptions extends AgentConfigEnvironment {
25
25
  readonly env: NodeJS.ProcessEnv;
@@ -70,7 +70,6 @@ export interface DoctorReport {
70
70
  }
71
71
  type AgentConfigStatusForReport = "configured" | "not configured" | "invalid or unreadable";
72
72
  export declare function checkCubeHealth(fetchImpl: typeof fetch, apiKey: string): Promise<string>;
73
- export declare function checkIiisHealth(fetchImpl: typeof fetch): Promise<string>;
74
73
  export declare function checkHealth(fetchImpl: typeof fetch, apiKey: string): Promise<HealthResult>;
75
74
  export declare function runDoctor(options: DoctorOptions): Promise<DoctorReport>;
76
75
  export declare function renderDoctor(report: DoctorReport): string[];
@@ -1,12 +1,18 @@
1
1
  import { constants as fsConstants } from "node:fs";
2
2
  import { lstat, open, readdir } from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { DEFAULT_CUBE_BASE_URL, GRANTED_STREAM_PROTOCOL_VERSION, MAX_FILE_BYTES, CUBE_GRANT_PROTOCOL_VERSION, } from "../constants.js";
4
+ import { DEFAULT_CUBE_BASE_URL, MAX_FILE_BYTES, CUBE_GRANT_PROTOCOL_VERSION, } from "../constants.js";
5
5
  import { API_KEY_URL, getOnboardingPolicyWithTimeout, onboardingGuidance, } from "../onboarding-policy.js";
6
6
  import { agentConfigPath, inspectAgentConfigDetails, } from "./agent-config.js";
7
7
  const CUBE_HEALTH_PATH = "/api/omni-reader/direct-upload/v1/health";
8
- const GRANTED_UPLOAD_HEALTH_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/health";
9
8
  const NPM_LATEST_URL = "https://registry.npmjs.org/@cueai/omni-reader-mcp/latest";
9
+ // Local-file parsing reaches the data plane only through the controlled origin
10
+ // returned by a Cube grant. There is no public relay /health endpoint to probe and
11
+ // no compatibility claim to make from a retired direct path, so doctor reports a
12
+ // static, zero-side-effect fact. It deliberately does NOT assert "available": the
13
+ // granted data-plane route is validated only by the first real local-file parse,
14
+ // so the status is explicitly "not probed" until then.
15
+ const RELAY_DATA_PLANE_STATUS = "not probed (validated on the first real local-file parse)";
10
16
  const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
11
17
  // Numeric semver comparison, not string equality: "1.3.9" < "1.3.10" lexically reverses
12
18
  // (string comparison would see "1.3.9" > "1.3.10"), and a local/dev build can legitimately
@@ -95,19 +101,14 @@ export async function checkCubeHealth(fetchImpl, apiKey) {
95
101
  }
96
102
  return cube.protocol_version;
97
103
  }
98
- export async function checkIiisHealth(fetchImpl) {
99
- const granted = await fetchHealth(fetchImpl, GRANTED_UPLOAD_HEALTH_URL);
100
- if (!granted.enabled || granted.protocol_version !== GRANTED_STREAM_PROTOCOL_VERSION) {
101
- throw new Error("Omni direct upload is disabled or incompatible");
102
- }
103
- return granted.protocol_version;
104
- }
105
104
  export async function checkHealth(fetchImpl, apiKey) {
106
- const [cubeProtocol, iiisProtocol] = await Promise.all([
107
- checkCubeHealth(fetchImpl, apiKey),
108
- checkIiisHealth(fetchImpl),
109
- ]);
110
- return { cubeProtocol, iiisProtocol };
105
+ const cubeProtocol = await checkCubeHealth(fetchImpl, apiKey);
106
+ // Local-file parsing reaches the granted data-plane only through the controlled
107
+ // origin returned by a Cube grant, so there is no separate endpoint to probe and
108
+ // no compatibility claim to make from the retired direct path. Doctor reports a
109
+ // static, zero-side-effect fact that is explicitly NOT "available": the route is
110
+ // validated by the first real local-file parse.
111
+ return { cubeProtocol, relayStatus: "reporting-only" };
111
112
  }
112
113
  async function artifactExpiry(metadataPath, resultId) {
113
114
  let handle;
@@ -227,13 +228,12 @@ export async function runDoctor(options) {
227
228
  urlControl = "unavailable or incompatible";
228
229
  }
229
230
  }
230
- let directUpload;
231
- try {
232
- directUpload = await checkIiisHealth(options.fetchImpl);
233
- }
234
- catch {
235
- directUpload = "unavailable or incompatible";
236
- }
231
+ // The granted data-plane has no public health endpoint; doctor makes no probe
232
+ // and reports a static fact so it never claims compatibility or availability from
233
+ // a retired path. This is deliberate: the only way to know local-file parsing
234
+ // works end-to-end is to run a real parse and inspect the grant response, so the
235
+ // status stays "not probed" until the first real local-file parse validates it.
236
+ const directUpload = RELAY_DATA_PLANE_STATUS;
237
237
  return {
238
238
  package_version: packageVersion,
239
239
  version_check: SEMVER_PATTERN.test(packageVersion)
@@ -295,10 +295,7 @@ export function renderDoctor(report) {
295
295
  if (report.endpoints.url_control === "unavailable or incompatible") {
296
296
  lines.push(" WARNING: URL parsing will fail until this is reachable.");
297
297
  }
298
- lines.push(`Omni direct upload protocol: ${report.endpoints.direct_upload}`);
299
- if (report.endpoints.direct_upload === "unavailable or incompatible") {
300
- lines.push(" WARNING: local file parsing will fail until this is reachable.");
301
- }
298
+ lines.push(`Omni granted data plane: ${report.endpoints.direct_upload}`);
302
299
  lines.push(`Artifacts: ${report.artifacts.count} file(s), ${report.artifacts.bytes} byte(s)`);
303
300
  lines.push(`Artifact expiry: ${report.artifacts.earliest_expiry ?? (report.artifacts.count === 0 ? "none" : "unknown")}`);
304
301
  lines.push(`Cache: ${report.cache.mode}`);
package/dist/cli/setup.js CHANGED
@@ -96,9 +96,9 @@ async function validateConfiguredEnvironment(options, prepared, apiKey) {
96
96
  await rollbackPreparedAgentConfig(prepared);
97
97
  }
98
98
  catch {
99
- throw new Error("Omni 安全解析环境验证失败,且无法确认 Agent 配置已自动恢复;请停止重试并检查 user-scope 配置。");
99
+ throw new Error("Omni 控制面健康检查(Cue API Key / Cube 控制面)失败,且无法确认 Agent 配置已自动恢复;请停止重试并检查 user-scope 配置。");
100
100
  }
101
- throw new Error("Omni 安全解析环境验证失败;原 Agent 配置已自动恢复。");
101
+ throw new Error("Omni 控制面健康检查(Cue API Key / Cube 控制面)失败;原 Agent 配置已自动恢复。");
102
102
  }
103
103
  }
104
104
  function writeSuccess(options, prepared, roots) {
@@ -139,7 +139,7 @@ export async function runSetup(options) {
139
139
  await checkHealth(options.fetchImpl, apiKey);
140
140
  }
141
141
  catch {
142
- throw new Error("Omni 安全解析环境验证失败;未写入 Agent 配置。");
142
+ throw new Error("Omni 控制面健康检查(Cue API Key / Cube 控制面)失败;未写入 Agent 配置。");
143
143
  }
144
144
  }
145
145
  else {
@@ -5,8 +5,8 @@ export declare const RESULT_CHUNK_MAX_BYTES = 65536;
5
5
  export declare const CUBE_GRANT_PROTOCOL_VERSION = "omni.parse_grant.v1";
6
6
  export declare const GRANTED_STREAM_PROTOCOL_VERSION = "omni.granted_parse_stream.v1";
7
7
  export declare const DEFAULT_CUBE_BASE_URL = "https://mcp.cuecue.cn";
8
- export declare const DEFAULT_IIIS_GRANTED_BASE_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/";
9
- export declare const BRIDGE_RELEASE_VERSION = "1.5.3";
8
+ export declare const DEFAULT_IIIS_GRANTED_BASE_URL = "https://omni-upload.cuecue.cn/omni/granted/";
9
+ export declare const BRIDGE_RELEASE_VERSION = "1.5.4";
10
10
  export declare const REMOTE_OMNI_MCP_URL = "https://mcp.cuecue.cn/api/omni-reader/mcp/";
11
11
  export declare const FOREGROUND_BUDGET_MS = 15000;
12
12
  export declare const STATUS_LONG_POLL_MAX_MS = 20000;
package/dist/constants.js CHANGED
@@ -5,8 +5,8 @@ export const RESULT_CHUNK_MAX_BYTES = 65536;
5
5
  export const CUBE_GRANT_PROTOCOL_VERSION = "omni.parse_grant.v1";
6
6
  export const GRANTED_STREAM_PROTOCOL_VERSION = "omni.granted_parse_stream.v1";
7
7
  export const DEFAULT_CUBE_BASE_URL = "https://mcp.cuecue.cn";
8
- export const DEFAULT_IIIS_GRANTED_BASE_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/";
9
- export const BRIDGE_RELEASE_VERSION = "1.5.3";
8
+ export const DEFAULT_IIIS_GRANTED_BASE_URL = "https://omni-upload.cuecue.cn/omni/granted/";
9
+ export const BRIDGE_RELEASE_VERSION = "1.5.4";
10
10
  export const REMOTE_OMNI_MCP_URL = "https://mcp.cuecue.cn/api/omni-reader/mcp/";
11
11
  export const FOREGROUND_BUDGET_MS = 15_000;
12
12
  export const STATUS_LONG_POLL_MAX_MS = 20_000;
@@ -35,6 +35,19 @@ const principalConcurrencyErrorSchema = z
35
35
  retryable: z.literal(true),
36
36
  })
37
37
  .strict();
38
+ // The strict 426 Bridge-upgrade marker: exactly six closed fields, all literal.
39
+ // Any drift (status, header, body, schema) falls through to the generic
40
+ // nonretryable CUBE_UNAVAILABLE — never a new public code and never server text.
41
+ const bridgeUpgradeRequiredSchema = z
42
+ .object({
43
+ code: z.literal("BRIDGE_UPGRADE_REQUIRED"),
44
+ message: z.literal("This Bridge release is not accepted for direct local-file parsing."),
45
+ file_uploaded: z.literal(false),
46
+ billed: z.literal(false),
47
+ content_released: z.literal(false),
48
+ retryable: z.literal(false),
49
+ })
50
+ .strict();
38
51
  // Closed v3 grant response: the exact tuple of the selected direct profile is
39
52
  // required before the upload phase may start. The tuple is re-checked against
40
53
  // the profile after schema parsing so a drifting server cannot slip through.
@@ -158,6 +171,24 @@ function retryAfterSeconds(response) {
158
171
  return Number.isSafeInteger(seconds) && seconds > 0 ? seconds : undefined;
159
172
  }
160
173
  async function grantResponseError(response) {
174
+ // The strict 426 marker is only recognized when there is exactly a 426 status,
175
+ // no Retry-After, and a body matching the six frozen literal fields. Any drift
176
+ // degrades through `responseError` to the generic nonretryable CUBE_UNAVAILABLE.
177
+ if (response.status === 426 && !response.headers.has("retry-after")) {
178
+ let body;
179
+ try {
180
+ body = await response.json();
181
+ }
182
+ catch {
183
+ return responseError(response.status);
184
+ }
185
+ if (bridgeUpgradeRequiredSchema.safeParse(body).success) {
186
+ return bridgeError("BRIDGE_UPGRADE_REQUIRED", "This Bridge release is not accepted for direct local-file parsing.", false, {
187
+ failureScope: "service",
188
+ userAction: "Upgrade to @cueai/omni-reader-mcp@1.5.4 using the official Omni setup instructions, then retry the same local-file parse.",
189
+ });
190
+ }
191
+ }
161
192
  const retryAfter = retryAfterSeconds(response);
162
193
  if (response.status === 429 && retryAfter !== undefined) {
163
194
  let body;
@@ -88,6 +88,19 @@ function throwIfCanceled(input) {
88
88
  if (input.signal?.aborted)
89
89
  throw canceledAfterUpload();
90
90
  }
91
+ // Cancel (without parsing or draining) a response body we are choosing not to
92
+ // consume, so a drifting long body cannot leave a network stream or connection
93
+ // alive after we return. Never adds an unbounded drain. User-abort precedence is
94
+ // preserved by the callers: they re-check `throwIfCanceled` around this helper.
95
+ function cancelResponseBody(response) {
96
+ try {
97
+ if (response.body !== null)
98
+ void response.body.cancel().catch(() => undefined);
99
+ }
100
+ catch {
101
+ // The body may already be cancelled/locked; nothing more to release.
102
+ }
103
+ }
91
104
  function secureIiisUrl(value) {
92
105
  const url = new URL(value);
93
106
  if (url.protocol !== "https:" || url.username !== "" || url.password !== "") {
@@ -209,6 +222,71 @@ function parserProgress(done, total) {
209
222
  : 40;
210
223
  }
211
224
  async function errorFromResponse(response, input) {
225
+ // Strict post-grant 460 relay marker: status 460, Content-Length exactly 0,
226
+ // Cache-Control exactly no-store, no Content-Type, empty body. Only that exact
227
+ // tuple maps to PROTOCOL_MISMATCH (kept operationCreated=true, post-grant); any
228
+ // header/body/status drift maps to IIIS_UNAVAILABLE and never reflects server
229
+ // text. Stable L1 JSON errors below are unchanged.
230
+ if (response.status === 460) {
231
+ // Reject obvious header drift BEFORE consuming the body: only the exact
232
+ // {Content-Length: 0, Cache-Control: no-store, no Content-Type} header tuple
233
+ // is a candidate for the bodyless PROTOCOL_MISMATCH marker. Any header drift
234
+ // maps straight to IIIS_UNAVAILABLE without reading the body.
235
+ const exactHeaderMarker = response.headers.get("content-length") === "0"
236
+ && response.headers.get("cache-control") === "no-store"
237
+ && !response.headers.has("content-type");
238
+ if (!exactHeaderMarker) {
239
+ // Cancel the unconsumed body so a drifting long response cannot leave a
240
+ // connection alive; never parse or drain it.
241
+ cancelResponseBody(response);
242
+ // User-abort precedence: a cancellation during the request must surface as
243
+ // CANCELED_AFTER_UPLOAD_STARTED, not a generic transport failure.
244
+ if (input.signal?.aborted)
245
+ throw canceledAfterUpload();
246
+ return bridgeError("IIIS_UNAVAILABLE", "IIIS could not complete the parse request.", { retryable: false });
247
+ }
248
+ // Bounded emptiness probe: read a bounded single read (one chunk from the
249
+ // default reader), never an unbounded arrayBuffer. The default reader bounds
250
+ // total-body buffering but a first read may deliver >1 byte, so the chunk may
251
+ // be multi-byte — the probe only needs the first read, not BYOB. A null body
252
+ // is empty by inspection; otherwise peek the first chunk and close the reader.
253
+ // Bodyless is proven ONLY by a closed stream on the first read (done === true)
254
+ // — a zero-length non-final chunk is NOT bodyless (secret bytes may follow) and
255
+ // must degrade to IIIS_UNAVAILABLE. Re-check cancellation immediately after the
256
+ // await and again before classification so an abort during the probe maps to
257
+ // CANCELED_AFTER_UPLOAD_STARTED (operationCreated=true, post-grant).
258
+ let empty = true;
259
+ try {
260
+ if (response.body !== null) {
261
+ const reader = response.body.getReader();
262
+ try {
263
+ const first = await reader.read();
264
+ if (input.signal?.aborted)
265
+ throw canceledAfterUpload();
266
+ empty = first.done === true;
267
+ }
268
+ finally {
269
+ reader.releaseLock();
270
+ }
271
+ }
272
+ }
273
+ catch (error) {
274
+ if (input.signal?.aborted)
275
+ throw canceledAfterUpload();
276
+ cancelResponseBody(response);
277
+ throw bridgeError("IIIS_UNAVAILABLE", "IIIS could not complete the parse request.", { retryable: false });
278
+ }
279
+ if (input.signal?.aborted)
280
+ throw canceledAfterUpload();
281
+ if (empty) {
282
+ return bridgeError("PROTOCOL_MISMATCH", "IIIS returned an invalid response contract.", { retryable: false });
283
+ }
284
+ // Non-bodyless stream: cancel the remainder (bounded, no drain) so secret bytes
285
+ // or a long body cannot linger on the wire. The one-byte probe already read the
286
+ // first chunk; cancel releases the rest without parsing or reflecting it.
287
+ cancelResponseBody(response);
288
+ return bridgeError("IIIS_UNAVAILABLE", "IIIS could not complete the parse request.", { retryable: false });
289
+ }
212
290
  try {
213
291
  const body = await response.json();
214
292
  if (isRecord(body) && typeof body.code === "string") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueai/omni-reader-mcp",
3
- "version": "1.5.3",
3
+ "version": "1.5.4",
4
4
  "description": "Local stdio MCP bridge for direct Omni document parsing",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",