@cueai/omni-reader-mcp 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -85
- package/dist/artifact-store.d.ts +58 -0
- package/dist/artifact-store.js +449 -5
- package/dist/capabilities.d.ts +92 -0
- package/dist/capabilities.js +123 -0
- package/dist/constants.d.ts +34 -1
- package/dist/constants.js +39 -1
- package/dist/cube-client.d.ts +3 -0
- package/dist/cube-client.js +141 -9
- package/dist/cursor.d.ts +12 -0
- package/dist/cursor.js +89 -9
- package/dist/onboarding-policy.d.ts +5 -2
- package/dist/onboarding-policy.js +8 -3
- package/dist/operation-journal.d.ts +19 -5
- package/dist/operation-journal.js +435 -73
- package/dist/operation-manager.d.ts +7 -2
- package/dist/operation-manager.js +215 -16
- package/dist/protocol.d.ts +16 -3
- package/dist/protocol.js +25 -1
- package/dist/remote-client.d.ts +4 -2
- package/dist/remote-client.js +156 -13
- package/dist/result-bundle.d.ts +21 -0
- package/dist/result-bundle.js +320 -0
- package/dist/result-contract.d.ts +398 -4
- package/dist/result-contract.js +169 -11
- package/dist/server.d.ts +17 -0
- package/dist/server.js +45 -1
- package/dist/tools.d.ts +7 -3
- package/dist/tools.js +77 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1,11 +1,52 @@
|
|
|
1
1
|
import { InMemoryTaskStore } from "@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js";
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { BRIDGE_RELEASE_VERSION } from "./constants.js";
|
|
3
|
+
import { BRIDGE_RELEASE_VERSION, CAPABILITIES_DEFAULT_TTL_SECONDS, CAPABILITIES_ENABLED_ENV, CAPABILITIES_PROTOCOL_VERSION, CAPABILITIES_TTL_SECONDS_ENV, DIRECT_GROUNDING_PROFILE, REMOTE_CAPABILITIES_CUSTOM_FIELD, URL_GROUNDING_PROFILE, } from "./constants.js";
|
|
4
4
|
import { MACHINE_INSTRUCTIONS } from "./protocol.js";
|
|
5
5
|
import { registerOmniTools } from "./tools.js";
|
|
6
6
|
const SERVER_NAME = "omni-reader-mcp";
|
|
7
|
+
/** RFC3339 UTC timestamp at seconds precision (matches the §D2-D example form). */
|
|
8
|
+
function rfc3339Seconds(date) {
|
|
9
|
+
return date.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
10
|
+
}
|
|
11
|
+
export function buildReaderCapabilitiesAdvertisement(now, ttlSeconds) {
|
|
12
|
+
return {
|
|
13
|
+
protocol_version: CAPABILITIES_PROTOCOL_VERSION,
|
|
14
|
+
expires_at: rfc3339Seconds(new Date(now.getTime() + ttlSeconds * 1000)),
|
|
15
|
+
direct_profiles: [DIRECT_GROUNDING_PROFILE],
|
|
16
|
+
url_profiles: [URL_GROUNDING_PROFILE],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** OMNI_CAPABILITIES_ENABLED gate, default off; a malformed value fails fast. */
|
|
20
|
+
export function capabilitiesEnabled(env = process.env) {
|
|
21
|
+
const raw = (env[CAPABILITIES_ENABLED_ENV] ?? "").trim().toLowerCase();
|
|
22
|
+
if (raw === "true")
|
|
23
|
+
return true;
|
|
24
|
+
if (raw === "" || raw === "false")
|
|
25
|
+
return false;
|
|
26
|
+
throw new Error(`${CAPABILITIES_ENABLED_ENV} must be true or false`);
|
|
27
|
+
}
|
|
28
|
+
/** OMNI_CAPABILITIES_TTL_SECONDS, default 300; malformed/non-positive values fall back. */
|
|
29
|
+
export function capabilitiesTtlSeconds(env = process.env) {
|
|
30
|
+
const raw = env[CAPABILITIES_TTL_SECONDS_ENV];
|
|
31
|
+
if (raw === undefined || raw.trim() === "")
|
|
32
|
+
return CAPABILITIES_DEFAULT_TTL_SECONDS;
|
|
33
|
+
const parsed = Number(raw);
|
|
34
|
+
if (!Number.isInteger(parsed) || parsed <= 0)
|
|
35
|
+
return CAPABILITIES_DEFAULT_TTL_SECONDS;
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The advertisement to include in an initialize result, or null when the flag is off
|
|
40
|
+
* (the custom field must be absent — the server never emits a partial value).
|
|
41
|
+
*/
|
|
42
|
+
export function readerCapabilitiesAdvertisement(now = new Date()) {
|
|
43
|
+
if (!capabilitiesEnabled())
|
|
44
|
+
return null;
|
|
45
|
+
return buildReaderCapabilitiesAdvertisement(now, capabilitiesTtlSeconds());
|
|
46
|
+
}
|
|
7
47
|
export function createOmniMcpServer(dependencies) {
|
|
8
48
|
const taskStore = new InMemoryTaskStore();
|
|
49
|
+
const advertised = readerCapabilitiesAdvertisement();
|
|
9
50
|
const server = new McpServer({ name: SERVER_NAME, version: BRIDGE_RELEASE_VERSION }, {
|
|
10
51
|
instructions: MACHINE_INSTRUCTIONS,
|
|
11
52
|
taskStore,
|
|
@@ -15,6 +56,9 @@ export function createOmniMcpServer(dependencies) {
|
|
|
15
56
|
cancel: {},
|
|
16
57
|
requests: { tools: { call: {} } },
|
|
17
58
|
},
|
|
59
|
+
...(advertised === null
|
|
60
|
+
? {}
|
|
61
|
+
: { experimental: { [REMOTE_CAPABILITIES_CUSTOM_FIELD]: advertised } }),
|
|
18
62
|
},
|
|
19
63
|
});
|
|
20
64
|
registerOmniTools(server, dependencies, taskStore);
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import type { TaskStore } from "@modelcontextprotocol/sdk/experimental/tasks/interfaces.js";
|
|
3
|
-
import type { ArtifactReadResult, LocalResult } from "./artifact-store.js";
|
|
3
|
+
import type { ArtifactReadResult, BundlePartReadChunk, LocalResult } from "./artifact-store.js";
|
|
4
|
+
import type { ReaderCapabilitiesV1 } from "./capabilities.js";
|
|
4
5
|
import { type CubeGrantClient } from "./cube-client.js";
|
|
5
6
|
import type { IiisClient, ResultRetentionSink } from "./iiis-client.js";
|
|
6
7
|
import { type OpenAllowedFileOptions, type OpenedAllowedFile } from "./path-security.js";
|
|
@@ -12,6 +13,7 @@ interface ToolRetention extends ResultRetentionSink {
|
|
|
12
13
|
interface ToolArtifactStore {
|
|
13
14
|
createRetention(): ToolRetention;
|
|
14
15
|
read(resultId: string, cursor?: string, maxBytes?: number): Promise<ArtifactReadResult>;
|
|
16
|
+
readBundlePart?(resultId: string, cursor: string, maxBytes?: number): Promise<BundlePartReadChunk>;
|
|
15
17
|
discard(resultId: string): Promise<boolean>;
|
|
16
18
|
}
|
|
17
19
|
export interface ParseOperationController {
|
|
@@ -28,11 +30,13 @@ export interface ParseOperationController {
|
|
|
28
30
|
export interface OmniToolDependencies {
|
|
29
31
|
readonly workspace: string;
|
|
30
32
|
readonly extraRoots?: readonly string[];
|
|
31
|
-
readonly cubeClient: Pick<CubeGrantClient, "createGrant">;
|
|
33
|
+
readonly cubeClient: Pick<CubeGrantClient, "createGrant" | "getCapabilities">;
|
|
32
34
|
readonly iiisClient: Pick<IiisClient, "uploadAndWait" | "ack">;
|
|
33
35
|
readonly artifactStore: ToolArtifactStore;
|
|
34
36
|
readonly createClientRequestId?: () => string;
|
|
35
|
-
readonly remoteClient?: RemoteOmniClient
|
|
37
|
+
readonly remoteClient?: RemoteOmniClient & {
|
|
38
|
+
initializeCapabilities(signal: AbortSignal): Promise<ReaderCapabilitiesV1>;
|
|
39
|
+
};
|
|
36
40
|
readonly operationManager?: ParseOperationController;
|
|
37
41
|
readonly openFile?: (localPath: string, options: OpenAllowedFileOptions) => Promise<OpenedAllowedFile>;
|
|
38
42
|
readonly statusOperation?: (operationId: string, waitMs: number | undefined, signal: AbortSignal) => Promise<ParseResult>;
|
package/dist/tools.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { CallToolRequestSchema, CancelTaskRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
|
+
import { selectDirectProfile, selectUrlProfile, } from "./capabilities.js";
|
|
3
4
|
import { createClientRequestId as newClientRequestId, } from "./cube-client.js";
|
|
4
5
|
import { OmniBridgeError } from "./errors.js";
|
|
5
6
|
import { openAllowedFile, } from "./path-security.js";
|
|
6
7
|
import { NOOP_PROGRESS } from "./progress.js";
|
|
7
|
-
import { MACHINE_INSTRUCTIONS, cancelParseSchema, discardResultSchema, getParseStatusSchema, parseSchema, readResultSchema, } from "./protocol.js";
|
|
8
|
+
import { MACHINE_INSTRUCTIONS, cancelParseSchema, discardResultSchema, getParseStatusSchema, normalizeRepresentation, parseSchema, readResultSchema, } from "./protocol.js";
|
|
8
9
|
import { discardResultOutputSchema, discardResultToolOutputSchema, parseResultSchema, parseToolOutputSchema, readResultOutputSchema, readResultToolOutputSchema, structuredResult, } from "./result-contract.js";
|
|
9
10
|
import { classifySource } from "./source.js";
|
|
10
11
|
import { TaskRuntime } from "./task-runtime.js";
|
|
@@ -137,9 +138,48 @@ async function parseLocal(args, clientRequestId, signal, progress, dependencies)
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
}
|
|
141
|
+
// D2-D items 4-5: capability preflight for any explicit non-text request.
|
|
142
|
+
// The capability source must be available BEFORE any non-text grant/tool
|
|
143
|
+
// request is constructed: Cube requests use the authenticated
|
|
144
|
+
// `GET /api/omni-reader/capabilities/v1` preflight, remote MCP requests use
|
|
145
|
+
// the initialize result's exact `capabilities.experimental["cue.omni-reader"]`
|
|
146
|
+
// custom field. Without one exact compatible profile for the requested
|
|
147
|
+
// path/detail the request fails closed with UNSUPPORTED_DETAIL — no unknown
|
|
148
|
+
// fields are sent and there is never a silent text fallback.
|
|
149
|
+
async function preflightNonTextRepresentation(detail, sourceKind, signal, dependencies) {
|
|
150
|
+
if (sourceKind === "local") {
|
|
151
|
+
const capabilities = await dependencies.cubeClient.getCapabilities(signal);
|
|
152
|
+
selectDirectProfile(capabilities, detail);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
if (dependencies.remoteClient === undefined) {
|
|
156
|
+
throw bridgeError("REMOTE_PARSE_UNAVAILABLE", "Remote URL parsing is not available in this Bridge build.", { retryable: true });
|
|
157
|
+
}
|
|
158
|
+
const capabilities = await dependencies.remoteClient.initializeCapabilities(signal);
|
|
159
|
+
selectUrlProfile(capabilities, detail);
|
|
160
|
+
}
|
|
161
|
+
if (dependencies.operationManager === undefined) {
|
|
162
|
+
throw new OmniBridgeError({
|
|
163
|
+
code: "UNSUPPORTED_DETAIL",
|
|
164
|
+
message: "This Bridge build cannot deliver the requested output detail.",
|
|
165
|
+
failureScope: "bridge",
|
|
166
|
+
userAction: "Use plain Markdown output for this source.",
|
|
167
|
+
operationCreated: false,
|
|
168
|
+
fileUploaded: false,
|
|
169
|
+
parserStarted: false,
|
|
170
|
+
billed: false,
|
|
171
|
+
contentReleased: false,
|
|
172
|
+
retryable: false,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
140
176
|
async function parseValue(args, signal, progress, dependencies) {
|
|
141
177
|
try {
|
|
142
178
|
const source = classifySource(args.source);
|
|
179
|
+
const representation = normalizeRepresentation(args.detail);
|
|
180
|
+
if (representation.detail !== "text") {
|
|
181
|
+
await preflightNonTextRepresentation(representation.detail, source.kind, signal, dependencies);
|
|
182
|
+
}
|
|
143
183
|
const clientRequestId = (dependencies.createClientRequestId ?? newClientRequestId)();
|
|
144
184
|
if (dependencies.operationManager !== undefined) {
|
|
145
185
|
const sourceHash = `sha256:${createHash("sha256")
|
|
@@ -171,6 +211,12 @@ async function parseValue(args, signal, progress, dependencies) {
|
|
|
171
211
|
sourceFacts: {
|
|
172
212
|
sourceHash,
|
|
173
213
|
...(sourceFingerprint === undefined ? {} : { sourceFingerprint }),
|
|
214
|
+
// D2-D item 3: the exact normalized representation fields enter
|
|
215
|
+
// SubmitOperationInput.sourceFacts BEFORE operationRequestHash so
|
|
216
|
+
// grounded/layout serialize to distinct identities. omitted/text
|
|
217
|
+
// must serialize byte-for-byte as legacy source facts so legacy
|
|
218
|
+
// journal identities stay reconstructable on upgrade.
|
|
219
|
+
...(representation.detail === "text" ? {} : representation),
|
|
174
220
|
},
|
|
175
221
|
clientRequestId,
|
|
176
222
|
signal,
|
|
@@ -263,6 +309,36 @@ async function callReadResult(resultId, cursor, maxBytes, dependencies) {
|
|
|
263
309
|
});
|
|
264
310
|
}
|
|
265
311
|
catch (error) {
|
|
312
|
+
// D2-D Task 14: a retained logical bundle is only readable through a
|
|
313
|
+
// part-selecting v2 cursor, which the plain artifact read refuses with
|
|
314
|
+
// INVALID_RESULT_CURSOR. When the store exposes a named part reader, route
|
|
315
|
+
// the cursor there so clients can reassemble content and grounding
|
|
316
|
+
// independently from offset 0.
|
|
317
|
+
if (cursor !== undefined &&
|
|
318
|
+
error instanceof OmniBridgeError &&
|
|
319
|
+
error.code === "INVALID_RESULT_CURSOR" &&
|
|
320
|
+
dependencies.artifactStore.readBundlePart !== undefined) {
|
|
321
|
+
try {
|
|
322
|
+
const part = await dependencies.artifactStore.readBundlePart(resultId, cursor, maxBytes);
|
|
323
|
+
return structuredResult(readResultOutputSchema, {
|
|
324
|
+
status: "completed",
|
|
325
|
+
result: {
|
|
326
|
+
result_id: part.resultId,
|
|
327
|
+
part: part.part,
|
|
328
|
+
media_type: part.mediaType,
|
|
329
|
+
result_bytes: part.resultBytes,
|
|
330
|
+
offset: part.offset,
|
|
331
|
+
decoded_bytes: part.decodedBytes,
|
|
332
|
+
text: part.text,
|
|
333
|
+
expires_at: part.expiresAt,
|
|
334
|
+
...(part.nextCursor === undefined ? {} : { next_cursor: part.nextCursor }),
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
catch (partError) {
|
|
339
|
+
return structuredResult(readResultOutputSchema, failed(partError));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
266
342
|
return structuredResult(readResultOutputSchema, failed(error));
|
|
267
343
|
}
|
|
268
344
|
}
|