@cueai/omni-reader-mcp 1.5.5 → 1.6.0
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 +70 -37
- package/dist/artifact-store.d.ts +2 -0
- package/dist/artifact-store.js +45 -10
- package/dist/cli/agent-config.js +2 -2
- package/dist/cli/arguments.d.ts +8 -4
- package/dist/cli/arguments.js +62 -7
- package/dist/cli/config-inspection.d.ts +59 -0
- package/dist/cli/config-inspection.js +307 -0
- package/dist/cli/doctor.d.ts +7 -0
- package/dist/cli/doctor.js +37 -2
- package/dist/cli/setup.js +13 -2
- package/dist/constants.d.ts +2 -1
- package/dist/constants.js +4 -3
- package/dist/cursor.d.ts +4 -0
- package/dist/cursor.js +11 -13
- package/dist/index.js +10 -1
- package/dist/operation-journal.d.ts +4 -1
- package/dist/operation-journal.js +85 -15
- package/dist/operation-manager.d.ts +6 -2
- package/dist/operation-manager.js +246 -63
- package/dist/path-normalization.d.ts +5 -0
- package/dist/path-normalization.js +25 -0
- package/dist/path-security.d.ts +2 -1
- package/dist/path-security.js +49 -28
- package/dist/protocol.d.ts +10 -2
- package/dist/protocol.js +19 -7
- package/dist/result-contract.d.ts +46 -6
- package/dist/result-contract.js +118 -3
- package/dist/task-runtime.d.ts +3 -2
- package/dist/task-runtime.js +2 -2
- package/dist/tools.d.ts +4 -0
- package/dist/tools.js +37 -16
- package/package.json +1 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import { constants } from "node:fs";
|
|
2
|
+
import { lstat as nodeLstat, open as nodeOpen, } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { PathNormalizationError, normalizePlatformPath, } from "../path-normalization.js";
|
|
5
|
+
const MAX_CONFIG_BYTES = 1_048_576;
|
|
6
|
+
const EXACT_PACKAGE_SPEC = /^@cueai\/omni-reader-mcp@(\d+\.\d+\.\d+)$/u;
|
|
7
|
+
const REFERENCE_VALUES = new Set([
|
|
8
|
+
"${CUE_API_KEY}",
|
|
9
|
+
"Bearer ${CUE_API_KEY}",
|
|
10
|
+
]);
|
|
11
|
+
const NODE_FILE_SYSTEM = {
|
|
12
|
+
lstat: (candidate) => nodeLstat(candidate, { bigint: true }),
|
|
13
|
+
open: (candidate, flags) => nodeOpen(candidate, flags),
|
|
14
|
+
};
|
|
15
|
+
export class ConfigInspectionError extends Error {
|
|
16
|
+
code;
|
|
17
|
+
constraints;
|
|
18
|
+
constructor(code, message, constraints) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = "ConfigInspectionError";
|
|
21
|
+
this.code = code;
|
|
22
|
+
if (constraints !== undefined)
|
|
23
|
+
this.constraints = constraints;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function pathsFor(platform) {
|
|
27
|
+
return platform === "win32" ? path.win32 : path.posix;
|
|
28
|
+
}
|
|
29
|
+
function inspectionError(code, message, constraints) {
|
|
30
|
+
return new ConfigInspectionError(code, message, constraints);
|
|
31
|
+
}
|
|
32
|
+
function normalizedAbsolutePath(value, platform) {
|
|
33
|
+
if (value.length === 0 || value.includes("\0")) {
|
|
34
|
+
throw inspectionError("INVALID_CONFIG_PATH", "Provide one absolute JSON configuration path.");
|
|
35
|
+
}
|
|
36
|
+
let normalized;
|
|
37
|
+
try {
|
|
38
|
+
normalized = normalizePlatformPath(value, platform);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error instanceof PathNormalizationError) {
|
|
42
|
+
throw inspectionError(error.code, error.message);
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
const paths = pathsFor(platform);
|
|
47
|
+
if (!paths.isAbsolute(normalized)) {
|
|
48
|
+
throw inspectionError("INVALID_CONFIG_PATH", "Provide one absolute JSON configuration path.");
|
|
49
|
+
}
|
|
50
|
+
return paths.normalize(normalized);
|
|
51
|
+
}
|
|
52
|
+
function validateServerName(value) {
|
|
53
|
+
if (value.length === 0 || value.includes("\0")) {
|
|
54
|
+
throw inspectionError("INVALID_SERVER_NAME", "Provide one exact MCP server entry name.");
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function requireSafeRegularFile(details) {
|
|
58
|
+
if (details.isSymbolicLink() || !details.isFile()) {
|
|
59
|
+
throw inspectionError("CONFIG_FILE_UNSAFE", "The selected configuration must be one regular file, not a symlink.");
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function requireAllowedSize(details) {
|
|
63
|
+
if (details.size > BigInt(MAX_CONFIG_BYTES)) {
|
|
64
|
+
throw inspectionError("CONFIG_FILE_TOO_LARGE", "The selected configuration exceeds the 1 MiB inspection limit.", { max_bytes: MAX_CONFIG_BYTES });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function readBoundedConfig(handle) {
|
|
68
|
+
const buffer = Buffer.allocUnsafe(MAX_CONFIG_BYTES + 1);
|
|
69
|
+
let total = 0;
|
|
70
|
+
while (total < buffer.byteLength) {
|
|
71
|
+
const remaining = buffer.byteLength - total;
|
|
72
|
+
const { bytesRead } = await handle.read(buffer, total, remaining, total);
|
|
73
|
+
if (bytesRead === 0)
|
|
74
|
+
break;
|
|
75
|
+
if (!Number.isInteger(bytesRead) || bytesRead < 0 || bytesRead > remaining) {
|
|
76
|
+
throw inspectionError("CONFIG_FILE_UNREADABLE", "The selected configuration file could not be inspected.");
|
|
77
|
+
}
|
|
78
|
+
total += bytesRead;
|
|
79
|
+
}
|
|
80
|
+
if (total > MAX_CONFIG_BYTES) {
|
|
81
|
+
throw inspectionError("CONFIG_FILE_TOO_LARGE", "The selected configuration exceeds the 1 MiB inspection limit.", { max_bytes: MAX_CONFIG_BYTES });
|
|
82
|
+
}
|
|
83
|
+
return buffer.subarray(0, total);
|
|
84
|
+
}
|
|
85
|
+
function requireSameFile(before, after) {
|
|
86
|
+
if (before.dev !== after.dev ||
|
|
87
|
+
before.ino !== after.ino ||
|
|
88
|
+
before.size !== after.size ||
|
|
89
|
+
before.mtimeNs !== after.mtimeNs ||
|
|
90
|
+
before.ctimeNs !== after.ctimeNs) {
|
|
91
|
+
throw inspectionError("CONFIG_FILE_CHANGED", "The selected configuration changed during inspection. Retry after changes stop.");
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function fileAccessError(error) {
|
|
95
|
+
if (error instanceof ConfigInspectionError)
|
|
96
|
+
return error;
|
|
97
|
+
const code = error?.code;
|
|
98
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
99
|
+
return inspectionError("CONFIG_FILE_NOT_FOUND", "The selected configuration file does not exist.");
|
|
100
|
+
}
|
|
101
|
+
if (code === "ELOOP") {
|
|
102
|
+
return inspectionError("CONFIG_FILE_UNSAFE", "The selected configuration must be one regular file, not a symlink.");
|
|
103
|
+
}
|
|
104
|
+
return inspectionError("CONFIG_FILE_UNREADABLE", "The selected configuration file could not be inspected.");
|
|
105
|
+
}
|
|
106
|
+
function isRecord(value) {
|
|
107
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
108
|
+
}
|
|
109
|
+
function unknownInspection(status) {
|
|
110
|
+
return {
|
|
111
|
+
inspection_scope: "explicit_config",
|
|
112
|
+
entry: { status },
|
|
113
|
+
transport: { shape: "unknown" },
|
|
114
|
+
api_key: { status: "unknown" },
|
|
115
|
+
allowed_roots: { safety: "unknown" },
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function commandBasename(command) {
|
|
119
|
+
const normalized = command.replaceAll("\\", "/");
|
|
120
|
+
const basename = normalized.slice(normalized.lastIndexOf("/") + 1).trim();
|
|
121
|
+
return basename.length === 0 ? undefined : basename;
|
|
122
|
+
}
|
|
123
|
+
function commandCategory(basename) {
|
|
124
|
+
const normalized = basename.toLowerCase().replace(/\.(?:cmd|exe)$/u, "");
|
|
125
|
+
if (["npx", "npm", "pnpm", "yarn", "bun"].includes(normalized)) {
|
|
126
|
+
return "package_runner";
|
|
127
|
+
}
|
|
128
|
+
if (["cmd", "powershell", "pwsh", "sh", "bash", "zsh"].includes(normalized)) {
|
|
129
|
+
return "shell";
|
|
130
|
+
}
|
|
131
|
+
if (["node", "deno", "python", "python3"].includes(normalized)) {
|
|
132
|
+
return "runtime";
|
|
133
|
+
}
|
|
134
|
+
return "executable";
|
|
135
|
+
}
|
|
136
|
+
function transportFacts(entry) {
|
|
137
|
+
const command = typeof entry.command === "string" && entry.command.length > 0
|
|
138
|
+
? entry.command
|
|
139
|
+
: undefined;
|
|
140
|
+
const url = typeof entry.url === "string" && entry.url.length > 0
|
|
141
|
+
? entry.url
|
|
142
|
+
: undefined;
|
|
143
|
+
if ((command === undefined) === (url === undefined)) {
|
|
144
|
+
return { shape: "unknown" };
|
|
145
|
+
}
|
|
146
|
+
if (url !== undefined)
|
|
147
|
+
return { shape: "remote_url" };
|
|
148
|
+
const basename = commandBasename(command);
|
|
149
|
+
return basename === undefined
|
|
150
|
+
? { shape: "local_command" }
|
|
151
|
+
: {
|
|
152
|
+
shape: "local_command",
|
|
153
|
+
command: { basename, category: commandCategory(basename) },
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function exactPackageVersion(entry) {
|
|
157
|
+
if (!Array.isArray(entry.args))
|
|
158
|
+
return undefined;
|
|
159
|
+
for (const argument of entry.args) {
|
|
160
|
+
if (typeof argument !== "string")
|
|
161
|
+
continue;
|
|
162
|
+
const match = EXACT_PACKAGE_SPEC.exec(argument);
|
|
163
|
+
if (match !== null)
|
|
164
|
+
return match[1];
|
|
165
|
+
}
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
function credentialCandidates(entry) {
|
|
169
|
+
const candidates = [];
|
|
170
|
+
let malformedContainer = false;
|
|
171
|
+
if (Object.hasOwn(entry, "env")) {
|
|
172
|
+
if (!isRecord(entry.env)) {
|
|
173
|
+
malformedContainer = true;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
for (const [key, value] of Object.entries(entry.env)) {
|
|
177
|
+
if (key.toUpperCase() === "CUE_API_KEY")
|
|
178
|
+
candidates.push(value);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (Object.hasOwn(entry, "headers")) {
|
|
183
|
+
if (!isRecord(entry.headers)) {
|
|
184
|
+
malformedContainer = true;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
for (const [key, value] of Object.entries(entry.headers)) {
|
|
188
|
+
if (key.toLowerCase() === "authorization")
|
|
189
|
+
candidates.push(value);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { candidates, malformedContainer };
|
|
194
|
+
}
|
|
195
|
+
function configuredApiKeyStatus(entry) {
|
|
196
|
+
const { candidates, malformedContainer } = credentialCandidates(entry);
|
|
197
|
+
if (candidates.length === 0)
|
|
198
|
+
return malformedContainer ? "unknown" : "absent";
|
|
199
|
+
let hasReference = false;
|
|
200
|
+
let hasUnknown = malformedContainer;
|
|
201
|
+
for (const candidate of candidates) {
|
|
202
|
+
if (typeof candidate !== "string" || candidate.length === 0) {
|
|
203
|
+
hasUnknown = true;
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (REFERENCE_VALUES.has(candidate)) {
|
|
207
|
+
hasReference = true;
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
if (/[$%{}]/u.test(candidate)) {
|
|
211
|
+
hasUnknown = true;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
return "literal_present";
|
|
215
|
+
}
|
|
216
|
+
if (hasUnknown)
|
|
217
|
+
return "unknown";
|
|
218
|
+
return hasReference ? "reference_present" : "unknown";
|
|
219
|
+
}
|
|
220
|
+
function allowedRootFacts(entry, platform) {
|
|
221
|
+
if (!Object.hasOwn(entry, "env"))
|
|
222
|
+
return { count: 0, safety: "safe" };
|
|
223
|
+
if (!isRecord(entry.env))
|
|
224
|
+
return { safety: "unknown" };
|
|
225
|
+
const rootValue = entry.env.OMNI_ALLOWED_ROOTS;
|
|
226
|
+
if (rootValue === undefined)
|
|
227
|
+
return { count: 0, safety: "safe" };
|
|
228
|
+
if (typeof rootValue !== "string")
|
|
229
|
+
return { safety: "unknown" };
|
|
230
|
+
if (rootValue.length === 0)
|
|
231
|
+
return { count: 0, safety: "safe" };
|
|
232
|
+
const paths = pathsFor(platform);
|
|
233
|
+
const roots = rootValue
|
|
234
|
+
.split(paths.delimiter)
|
|
235
|
+
.filter((root) => root.length > 0);
|
|
236
|
+
if (roots.length === 0)
|
|
237
|
+
return { count: 0, safety: "safe" };
|
|
238
|
+
const safe = roots.every((root) => {
|
|
239
|
+
if (root.includes("\0"))
|
|
240
|
+
return false;
|
|
241
|
+
try {
|
|
242
|
+
return paths.isAbsolute(normalizePlatformPath(root, platform));
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
return { count: roots.length, safety: safe ? "safe" : "unsafe" };
|
|
249
|
+
}
|
|
250
|
+
function inspectParsedConfig(value, serverName, platform) {
|
|
251
|
+
if (!isRecord(value))
|
|
252
|
+
return unknownInspection("invalid");
|
|
253
|
+
if (!Object.hasOwn(value, "mcpServers"))
|
|
254
|
+
return unknownInspection("missing");
|
|
255
|
+
if (!isRecord(value.mcpServers))
|
|
256
|
+
return unknownInspection("invalid");
|
|
257
|
+
if (!Object.hasOwn(value.mcpServers, serverName))
|
|
258
|
+
return unknownInspection("missing");
|
|
259
|
+
const selected = value.mcpServers[serverName];
|
|
260
|
+
if (!isRecord(selected))
|
|
261
|
+
return unknownInspection("invalid");
|
|
262
|
+
const packageVersion = exactPackageVersion(selected);
|
|
263
|
+
return {
|
|
264
|
+
inspection_scope: "explicit_config",
|
|
265
|
+
entry: { status: "present" },
|
|
266
|
+
transport: transportFacts(selected),
|
|
267
|
+
...(packageVersion === undefined ? {} : { package_version: packageVersion }),
|
|
268
|
+
api_key: { status: configuredApiKeyStatus(selected) },
|
|
269
|
+
allowed_roots: allowedRootFacts(selected, platform),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
export async function inspectExplicitConfig(options) {
|
|
273
|
+
const platform = options.platform ?? process.platform;
|
|
274
|
+
validateServerName(options.serverName);
|
|
275
|
+
const configPath = normalizedAbsolutePath(options.configPath, platform);
|
|
276
|
+
const fileSystem = options.fileSystem ?? NODE_FILE_SYSTEM;
|
|
277
|
+
let handle;
|
|
278
|
+
try {
|
|
279
|
+
const before = await fileSystem.lstat(configPath);
|
|
280
|
+
requireSafeRegularFile(before);
|
|
281
|
+
requireAllowedSize(before);
|
|
282
|
+
handle = await fileSystem.open(configPath, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
|
|
283
|
+
const opened = await handle.stat({ bigint: true });
|
|
284
|
+
requireSafeRegularFile(opened);
|
|
285
|
+
requireSameFile(before, opened);
|
|
286
|
+
requireAllowedSize(opened);
|
|
287
|
+
const serialized = await readBoundedConfig(handle);
|
|
288
|
+
const after = await handle.stat({ bigint: true });
|
|
289
|
+
requireSafeRegularFile(after);
|
|
290
|
+
requireSameFile(opened, after);
|
|
291
|
+
requireAllowedSize(after);
|
|
292
|
+
let parsed;
|
|
293
|
+
try {
|
|
294
|
+
parsed = JSON.parse(serialized.toString("utf8"));
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return unknownInspection("invalid");
|
|
298
|
+
}
|
|
299
|
+
return inspectParsedConfig(parsed, options.serverName, platform);
|
|
300
|
+
}
|
|
301
|
+
catch (error) {
|
|
302
|
+
throw fileAccessError(error);
|
|
303
|
+
}
|
|
304
|
+
finally {
|
|
305
|
+
await handle?.close().catch(() => undefined);
|
|
306
|
+
}
|
|
307
|
+
}
|
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type AgentConfigEnvironment } from "./agent-config.js";
|
|
2
|
+
import { type ExplicitConfigInspection } from "./config-inspection.js";
|
|
2
3
|
export type VersionCheck = {
|
|
3
4
|
readonly status: "current";
|
|
4
5
|
readonly installed: string;
|
|
@@ -29,8 +30,14 @@ export interface DoctorOptions extends AgentConfigEnvironment {
|
|
|
29
30
|
readonly nodeVersion?: string;
|
|
30
31
|
readonly npmVersion?: string;
|
|
31
32
|
readonly packageVersion?: string;
|
|
33
|
+
readonly explicitConfig?: {
|
|
34
|
+
readonly configPath: string;
|
|
35
|
+
readonly serverName: string;
|
|
36
|
+
};
|
|
32
37
|
}
|
|
33
38
|
export interface DoctorReport {
|
|
39
|
+
readonly inspection_scope: "current_process_env";
|
|
40
|
+
readonly explicit_config?: ExplicitConfigInspection;
|
|
34
41
|
readonly package_version: string;
|
|
35
42
|
readonly version_check: VersionCheck;
|
|
36
43
|
readonly node_version: string;
|
package/dist/cli/doctor.js
CHANGED
|
@@ -3,7 +3,9 @@ import { lstat, open, readdir } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
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
|
+
import { normalizePlatformPath, PathNormalizationError, } from "../path-normalization.js";
|
|
6
7
|
import { agentConfigPath, inspectAgentConfigDetails, } from "./agent-config.js";
|
|
8
|
+
import { inspectExplicitConfig, } from "./config-inspection.js";
|
|
7
9
|
const CUBE_HEALTH_PATH = "/api/omni-reader/direct-upload/v1/health";
|
|
8
10
|
const NPM_LATEST_URL = "https://registry.npmjs.org/@cueai/omni-reader-mcp/latest";
|
|
9
11
|
// Local-file parsing reaches the data plane only through the controlled origin
|
|
@@ -188,7 +190,16 @@ function allowedRootFacts(options) {
|
|
|
188
190
|
const roots = value.split(separator).filter((root) => root.length > 0);
|
|
189
191
|
return {
|
|
190
192
|
count: roots.length,
|
|
191
|
-
safe: roots.length > 0 && roots.every((root) =>
|
|
193
|
+
safe: roots.length > 0 && roots.every((root) => {
|
|
194
|
+
try {
|
|
195
|
+
return paths.isAbsolute(normalizePlatformPath(root, options.platform));
|
|
196
|
+
}
|
|
197
|
+
catch (error) {
|
|
198
|
+
if (error instanceof PathNormalizationError)
|
|
199
|
+
return false;
|
|
200
|
+
throw error;
|
|
201
|
+
}
|
|
202
|
+
}),
|
|
192
203
|
};
|
|
193
204
|
}
|
|
194
205
|
async function clientAdapterFacts(options) {
|
|
@@ -207,7 +218,7 @@ async function clientAdapterFacts(options) {
|
|
|
207
218
|
}
|
|
208
219
|
export async function runDoctor(options) {
|
|
209
220
|
const packageVersion = options.packageVersion ?? "unknown";
|
|
210
|
-
const [clientAdapters, artifacts, onboarding, latestPublishedVersion] = await Promise.all([
|
|
221
|
+
const [clientAdapters, artifacts, onboarding, latestPublishedVersion, explicitConfig,] = await Promise.all([
|
|
211
222
|
clientAdapterFacts(options),
|
|
212
223
|
artifactFacts(options.artifactRoot),
|
|
213
224
|
getOnboardingPolicyWithTimeout(options.fetchImpl),
|
|
@@ -218,6 +229,12 @@ export async function runDoctor(options) {
|
|
|
218
229
|
SEMVER_PATTERN.test(packageVersion)
|
|
219
230
|
? fetchLatestPublishedVersion(options.fetchImpl)
|
|
220
231
|
: Promise.resolve(undefined),
|
|
232
|
+
options.explicitConfig === undefined
|
|
233
|
+
? Promise.resolve(undefined)
|
|
234
|
+
: inspectExplicitConfig({
|
|
235
|
+
...options.explicitConfig,
|
|
236
|
+
platform: options.platform,
|
|
237
|
+
}),
|
|
221
238
|
]);
|
|
222
239
|
let urlControl = "skipped (Cue API Key absent)";
|
|
223
240
|
if (options.env.CUE_API_KEY) {
|
|
@@ -235,6 +252,8 @@ export async function runDoctor(options) {
|
|
|
235
252
|
// status stays "not probed" until the first real local-file parse validates it.
|
|
236
253
|
const directUpload = RELAY_DATA_PLANE_STATUS;
|
|
237
254
|
return {
|
|
255
|
+
inspection_scope: "current_process_env",
|
|
256
|
+
...(explicitConfig === undefined ? {} : { explicit_config: explicitConfig }),
|
|
238
257
|
package_version: packageVersion,
|
|
239
258
|
version_check: SEMVER_PATTERN.test(packageVersion)
|
|
240
259
|
? versionCheckFacts(packageVersion, latestPublishedVersion)
|
|
@@ -278,6 +297,7 @@ function packageLine(versionCheck) {
|
|
|
278
297
|
}
|
|
279
298
|
export function renderDoctor(report) {
|
|
280
299
|
const lines = [
|
|
300
|
+
`inspection_scope=${report.inspection_scope}`,
|
|
281
301
|
`Node: ${report.node_version}`,
|
|
282
302
|
`npm: ${report.npm_version}`,
|
|
283
303
|
packageLine(report.version_check),
|
|
@@ -291,6 +311,21 @@ export function renderDoctor(report) {
|
|
|
291
311
|
]) {
|
|
292
312
|
lines.push(`${label} config: ${report.client_adapters[key].status}`);
|
|
293
313
|
}
|
|
314
|
+
if (report.explicit_config !== undefined) {
|
|
315
|
+
const explicit = report.explicit_config;
|
|
316
|
+
lines.push(`explicit_config.inspection_scope=${explicit.inspection_scope}`);
|
|
317
|
+
lines.push(`Explicit config entry: ${explicit.entry.status}`);
|
|
318
|
+
lines.push(`Explicit config transport: ${explicit.transport.shape}`);
|
|
319
|
+
if (explicit.transport.command !== undefined) {
|
|
320
|
+
lines.push(`Explicit config command: ${explicit.transport.command.category} `
|
|
321
|
+
+ `(${explicit.transport.command.basename})`);
|
|
322
|
+
}
|
|
323
|
+
lines.push(`Explicit config package version: ${explicit.package_version ?? "unknown"}`);
|
|
324
|
+
lines.push(`Explicit config API key: ${explicit.api_key.status}`);
|
|
325
|
+
lines.push(`Explicit config allowed roots: ${"count" in explicit.allowed_roots
|
|
326
|
+
? explicit.allowed_roots.count
|
|
327
|
+
: "unknown"} (${explicit.allowed_roots.safety})`);
|
|
328
|
+
}
|
|
294
329
|
lines.push(`Omni control protocol: ${report.endpoints.url_control}`);
|
|
295
330
|
if (report.endpoints.url_control === "unavailable or incompatible") {
|
|
296
331
|
lines.push(" WARNING: URL parsing will fail until this is reachable.");
|
package/dist/cli/setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { BRIDGE_RELEASE_VERSION } from "../constants.js";
|
|
3
|
+
import { PathNormalizationError, normalizePlatformPath, } from "../path-normalization.js";
|
|
3
4
|
import { getOnboardingPolicyWithTimeout, onboardingGuidance, } from "../onboarding-policy.js";
|
|
4
5
|
import { CliUsageError } from "./arguments.js";
|
|
5
6
|
import { configuredAllowedRoots, detectAgentTargets, parseAgentTarget, prepareAgentConfig, rollbackPreparedAgentConfig, verifyPreparedAgentConfig, writePreparedAgentConfig, } from "./agent-config.js";
|
|
@@ -16,10 +17,20 @@ function parseExtraRoots(input, environment) {
|
|
|
16
17
|
: value.startsWith(`~${paths.sep}`)
|
|
17
18
|
? paths.join(environment.homeDirectory, value.slice(2))
|
|
18
19
|
: value;
|
|
19
|
-
|
|
20
|
+
let normalizedInput;
|
|
21
|
+
try {
|
|
22
|
+
normalizedInput = normalizePlatformPath(expanded, environment.platform);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
if (error instanceof PathNormalizationError) {
|
|
26
|
+
throw new CliUsageError(error.message);
|
|
27
|
+
}
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
if (!paths.isAbsolute(normalizedInput)) {
|
|
20
31
|
throw new CliUsageError("Every additional allowed root must be an absolute path.");
|
|
21
32
|
}
|
|
22
|
-
return paths.normalize(
|
|
33
|
+
return paths.normalize(normalizedInput);
|
|
23
34
|
});
|
|
24
35
|
}
|
|
25
36
|
async function interactiveArguments(options) {
|
package/dist/constants.d.ts
CHANGED
|
@@ -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://omni-upload.cuecue.cn/omni/granted/";
|
|
9
|
-
export declare const BRIDGE_RELEASE_VERSION = "1.
|
|
9
|
+
export declare const BRIDGE_RELEASE_VERSION = "1.6.0";
|
|
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;
|
|
@@ -14,6 +14,7 @@ export declare const STATUS_POLL_AFTER_SECONDS = 5;
|
|
|
14
14
|
export declare const DELIVERY_TTL_SECONDS = 600;
|
|
15
15
|
export declare const CUBE_CAPABILITIES_PATH = "/api/omni-reader/capabilities/v1";
|
|
16
16
|
export declare const REMOTE_CAPABILITIES_CUSTOM_FIELD = "cue.omni-reader";
|
|
17
|
+
export declare const SERVER_NEGOTIATED_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v3";
|
|
17
18
|
export declare const CAPABILITIES_PROTOCOL_VERSION = "omni.reader_capabilities.v1";
|
|
18
19
|
export declare const CAPABILITIES_ENABLED_ENV = "OMNI_CAPABILITIES_ENABLED";
|
|
19
20
|
export declare const CAPABILITIES_TTL_SECONDS_ENV = "OMNI_CAPABILITIES_TTL_SECONDS";
|
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://omni-upload.cuecue.cn/omni/granted/";
|
|
9
|
-
export const BRIDGE_RELEASE_VERSION = "1.
|
|
9
|
+
export const BRIDGE_RELEASE_VERSION = "1.6.0";
|
|
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;
|
|
@@ -14,6 +14,7 @@ export const STATUS_POLL_AFTER_SECONDS = 5;
|
|
|
14
14
|
export const DELIVERY_TTL_SECONDS = 600;
|
|
15
15
|
export const CUBE_CAPABILITIES_PATH = "/api/omni-reader/capabilities/v1";
|
|
16
16
|
export const REMOTE_CAPABILITIES_CUSTOM_FIELD = "cue.omni-reader";
|
|
17
|
+
export const SERVER_NEGOTIATED_BRIDGE_PROTOCOL_VERSION = "omni.local_bridge_tools.v3";
|
|
17
18
|
// ── omni.reader_capabilities.v1 advertisement (D2-D item 4) ────────────────────────────────
|
|
18
19
|
// The direct profile's 14 exact literal fields mirror the pinned cube-mcp accepted set
|
|
19
20
|
// (contracts/cube-mcp/omni-capabilities/v1); the URL profile's 9 fields belong to the
|
|
@@ -33,7 +34,7 @@ export const DIRECT_GROUNDING_PROFILE = {
|
|
|
33
34
|
settlement_journal_protocol: "omni.direct_settlement_journal.v2",
|
|
34
35
|
usage_protocol: "omni_parse_usage.v2",
|
|
35
36
|
billing_protocol: "omni_billing.v2",
|
|
36
|
-
bridge_protocol:
|
|
37
|
+
bridge_protocol: SERVER_NEGOTIATED_BRIDGE_PROTOCOL_VERSION,
|
|
37
38
|
bundle_protocol: "omni.result_bundle.v1",
|
|
38
39
|
grounding_schema: "omni.grounding.v1",
|
|
39
40
|
details: ["grounded", "layout"],
|
|
@@ -44,7 +45,7 @@ export const URL_GROUNDING_PROFILE = {
|
|
|
44
45
|
operation_protocol: "omni.url_operation.v3",
|
|
45
46
|
usage_protocol: "omni_parse_usage.v2",
|
|
46
47
|
billing_protocol: "omni_billing.v2",
|
|
47
|
-
bridge_protocol:
|
|
48
|
+
bridge_protocol: SERVER_NEGOTIATED_BRIDGE_PROTOCOL_VERSION,
|
|
48
49
|
bundle_protocol: "omni.result_bundle.v1",
|
|
49
50
|
grounding_schema: "omni.grounding.v1",
|
|
50
51
|
details: ["grounded", "layout"],
|
package/dist/cursor.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export interface BundleCursorPayload {
|
|
|
16
16
|
export interface CursorCodecOptions {
|
|
17
17
|
readonly now?: () => Date;
|
|
18
18
|
}
|
|
19
|
+
export declare class CursorCodecError extends Error {
|
|
20
|
+
readonly code: string;
|
|
21
|
+
constructor(code: string, message: string);
|
|
22
|
+
}
|
|
19
23
|
export declare class CursorCodec {
|
|
20
24
|
#private;
|
|
21
25
|
constructor(key: Uint8Array, options?: CursorCodecOptions);
|
package/dist/cursor.js
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
-
import { OmniBridgeError } from "./errors.js";
|
|
3
2
|
import { BUNDLE_CURSOR_VERSION, GROUNDING_SCHEMA_VERSION, RESULT_BUNDLE_PROTOCOL_VERSION, } from "./protocol.js";
|
|
4
3
|
const CURSOR_VERSION = 1;
|
|
5
4
|
const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
6
5
|
const RESULT_ID_PATTERN = /^result_[A-Za-z0-9_-]{16,64}$/;
|
|
6
|
+
export class CursorCodecError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "CursorCodecError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
7
14
|
function cursorError(code, message) {
|
|
8
|
-
return new
|
|
9
|
-
code,
|
|
10
|
-
message,
|
|
11
|
-
operationCreated: true,
|
|
12
|
-
fileUploaded: true,
|
|
13
|
-
parserStarted: true,
|
|
14
|
-
billed: true,
|
|
15
|
-
contentReleased: true,
|
|
16
|
-
retryable: false,
|
|
17
|
-
});
|
|
15
|
+
return new CursorCodecError(code, message);
|
|
18
16
|
}
|
|
19
17
|
function decodeBase64Url(value) {
|
|
20
18
|
if (!BASE64URL_PATTERN.test(value)) {
|
|
@@ -55,7 +53,7 @@ export class CursorCodec {
|
|
|
55
53
|
value = JSON.parse(decodeBase64Url(encoded).toString("utf8"));
|
|
56
54
|
}
|
|
57
55
|
catch (error) {
|
|
58
|
-
if (error instanceof
|
|
56
|
+
if (error instanceof CursorCodecError)
|
|
59
57
|
throw error;
|
|
60
58
|
throw cursorError("INVALID_RESULT_CURSOR", "The result cursor is invalid.");
|
|
61
59
|
}
|
|
@@ -107,7 +105,7 @@ export class CursorCodec {
|
|
|
107
105
|
value = JSON.parse(decodeBase64Url(encoded).toString("utf8"));
|
|
108
106
|
}
|
|
109
107
|
catch (error) {
|
|
110
|
-
if (error instanceof
|
|
108
|
+
if (error instanceof CursorCodecError)
|
|
111
109
|
throw error;
|
|
112
110
|
throw cursorError("INVALID_RESULT_CURSOR", "The result cursor is invalid.");
|
|
113
111
|
}
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ function helpText() {
|
|
|
26
26
|
"No arguments start the stdio MCP server.",
|
|
27
27
|
"setup Configure a supported user-scope Agent",
|
|
28
28
|
"doctor Check local configuration and protocol health",
|
|
29
|
+
" [--json] [--config-path <absolute-json-path> --server-name <entry>]",
|
|
29
30
|
"clean Delete Bridge-created local artifacts and expired records",
|
|
30
31
|
"uninstall Restore a trusted URL-only Agent entry without deleting artifacts",
|
|
31
32
|
"",
|
|
@@ -99,7 +100,7 @@ export async function startStdioServer(options = {}) {
|
|
|
99
100
|
fetchImpl: options.fetchImpl,
|
|
100
101
|
operationBaseUrl: options.iiisOperationBaseUrl,
|
|
101
102
|
});
|
|
102
|
-
const extraRoots = splitAllowedRoots(env.OMNI_ALLOWED_ROOTS);
|
|
103
|
+
const extraRoots = splitAllowedRoots(env.OMNI_ALLOWED_ROOTS, platform);
|
|
103
104
|
const remoteClient = new HttpRemoteOmniClient({
|
|
104
105
|
apiKey: env.CUE_API_KEY,
|
|
105
106
|
fetchImpl: options.fetchImpl,
|
|
@@ -200,6 +201,14 @@ export async function runCli(args, options = {}) {
|
|
|
200
201
|
fetchImpl,
|
|
201
202
|
npmVersion: options.npmVersion ?? await installedNpmVersion(env),
|
|
202
203
|
packageVersion,
|
|
204
|
+
...(parsed.configPath === undefined
|
|
205
|
+
? {}
|
|
206
|
+
: {
|
|
207
|
+
explicitConfig: {
|
|
208
|
+
configPath: parsed.configPath,
|
|
209
|
+
serverName: parsed.serverName,
|
|
210
|
+
},
|
|
211
|
+
}),
|
|
203
212
|
});
|
|
204
213
|
write(parsed.json
|
|
205
214
|
? `${JSON.stringify(report)}\n`
|
|
@@ -3,6 +3,7 @@ export type JournalState = "CREATED" | "GRANT_PENDING" | "GRANT_ISSUED" | "UPLOA
|
|
|
3
3
|
export type JournalProgressUnit = "page" | "sheet" | "slide" | "frame" | "segment";
|
|
4
4
|
export type JournalCleanupState = "not_created" | "in_use" | "pending" | "deleted";
|
|
5
5
|
export type JournalDeliveryState = "not_created" | "pending" | "deleted_after_ack";
|
|
6
|
+
export type JournalResultDelivery = "auto" | "artifact";
|
|
6
7
|
export interface JournalProgress {
|
|
7
8
|
readonly unit: JournalProgressUnit;
|
|
8
9
|
readonly completed: number;
|
|
@@ -22,6 +23,7 @@ export interface JournalRecord {
|
|
|
22
23
|
readonly detail: JournalDetail;
|
|
23
24
|
readonly groundingSchemaVersion: JournalGroundingSchemaVersion;
|
|
24
25
|
readonly bundleProtocolVersion: JournalBundleProtocolVersion;
|
|
26
|
+
readonly resultDeliveryEffective: JournalResultDelivery;
|
|
25
27
|
readonly operationId: string | null;
|
|
26
28
|
readonly operationToken: string | null;
|
|
27
29
|
readonly uploadUrl: string | null;
|
|
@@ -85,9 +87,10 @@ export declare class OperationJournal {
|
|
|
85
87
|
constructor(options?: OperationJournalOptions);
|
|
86
88
|
requestIdentityHmac(canonicalIdentityJson: string): Promise<string>;
|
|
87
89
|
sourceLocatorHmac(sourceLocator: string): Promise<string>;
|
|
88
|
-
beginIntent(clientRequestId: string, canonicalIdentityJson: string, sourceKind?: JournalSourceKind, sourceLocator?: string | null, representation?: RepresentationIntent): Promise<JournalRecord>;
|
|
90
|
+
beginIntent(clientRequestId: string, canonicalIdentityJson: string, sourceKind?: JournalSourceKind, sourceLocator?: string | null, representation?: RepresentationIntent, resultDelivery?: JournalResultDelivery): Promise<JournalRecord>;
|
|
89
91
|
migrateLegacyRecord(clientRequestId: string, canonicalIdentityJson: string, sourceLocator?: string | null): Promise<JournalRecord | null>;
|
|
90
92
|
transition(clientRequestId: string, expectedState: JournalState, nextState: JournalState, patch: JournalPatch): Promise<JournalRecord>;
|
|
93
|
+
strengthenResultDelivery(clientRequestId: string, requested: JournalResultDelivery): Promise<JournalRecord>;
|
|
91
94
|
loadByRequestId(clientRequestId: string): Promise<JournalRecord | null>;
|
|
92
95
|
loadByOperationId(operationId: string): Promise<JournalRecord | null>;
|
|
93
96
|
loadLatestByRequestIdentityHmac(requestIdentityHmac: string): Promise<JournalRecord | null>;
|