@cueai/omni-reader-mcp 1.2.1 → 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
@@ -60,7 +60,7 @@ differ from the numbers above, report the live values.
60
60
  Always use an audited exact version, never an implicit `latest`:
61
61
 
62
62
  ```sh
63
- npx -y @cueai/omni-reader-mcp@1.2.1 setup
63
+ npx -y @cueai/omni-reader-mcp@1.2.2 setup
64
64
  ```
65
65
 
66
66
  The interactive setup supports Hermes, Cursor, Claude Desktop, and generic stdio
@@ -68,9 +68,9 @@ configuration. Non-interactive installation uses the same argument parsing and w
68
68
  logic:
69
69
 
70
70
  ```sh
71
- npx -y @cueai/omni-reader-mcp@1.2.1 setup --client hermes --allowed-root /absolute/minimum/root --yes --json
72
- npx -y @cueai/omni-reader-mcp@1.2.1 setup --client cursor --add-root /absolute/minimum/root --yes --json
73
- npx -y @cueai/omni-reader-mcp@1.2.1 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
74
74
  ```
75
75
 
76
76
  When an agent or script runs under a pty (stdin is still a TTY), declare non-interactive
@@ -78,7 +78,7 @@ mode explicitly with `--headless` (alias `--non-interactive`): no `--yes` is req
78
78
  stdin is never read:
79
79
 
80
80
  ```sh
81
- npx -y @cueai/omni-reader-mcp@1.2.1 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
82
82
  ```
83
83
 
84
84
  ## Cache and journal isolation
@@ -198,16 +198,16 @@ new source that satisfies the constraints.
198
198
  ## Commands
199
199
 
200
200
  ```sh
201
- npx -y @cueai/omni-reader-mcp@1.2.1 doctor
202
- npx -y @cueai/omni-reader-mcp@1.2.1 doctor --json
203
- npx -y @cueai/omni-reader-mcp@1.2.1 clean
204
- npx -y @cueai/omni-reader-mcp@1.2.1 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
205
205
  ```
206
206
 
207
207
  Running the pinned version without a command starts the stdio MCP server:
208
208
 
209
209
  ```sh
210
- npx -y @cueai/omni-reader-mcp@1.2.1
210
+ npx -y @cueai/omni-reader-mcp@1.2.2
211
211
  ```
212
212
 
213
213
  `doctor --json` returns package/npm/client adapter, Key present/absent, allowed-root
@@ -216,12 +216,12 @@ status; it never prints the Key, private source paths, or content.
216
216
 
217
217
  ## Uninstall and rollback
218
218
 
219
- `uninstall --yes --json` removes only the currently trusted 1.2.1 Bridge entry; when a
219
+ `uninstall --yes --json` removes only a trusted 1.2.1 or 1.2.2 Bridge entry; when a
220
220
  matching trusted backup exists, it restores the original URL-only `omni-reader` entry.
221
221
  Uninstall never deletes user source files and never silently removes unexpired local
222
222
  results.
223
223
 
224
- To roll back from 1.1.1:
224
+ To roll back from 1.2.2:
225
225
 
226
226
  1. stop recommending or installing that version;
227
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.1";
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.1";
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;
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();
@@ -186,8 +186,29 @@ function decodeEnvelope(value, requestId) {
186
186
  throw protocolError();
187
187
  }
188
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
+ }
189
210
  try {
190
- return parseResultSchema.parse(result.structuredContent);
211
+ return parseResultSchema.parse(candidate);
191
212
  }
192
213
  catch {
193
214
  throw protocolError();
@@ -302,7 +323,10 @@ export class HttpRemoteOmniClient {
302
323
  retryable: false,
303
324
  });
304
325
  }
305
- let args = { source: classified.source };
326
+ let args = {
327
+ source: classified.source,
328
+ wait: false,
329
+ };
306
330
  if (detail === "grounded" || detail === "layout") {
307
331
  // D2-D Task 14: the URL profile is obtained and selected BEFORE any
308
332
  // non-text tools/call is constructed. Detail is sent only for a valid
@@ -310,7 +334,7 @@ export class HttpRemoteOmniClient {
310
334
  // UNSUPPORTED_DETAIL and zero tools/call requests.
311
335
  const capabilities = await this.initializeCapabilities(signal);
312
336
  selectUrlProfile(capabilities, detail);
313
- args = { source: classified.source, detail };
337
+ args = { source: classified.source, detail, wait: false };
314
338
  }
315
339
  return this.#call("parse", args, clientRequestId, signal);
316
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.1",
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",