@cueai/omni-reader-mcp 1.2.0 → 1.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/README.md CHANGED
@@ -23,9 +23,10 @@ summarization, Q&A, field extraction — rather than stopping at "parse done".
23
23
  ## Requirements
24
24
 
25
25
  - Node.js 20.12 or newer.
26
- - Cue API Key: <https://cuecue.cn/api-key>.
27
- - New accounts receive free credits to try; the current grant and daily allowance follow
28
- the server-side onboarding policy and the `doctor` output.
26
+ - Cue API Key: <https://cuecue.cn/hub/api-key>.
27
+ - New accounts can try Omni without paying; see [Free credits](#free-credits) below. The
28
+ server-side onboarding policy and the `doctor` output are the live authority for current
29
+ allowances.
29
30
  - Configure `CUE_API_KEY` in the agent's secret store or local environment.
30
31
  **Never paste the API Key into a conversation.**
31
32
 
@@ -38,12 +39,28 @@ Bridge reads only these product configuration variables at runtime:
38
39
  authorizes; colon-separated on macOS/Linux, semicolon-separated on Windows. The current
39
40
  agent workspace remains the default allowed scope.
40
41
 
42
+ ## Free credits
43
+
44
+ As of 2026-08-14:
45
+
46
+ - every account receives 10 free credits daily — roughly 150 pages of ordinary documents,
47
+ 75 pages of scanned images or charts, 30 minutes of audio, or 4 minutes of video;
48
+ - new accounts receive a one-time 50-credit gift when obtaining `CUE_API_KEY` (60 credits
49
+ available on day one, including the daily grant);
50
+ - inviting a new user who registers gives both the inviter and the invitee 50 credits, with
51
+ no invite limit; when an invited user subscribes, the inviter additionally receives 10% of
52
+ the invitee's first-month credit quota as a bonus.
53
+
54
+ When available, the `doctor` output reports the live first-registration gift and daily grant;
55
+ referral rules and conversion ratios follow the server-side onboarding policy. If live values
56
+ differ from the numbers above, report the live values.
57
+
41
58
  ## Installation and configuration
42
59
 
43
60
  Always use an audited exact version, never an implicit `latest`:
44
61
 
45
62
  ```sh
46
- npx -y @cueai/omni-reader-mcp@1.2.0 setup
63
+ npx -y @cueai/omni-reader-mcp@1.2.2 setup
47
64
  ```
48
65
 
49
66
  The interactive setup supports Hermes, Cursor, Claude Desktop, and generic stdio
@@ -51,9 +68,9 @@ configuration. Non-interactive installation uses the same argument parsing and w
51
68
  logic:
52
69
 
53
70
  ```sh
54
- npx -y @cueai/omni-reader-mcp@1.2.0 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
55
- npx -y @cueai/omni-reader-mcp@1.2.0 setup --client cursor --add-root /absolute/minimum/root --yes --json
56
- npx -y @cueai/omni-reader-mcp@1.2.0 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
71
+ npx -y @cueai/omni-reader-mcp@1.2.2 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
72
+ npx -y @cueai/omni-reader-mcp@1.2.2 setup --client cursor --add-root /absolute/minimum/root --yes --json
73
+ npx -y @cueai/omni-reader-mcp@1.2.2 setup --client claude-desktop --allowed-root /absolute/minimum/root --yes --json
57
74
  ```
58
75
 
59
76
  When an agent or script runs under a pty (stdin is still a TTY), declare non-interactive
@@ -61,7 +78,7 @@ mode explicitly with `--headless` (alias `--non-interactive`): no `--yes` is req
61
78
  stdin is never read:
62
79
 
63
80
  ```sh
64
- npx -y @cueai/omni-reader-mcp@1.2.0 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
81
+ npx -y @cueai/omni-reader-mcp@1.2.2 setup --client cursor --allowed-root /absolute/minimum/root --headless --json
65
82
  ```
66
83
 
67
84
  ## Cache and journal isolation
@@ -181,16 +198,16 @@ new source that satisfies the constraints.
181
198
  ## Commands
182
199
 
183
200
  ```sh
184
- npx -y @cueai/omni-reader-mcp@1.2.0 doctor
185
- npx -y @cueai/omni-reader-mcp@1.2.0 doctor --json
186
- npx -y @cueai/omni-reader-mcp@1.2.0 clean
187
- npx -y @cueai/omni-reader-mcp@1.2.0 uninstall --yes --json
201
+ npx -y @cueai/omni-reader-mcp@1.2.2 doctor
202
+ npx -y @cueai/omni-reader-mcp@1.2.2 doctor --json
203
+ npx -y @cueai/omni-reader-mcp@1.2.2 clean
204
+ npx -y @cueai/omni-reader-mcp@1.2.2 uninstall --yes --json
188
205
  ```
189
206
 
190
207
  Running the pinned version without a command starts the stdio MCP server:
191
208
 
192
209
  ```sh
193
- npx -y @cueai/omni-reader-mcp@1.2.0
210
+ npx -y @cueai/omni-reader-mcp@1.2.2
194
211
  ```
195
212
 
196
213
  `doctor --json` returns package/npm/client adapter, Key present/absent, allowed-root
@@ -199,12 +216,12 @@ status; it never prints the Key, private source paths, or content.
199
216
 
200
217
  ## Uninstall and rollback
201
218
 
202
- `uninstall --yes --json` removes only the currently trusted 1.2.0 Bridge entry; when a
219
+ `uninstall --yes --json` removes only a trusted 1.2.1 or 1.2.2 Bridge entry; when a
203
220
  matching trusted backup exists, it restores the original URL-only `omni-reader` entry.
204
221
  Uninstall never deletes user source files and never silently removes unexpired local
205
222
  results.
206
223
 
207
- To roll back from 1.1.1:
224
+ To roll back from 1.2.2:
208
225
 
209
226
  1. stop recommending or installing that version;
210
227
  2. run `uninstall --yes --json` to restore the trusted URL-only entry;
@@ -7,6 +7,10 @@ export interface AgentConfigEnvironment {
7
7
  readonly env?: Readonly<Record<string, string | undefined>>;
8
8
  }
9
9
  export type AgentConfigStatus = "configured" | "not configured" | "invalid or unreadable";
10
+ export interface AgentConfigInspection {
11
+ readonly status: AgentConfigStatus;
12
+ readonly version?: string;
13
+ }
10
14
  interface JsonPreviousEntry {
11
15
  readonly format: "json";
12
16
  readonly value: unknown;
@@ -40,6 +44,7 @@ export declare function buildAgentEntry(target: AgentTarget, extraRoots: readonl
40
44
  export declare function prepareAgentConfig(target: AgentTarget, extraRoots: readonly string[], environment: AgentConfigEnvironment): Promise<PreparedAgentConfig>;
41
45
  export declare function verifyPreparedAgentConfig(prepared: PreparedAgentConfig): Promise<void>;
42
46
  export declare function writePreparedAgentConfig(prepared: PreparedAgentConfig): Promise<void>;
47
+ export declare function inspectAgentConfigDetails(configPath: string, environment?: AgentConfigEnvironment, target?: NativeAgentTarget): Promise<AgentConfigInspection>;
43
48
  export declare function inspectAgentConfig(configPath: string, environment?: AgentConfigEnvironment, target?: NativeAgentTarget): Promise<AgentConfigStatus>;
44
49
  export declare function detectAgentTargets(environment: AgentConfigEnvironment): Promise<NativeAgentTarget[]>;
45
50
  export declare function configContainsOmni(configPath: string): Promise<boolean>;
@@ -4,7 +4,9 @@ import { chmod, lstat, mkdir, open, realpath, rename, unlink, } from "node:fs/pr
4
4
  import path from "node:path";
5
5
  import { BRIDGE_RELEASE_VERSION, REMOTE_OMNI_MCP_URL, } from "../constants.js";
6
6
  const PACKAGE_SPEC = `@cueai/omni-reader-mcp@${BRIDGE_RELEASE_VERSION}`;
7
+ const PREVIOUS_PACKAGE_SPEC = "@cueai/omni-reader-mcp@1.2.1";
7
8
  const LEGACY_PACKAGE_SPEC = "@cueai/omni-reader-mcp";
9
+ const TRUSTED_EXACT_PACKAGE_SPECS = new Set([PREVIOUS_PACKAGE_SPEC, PACKAGE_SPEC]);
8
10
  function isRecord(value) {
9
11
  return value !== null && typeof value === "object" && !Array.isArray(value);
10
12
  }
@@ -543,7 +545,8 @@ function isExpectedOmniEntry(target, value) {
543
545
  !Array.isArray(args) ||
544
546
  args.length !== 2 ||
545
547
  args[0] !== "-y" ||
546
- args[1] !== PACKAGE_SPEC)
548
+ typeof args[1] !== "string" ||
549
+ !TRUSTED_EXACT_PACKAGE_SPECS.has(args[1]))
547
550
  return false;
548
551
  const keys = Object.keys(value).sort();
549
552
  if (value.env === undefined) {
@@ -579,6 +582,12 @@ function isExpectedOmniEntry(target, value) {
579
582
  && keys[1] === "command"
580
583
  && keys[2] === "env";
581
584
  }
585
+ function expectedBridgeVersion(target, value) {
586
+ if (!isExpectedOmniEntry(target, value))
587
+ return undefined;
588
+ const packageSpec = value.args[1];
589
+ return packageSpec === PREVIOUS_PACKAGE_SPEC ? "1.2.1" : BRIDGE_RELEASE_VERSION;
590
+ }
582
591
  function isLegacyBridgeEntry(target, value) {
583
592
  if (!isRecord(value) || value.command !== "npx")
584
593
  return false;
@@ -586,7 +595,8 @@ function isLegacyBridgeEntry(target, value) {
586
595
  if (!Array.isArray(args) ||
587
596
  args.length !== 2 ||
588
597
  args[0] !== "-y" ||
589
- (args[1] !== LEGACY_PACKAGE_SPEC && args[1] !== PACKAGE_SPEC))
598
+ typeof args[1] !== "string" ||
599
+ (args[1] !== LEGACY_PACKAGE_SPEC && !TRUSTED_EXACT_PACKAGE_SPECS.has(args[1])))
590
600
  return false;
591
601
  if (value.env === undefined)
592
602
  return target !== "hermes";
@@ -742,6 +752,7 @@ export async function prepareAgentConfig(target, extraRoots, environment) {
742
752
  environment,
743
753
  reload: reloadInstruction("hermes"),
744
754
  format,
755
+ serializedBefore: loaded.serialized,
745
756
  serializedAfter,
746
757
  previousEntry,
747
758
  };
@@ -786,9 +797,11 @@ async function atomicReplace(filePath, content) {
786
797
  await handle.sync();
787
798
  await handle.close();
788
799
  handle = undefined;
800
+ await chmod(temporaryPath, 0o600);
789
801
  await rename(temporaryPath, filePath);
790
- await chmod(filePath, 0o600);
791
- await syncDirectory(directory);
802
+ // Rename is the commit point. Do not throw afterward: callers cannot safely
803
+ // roll back a committed user-config write without racing an external editor.
804
+ await syncDirectory(directory).catch(() => undefined);
792
805
  }
793
806
  finally {
794
807
  await handle?.close();
@@ -912,6 +925,17 @@ export async function verifyPreparedAgentConfig(prepared) {
912
925
  throw fileError("The Agent configuration changed after preview; no changes were written.");
913
926
  }
914
927
  }
928
+ async function restoreTrustedBackup(backupPath, backup) {
929
+ if (backup !== undefined) {
930
+ await atomicReplace(backupPath, `${JSON.stringify(backup, null, 2)}\n`);
931
+ return;
932
+ }
933
+ await unlink(backupPath).catch((error) => {
934
+ if (error.code !== "ENOENT")
935
+ throw error;
936
+ });
937
+ await syncDirectory(path.dirname(backupPath));
938
+ }
915
939
  export async function writePreparedAgentConfig(prepared) {
916
940
  if (prepared.configPath === undefined ||
917
941
  prepared.environment === undefined ||
@@ -919,36 +943,56 @@ export async function writePreparedAgentConfig(prepared) {
919
943
  prepared.serializedAfter === undefined)
920
944
  return;
921
945
  await withConfigLock(prepared.configPath, prepared.environment, async () => {
922
- const current = await readConfig(prepared.configPath);
946
+ const configPath = prepared.configPath;
947
+ const current = await readConfig(configPath);
923
948
  if (current.fingerprint !== prepared.sourceFingerprint) {
924
949
  throw fileError("The Agent configuration changed after preview; no changes were written.");
925
950
  }
926
951
  const backup = trustedBackup(prepared);
927
- const backupPath = agentBackupPath(prepared.configPath);
928
- const existingBackup = await readTrustedBackup(prepared.configPath);
929
- if (existingBackup === undefined) {
930
- await atomicReplace(backupPath, `${JSON.stringify(backup, null, 2)}\n`);
931
- }
932
- else {
933
- const currentEntryMatches = await configHasExpectedEntry(prepared.target, current);
934
- if (currentEntryMatches && existingBackup.target === prepared.target) {
935
- const updatedBackup = {
952
+ const backupPath = agentBackupPath(configPath);
953
+ const existingBackup = await readTrustedBackup(configPath);
954
+ let replacementBackup = backup;
955
+ if (existingBackup !== undefined) {
956
+ const currentEntry = expectedConfigEntry(prepared.target, current);
957
+ if (currentEntry !== undefined && existingBackup.target === prepared.target) {
958
+ if (existingBackup.bridge_entry_digest !== entryDigest(currentEntry)) {
959
+ throw fileError("A conflicting trusted Omni backup already exists; no changes were written.");
960
+ }
961
+ replacementBackup = {
936
962
  ...existingBackup,
937
963
  bridge_entry_digest: entryDigest(prepared.entry),
938
964
  };
939
- if (JSON.stringify(updatedBackup) !== JSON.stringify(existingBackup)) {
940
- await atomicReplace(backupPath, `${JSON.stringify(updatedBackup, null, 2)}\n`);
941
- }
942
965
  }
943
966
  else if (JSON.stringify(existingBackup) !== JSON.stringify(backup)) {
944
967
  throw fileError("A conflicting trusted Omni backup already exists; no changes were written.");
945
968
  }
969
+ else {
970
+ replacementBackup = existingBackup;
971
+ }
946
972
  }
947
- const immediatelyBeforeWrite = await readConfig(prepared.configPath);
948
- if (immediatelyBeforeWrite.fingerprint !== current.fingerprint) {
949
- throw fileError("The Agent configuration changed during setup; no changes were written.");
973
+ const backupNeedsWrite = existingBackup === undefined ||
974
+ JSON.stringify(replacementBackup) !== JSON.stringify(existingBackup);
975
+ try {
976
+ if (backupNeedsWrite) {
977
+ await atomicReplace(backupPath, `${JSON.stringify(replacementBackup, null, 2)}\n`);
978
+ }
979
+ const immediatelyBeforeWrite = await readConfig(configPath);
980
+ if (immediatelyBeforeWrite.fingerprint !== current.fingerprint) {
981
+ throw fileError("The Agent configuration changed during setup; no changes were written.");
982
+ }
983
+ await atomicReplace(configPath, prepared.serializedAfter);
984
+ }
985
+ catch (error) {
986
+ try {
987
+ if (backupNeedsWrite) {
988
+ await restoreTrustedBackup(backupPath, existingBackup);
989
+ }
990
+ }
991
+ catch {
992
+ throw fileError("The Agent configuration write failed and trusted rollback state could not be restored.");
993
+ }
994
+ throw error;
950
995
  }
951
- await atomicReplace(prepared.configPath, prepared.serializedAfter);
952
996
  });
953
997
  }
954
998
  function jsonConfigEntry(value) {
@@ -956,49 +1000,64 @@ function jsonConfigEntry(value) {
956
1000
  return undefined;
957
1001
  return onlyOmniEntry(value.mcpServers).value;
958
1002
  }
959
- async function configHasExpectedEntry(target, loaded) {
1003
+ function expectedConfigEntry(target, loaded) {
960
1004
  if (target === "hermes") {
961
1005
  const document = parseHermesDocument(loaded.serialized);
962
1006
  const entry = hermesOmniEntry(document);
963
- return entry !== undefined && isExpectedOmniEntry(target, parseHermesEntry(entry));
1007
+ if (entry === undefined)
1008
+ return undefined;
1009
+ const value = parseHermesEntry(entry);
1010
+ return isExpectedOmniEntry(target, value) ? value : undefined;
964
1011
  }
965
1012
  assertNoDuplicateJsonOmniEntries(loaded.serialized);
966
- const value = parseJsonConfig(loaded);
967
- return isExpectedOmniEntry(target, jsonConfigEntry(value));
1013
+ const value = jsonConfigEntry(parseJsonConfig(loaded));
1014
+ return isExpectedOmniEntry(target, value) ? value : undefined;
968
1015
  }
969
- export async function inspectAgentConfig(configPath, environment, target) {
1016
+ async function configHasExpectedEntry(target, loaded) {
1017
+ return expectedConfigEntry(target, loaded) !== undefined;
1018
+ }
1019
+ export async function inspectAgentConfigDetails(configPath, environment, target) {
970
1020
  try {
971
1021
  if (environment !== undefined)
972
1022
  await validateUserConfigPath(configPath, environment);
973
1023
  const loaded = await readConfig(configPath);
974
1024
  if (!loaded.existed)
975
- return "not configured";
1025
+ return { status: "not configured" };
976
1026
  const resolvedTarget = target ?? (configPath.endsWith("config.yaml") ? "hermes" : "cursor");
977
1027
  if (resolvedTarget === "hermes") {
978
1028
  const document = parseHermesDocument(loaded.serialized);
979
1029
  const entry = hermesOmniEntry(document);
980
1030
  if (entry === undefined)
981
- return "not configured";
1031
+ return { status: "not configured" };
982
1032
  const value = parseHermesEntry(entry);
983
- if (isExpectedOmniEntry("hermes", value))
984
- return "configured";
985
- return isCanonicalRemoteEntry(value) ? "not configured" : "invalid or unreadable";
1033
+ const version = expectedBridgeVersion("hermes", value);
1034
+ if (version !== undefined)
1035
+ return { status: "configured", version };
1036
+ return {
1037
+ status: isCanonicalRemoteEntry(value) ? "not configured" : "invalid or unreadable",
1038
+ };
986
1039
  }
987
1040
  assertNoDuplicateJsonOmniEntries(loaded.serialized);
988
1041
  const value = parseJsonConfig(loaded);
989
1042
  if (!isRecord(value.mcpServers))
990
- return "not configured";
1043
+ return { status: "not configured" };
991
1044
  const entry = onlyOmniEntry(value.mcpServers).value;
992
1045
  if (entry === undefined)
993
- return "not configured";
994
- if (isExpectedOmniEntry(resolvedTarget, entry))
995
- return "configured";
996
- return isCanonicalRemoteEntry(entry) ? "not configured" : "invalid or unreadable";
1046
+ return { status: "not configured" };
1047
+ const version = expectedBridgeVersion(resolvedTarget, entry);
1048
+ if (version !== undefined)
1049
+ return { status: "configured", version };
1050
+ return {
1051
+ status: isCanonicalRemoteEntry(entry) ? "not configured" : "invalid or unreadable",
1052
+ };
997
1053
  }
998
1054
  catch {
999
- return "invalid or unreadable";
1055
+ return { status: "invalid or unreadable" };
1000
1056
  }
1001
1057
  }
1058
+ export async function inspectAgentConfig(configPath, environment, target) {
1059
+ return (await inspectAgentConfigDetails(configPath, environment, target)).status;
1060
+ }
1002
1061
  export async function detectAgentTargets(environment) {
1003
1062
  const detected = [];
1004
1063
  for (const target of ["hermes", "cursor", "claude-desktop"]) {
@@ -1056,6 +1115,15 @@ export async function rollbackPreparedAgentConfig(prepared) {
1056
1115
  if (!current.existed || current.serialized !== prepared.serializedAfter) {
1057
1116
  throw fileError("The Agent configuration changed before rollback; automatic rollback stopped.");
1058
1117
  }
1118
+ const backupPath = agentBackupPath(prepared.configPath);
1119
+ const backup = await readTrustedBackup(prepared.configPath);
1120
+ const predecessor = prepared.existed
1121
+ ? expectedConfigEntry(prepared.target, {
1122
+ existed: true,
1123
+ serialized: prepared.serializedBefore,
1124
+ fingerprint: prepared.sourceFingerprint ?? "unused",
1125
+ })
1126
+ : undefined;
1059
1127
  if (prepared.existed) {
1060
1128
  await atomicReplace(prepared.configPath, prepared.serializedBefore);
1061
1129
  }
@@ -1063,10 +1131,22 @@ export async function rollbackPreparedAgentConfig(prepared) {
1063
1131
  await unlink(prepared.configPath);
1064
1132
  await syncDirectory(path.dirname(prepared.configPath));
1065
1133
  }
1066
- await unlink(agentBackupPath(prepared.configPath)).catch((error) => {
1067
- if (error.code !== "ENOENT")
1068
- throw error;
1069
- });
1134
+ if (backup !== undefined &&
1135
+ predecessor !== undefined &&
1136
+ backup.target === prepared.target &&
1137
+ backup.source_fingerprint !== prepared.sourceFingerprint) {
1138
+ const restoredBackup = {
1139
+ ...backup,
1140
+ bridge_entry_digest: entryDigest(predecessor),
1141
+ };
1142
+ await atomicReplace(backupPath, `${JSON.stringify(restoredBackup, null, 2)}\n`);
1143
+ }
1144
+ else {
1145
+ await unlink(backupPath).catch((error) => {
1146
+ if (error.code !== "ENOENT")
1147
+ throw error;
1148
+ });
1149
+ }
1070
1150
  await syncDirectory(path.dirname(prepared.configPath));
1071
1151
  });
1072
1152
  }
@@ -1,9 +1,9 @@
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 { BRIDGE_RELEASE_VERSION, DEFAULT_CUBE_BASE_URL, GRANTED_STREAM_PROTOCOL_VERSION, MAX_FILE_BYTES, CUBE_GRANT_PROTOCOL_VERSION, } from "../constants.js";
4
+ import { DEFAULT_CUBE_BASE_URL, GRANTED_STREAM_PROTOCOL_VERSION, MAX_FILE_BYTES, CUBE_GRANT_PROTOCOL_VERSION, } from "../constants.js";
5
5
  import { API_KEY_URL, getOnboardingPolicyWithTimeout, onboardingGuidance, } from "../onboarding-policy.js";
6
- import { agentConfigPath, inspectAgentConfig, } from "./agent-config.js";
6
+ import { agentConfigPath, inspectAgentConfigDetails, } from "./agent-config.js";
7
7
  const CUBE_HEALTH_PATH = "/api/omni-reader/direct-upload/v1/health";
8
8
  const GRANTED_UPLOAD_HEALTH_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/health";
9
9
  function isHealthBody(value) {
@@ -137,13 +137,9 @@ async function clientAdapterFacts(options) {
137
137
  ["claude_desktop", "claude-desktop"],
138
138
  ]) {
139
139
  const configPath = agentConfigPath(target, options);
140
- const status = configPath === undefined
141
- ? "not configured"
142
- : await inspectAgentConfig(configPath, options, target);
143
- result[label] = {
144
- status,
145
- ...(status === "configured" ? { version: BRIDGE_RELEASE_VERSION } : {}),
146
- };
140
+ result[label] = configPath === undefined
141
+ ? { status: "not configured" }
142
+ : await inspectAgentConfigDetails(configPath, options, target);
147
143
  }
148
144
  return result;
149
145
  }
@@ -6,7 +6,7 @@ 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
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.2.0";
9
+ export declare const BRIDGE_RELEASE_VERSION = "1.2.2";
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
@@ -6,7 +6,7 @@ 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
8
  export const DEFAULT_IIIS_GRANTED_BASE_URL = "https://cubefile.ai.iiis.co:9443/omni/granted/";
9
- export const BRIDGE_RELEASE_VERSION = "1.2.0";
9
+ export const BRIDGE_RELEASE_VERSION = "1.2.2";
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;
@@ -1,10 +1,13 @@
1
1
  export declare const ONBOARDING_POLICY_URL = "https://cuecue.cn/api/v1/billing/public/onboarding-policy";
2
- export declare const API_KEY_URL = "https://cuecue.cn/api-key";
2
+ export declare const API_KEY_URL = "https://cuecue.cn/hub/api-key";
3
+ export declare const LEGACY_API_KEY_URL = "https://cuecue.cn/api-key";
4
+ type ApiKeyUrl = typeof API_KEY_URL | typeof LEGACY_API_KEY_URL;
3
5
  export interface OnboardingPolicy {
4
- readonly apiKeyUrl: typeof API_KEY_URL;
6
+ readonly apiKeyUrl: ApiKeyUrl;
5
7
  readonly firstRegistrationCredits: number;
6
8
  readonly freeDailyCredits: number;
7
9
  }
8
10
  export declare function getOnboardingPolicy(fetchImpl: typeof fetch, signal: AbortSignal): Promise<OnboardingPolicy | undefined>;
9
11
  export declare function onboardingGuidance(policy: OnboardingPolicy | undefined): string;
10
12
  export declare function getOnboardingPolicyWithTimeout(fetchImpl: typeof fetch, timeoutMs?: number): Promise<OnboardingPolicy | undefined>;
13
+ export {};
@@ -1,5 +1,6 @@
1
1
  export const ONBOARDING_POLICY_URL = "https://cuecue.cn/api/v1/billing/public/onboarding-policy";
2
- export const API_KEY_URL = "https://cuecue.cn/api-key";
2
+ export const API_KEY_URL = "https://cuecue.cn/hub/api-key";
3
+ export const LEGACY_API_KEY_URL = "https://cuecue.cn/api-key";
3
4
  function isRecord(value) {
4
5
  return value !== null && typeof value === "object" && !Array.isArray(value);
5
6
  }
@@ -9,12 +10,16 @@ function boundedCredits(value) {
9
10
  function normalizeOnboardingPolicy(value) {
10
11
  if (!isRecord(value))
11
12
  return undefined;
12
- if (value.api_key_url !== API_KEY_URL ||
13
+ const apiKeyUrl = value.api_key_url;
14
+ const accepted = apiKeyUrl === API_KEY_URL || apiKeyUrl === LEGACY_API_KEY_URL;
15
+ if (!accepted ||
13
16
  !boundedCredits(value.first_registration) ||
14
17
  !boundedCredits(value.free_daily))
15
18
  return undefined;
16
19
  return {
17
- apiKeyUrl: API_KEY_URL,
20
+ // Preserve the server's actual accepted URL so a legacy-serving backend
21
+ // keeps working until it flips to the Hub path.
22
+ apiKeyUrl: apiKeyUrl,
18
23
  firstRegistrationCredits: value.first_registration,
19
24
  freeDailyCredits: value.free_daily,
20
25
  };
package/dist/protocol.js CHANGED
@@ -64,7 +64,12 @@ export const cancelParseSchema = z
64
64
  export const readResultSchema = z
65
65
  .object({
66
66
  result_id: resultIdSchema,
67
- cursor: z.string().min(1).max(2048).optional(),
67
+ cursor: z
68
+ .string()
69
+ .min(1)
70
+ .max(2048)
71
+ .describe("Pass the previous result's next_cursor as this cursor.")
72
+ .optional(),
68
73
  max_bytes: z.number().int().min(1).max(RESULT_CHUNK_MAX_BYTES).optional(),
69
74
  })
70
75
  .strict();
@@ -1,6 +1,7 @@
1
1
  import { LATEST_PROTOCOL_VERSION } from "@modelcontextprotocol/sdk/types.js";
2
2
  import { parseReaderCapabilities, selectUrlProfile, } from "./capabilities.js";
3
3
  import { BRIDGE_RELEASE_VERSION, REMOTE_CAPABILITIES_CUSTOM_FIELD, REMOTE_OMNI_MCP_URL, } from "./constants.js";
4
+ import { API_KEY_URL } from "./onboarding-policy.js";
4
5
  import { OmniBridgeError } from "./errors.js";
5
6
  import { parseResultSchema } from "./result-contract.js";
6
7
  import { classifySource } from "./source.js";
@@ -185,8 +186,29 @@ function decodeEnvelope(value, requestId) {
185
186
  throw protocolError();
186
187
  }
187
188
  const result = envelope.result;
189
+ let candidate = result.structuredContent;
190
+ if (candidate === undefined) {
191
+ const content = result.content;
192
+ if (!Array.isArray(content) || content.length !== 1) {
193
+ throw protocolError();
194
+ }
195
+ const item = content[0];
196
+ if (item === null
197
+ || typeof item !== "object"
198
+ || Array.isArray(item)
199
+ || item.type !== "text"
200
+ || typeof item.text !== "string") {
201
+ throw protocolError();
202
+ }
203
+ try {
204
+ candidate = JSON.parse(item.text);
205
+ }
206
+ catch {
207
+ throw protocolError();
208
+ }
209
+ }
188
210
  try {
189
- return parseResultSchema.parse(result.structuredContent);
211
+ return parseResultSchema.parse(candidate);
190
212
  }
191
213
  catch {
192
214
  throw protocolError();
@@ -206,7 +228,7 @@ export class HttpRemoteOmniClient {
206
228
  code: "API_KEY_REQUIRED",
207
229
  message: "A Cue API Key is required for Omni parsing.",
208
230
  failureScope: "authentication",
209
- userAction: "Create or configure an API Key at https://cuecue.cn/api-key without pasting it into chat.",
231
+ userAction: `Create or configure an API Key at ${API_KEY_URL} without pasting it into chat.`,
210
232
  operationCreated: false,
211
233
  retryable: false,
212
234
  });
@@ -301,7 +323,10 @@ export class HttpRemoteOmniClient {
301
323
  retryable: false,
302
324
  });
303
325
  }
304
- let args = { source: classified.source };
326
+ let args = {
327
+ source: classified.source,
328
+ wait: false,
329
+ };
305
330
  if (detail === "grounded" || detail === "layout") {
306
331
  // D2-D Task 14: the URL profile is obtained and selected BEFORE any
307
332
  // non-text tools/call is constructed. Detail is sent only for a valid
@@ -309,7 +334,7 @@ export class HttpRemoteOmniClient {
309
334
  // UNSUPPORTED_DETAIL and zero tools/call requests.
310
335
  const capabilities = await this.initializeCapabilities(signal);
311
336
  selectUrlProfile(capabilities, detail);
312
- args = { source: classified.source, detail };
337
+ args = { source: classified.source, detail, wait: false };
313
338
  }
314
339
  return this.#call("parse", args, clientRequestId, signal);
315
340
  }
@@ -70,11 +70,19 @@ export type ParseResult = {
70
70
  status: "canceled";
71
71
  operation_id: string;
72
72
  cleanup_deadline?: string;
73
+ billing?: {
74
+ credits_charged: number;
75
+ credits_remaining: number;
76
+ };
73
77
  data_handling: DataHandling;
74
78
  } | {
75
79
  status: "expired";
76
80
  operation_id: string;
77
81
  requires_user_confirmation: true;
82
+ billing?: {
83
+ credits_charged: number;
84
+ credits_remaining: number;
85
+ };
78
86
  };
79
87
  export declare const stableErrorSchema: z.ZodObject<{
80
88
  ok: z.ZodLiteral<false>;
@@ -2,6 +2,11 @@ import { z } from "zod";
2
2
  import { RESULT_BUNDLE_PROTOCOL_VERSION } from "./protocol.js";
3
3
  const operationIdSchema = z.string().min(1).max(128);
4
4
  const resultIdSchema = z.string().regex(/^result_[A-Za-z0-9_-]{16,64}$/u);
5
+ const nextCursorSchema = z
6
+ .string()
7
+ .min(1)
8
+ .max(2048)
9
+ .describe("Pass this value as cursor in the next read_result call.");
5
10
  const constraintsSchema = z
6
11
  .object({
7
12
  max_bytes: z.number().int().nonnegative().optional(),
@@ -76,7 +81,7 @@ const artifactResultSchema = z
76
81
  result_bytes: z.number().int().nonnegative(),
77
82
  expires_at: z.string().datetime({ offset: true }),
78
83
  preview: z.string(),
79
- next_cursor: z.string().min(1).max(2048),
84
+ next_cursor: nextCursorSchema,
80
85
  })
81
86
  .strict();
82
87
  const billingSchema = z
@@ -105,7 +110,7 @@ const contentStorageSchema = z.discriminatedUnion("kind", [
105
110
  z
106
111
  .object({
107
112
  kind: z.literal("artifact"),
108
- next_cursor: z.string().min(1).max(2048),
113
+ next_cursor: nextCursorSchema,
109
114
  preview: z.string().optional(),
110
115
  })
111
116
  .strict(),
@@ -120,7 +125,7 @@ const groundingStorageSchema = z.discriminatedUnion("kind", [
120
125
  z
121
126
  .object({
122
127
  kind: z.literal("artifact"),
123
- next_cursor: z.string().min(1).max(2048),
128
+ next_cursor: nextCursorSchema,
124
129
  })
125
130
  .strict(),
126
131
  ]);
@@ -218,6 +223,7 @@ export const parseResultSchema = z.discriminatedUnion("status", [
218
223
  status: z.literal("canceled"),
219
224
  operation_id: operationIdSchema,
220
225
  cleanup_deadline: z.string().datetime({ offset: true }).optional(),
226
+ billing: billingSchema.optional(),
221
227
  data_handling: dataHandlingSchema,
222
228
  })
223
229
  .strict(),
@@ -226,6 +232,7 @@ export const parseResultSchema = z.discriminatedUnion("status", [
226
232
  status: z.literal("expired"),
227
233
  operation_id: operationIdSchema,
228
234
  requires_user_confirmation: z.literal(true),
235
+ billing: billingSchema.optional(),
229
236
  })
230
237
  .strict(),
231
238
  ]);
@@ -235,7 +242,7 @@ const resultChunkSchema = z
235
242
  result_bytes: z.number().int().nonnegative(),
236
243
  expires_at: z.string().datetime({ offset: true }),
237
244
  text: z.string(),
238
- next_cursor: z.string().min(1).max(2048).optional(),
245
+ next_cursor: nextCursorSchema.optional(),
239
246
  })
240
247
  .strict();
241
248
  // v3 part read (D2-D item 9): closed chunk with part/media type/offset/
@@ -253,7 +260,7 @@ const bundlePartReadChunkSchema = z
253
260
  offset: z.number().int().nonnegative(),
254
261
  decoded_bytes: z.number().int().nonnegative(),
255
262
  text: z.string(),
256
- next_cursor: z.string().min(1).max(2048).optional(),
263
+ next_cursor: nextCursorSchema.optional(),
257
264
  expires_at: z.string().datetime({ offset: true }),
258
265
  })
259
266
  .strict(),
@@ -266,7 +273,7 @@ const bundlePartReadChunkSchema = z
266
273
  offset: z.number().int().nonnegative(),
267
274
  decoded_bytes: z.number().int().nonnegative(),
268
275
  text: z.string(),
269
- next_cursor: z.string().min(1).max(2048).optional(),
276
+ next_cursor: nextCursorSchema.optional(),
270
277
  expires_at: z.string().datetime({ offset: true }),
271
278
  })
272
279
  .strict(),
package/dist/tools.js CHANGED
@@ -452,7 +452,7 @@ export function registerOmniTools(server, dependencies, taskStore) {
452
452
  },
453
453
  }, (args, extra) => callCancel(args.operation_id, extra, dependencies));
454
454
  server.registerTool("read_result", {
455
- description: "Read one UTF-8 chunk from a Bridge-created local result artifact.",
455
+ description: "Read one UTF-8 chunk from a Bridge-created local result artifact. Pass each returned next_cursor as cursor in the next call until it is absent.",
456
456
  inputSchema: readResultSchema,
457
457
  outputSchema: readResultToolOutputSchema,
458
458
  annotations: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueai/omni-reader-mcp",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "Local stdio MCP bridge for direct Omni document parsing",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",