ONNX
English
vons
research
candidate-selection
File size: 11,168 Bytes
506a341
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
import { parseArgs } from "node:util";
import { readFileSync, realpathSync } from "node:fs";
import { resolve, join, sep } from "node:path";
import { pathToFileURL } from "node:url";

import { loadBundleManifestStrict, runStdioMcpServer, isForbiddenBundlePath } from "./mcp.ts";

import { createOnnxWebBackend } from "./onnx.ts";
import type { BundleManifest } from "./onnx.ts";

interface CliArgs {
  bundle: string | undefined;
  manifestSha256: string | undefined;
  provider: "wasm" | "webgpu" | undefined;
  help: boolean;
}

function printUsage(stream: NodeJS.WritableStream = process.stderr): void {
  stream.write(
    [
      "vons-local-mcp stdio server (uses local ONNX Web backend via --bundle; no KASI adapter; no host tool execution)",
      "",
      "USAGE:",
      "  node --experimental-strip-types sdk/typescript/src/mcp-cli.ts --bundle <dir> [options]",
      "",
      "REQUIRED:",
      "  --bundle <dir>           Path to a user-supplied, hash-verified local ONNX bundle directory.",
      "                           Must contain bundle-manifest-v1.json declaring vons.bundle.manifest/v1.",
      "                           No URL, no auto-discovery, no implicit defaults. Shell-resolved relative paths (including ../..) are supported for the local --bundle directory after the scheme-rejection check.",
      "",
      "OPTIONS:",
      "  --manifest-sha256 <hex>  Optional 64-char lowercase SHA-256 to authenticate the bundle manifest.",
      "                           If declared and mismatched, the process aborts; no fallback or soft-error.",
      "  --provider <wasm|webgpu> Backend provider choice for the ONNX runtime (default: wasm).",
      "                           In the stdio CLI, --provider wasm is the only supported value.",
      "                           --provider webgpu is explicitly rejected with exit code 4 because a",
      "                           stdio process cannot obtain a browser WebGPU adapter. Use a browser",
      "                           or extension host with requestAdapter() explicitly to use WebGPU.",
      "  --help                   Print this help text and exit 0.",
      "",
      "CONSTRAINTS (explicit non-goals):",
      "  - No HTTP listener, no network, no remote connections.",
      "  - Writes MCP JSON-RPC messages ONLY to stdout.",
      "  - Diagnostic messages (errors, warning banners, runtime logs, and usage printed for invalid/missing flags) go ONLY to stderr.",
      "  - The --help usage text is intentionally printed to stdout (exit 0) so it may be piped or redirected by shell callers.",
      "  - Never executes downstream tools, never authorizes actions, never stores/persists/upload data.",
      "  - Never performs silent fallback decisions, never substitutes a fake choice on errors.",
      "  - CLI --bundle accepts local directory paths (including shell-resolved ../..); URL schemes are rejected; the chosen root is realpath-resolved before manifest and declared files are validated.",
      "  - Declared files inside bundle-manifest-v1.json must not use URLs, absolute paths, .. traversal, drive letters, query strings, or fragments; any symlink escape or realpath escape is rejected.",
      "  - No request or response contents are written to any log except the MCP stdout channel.",
      "",
    ].join("\n"),
  );
}

function parseCliArgs(argv: string[]): CliArgs {
  try {
    const parsed = parseArgs({
      args: argv,
      options: {
        bundle: { type: "string" },
        "manifest-sha256": { type: "string" },
        provider: { type: "string" },
        help: { type: "boolean", short: "h" },
      },
      strict: true,
      allowPositionals: false,
    });
    const providerRaw = parsed.values["provider"];
    let provider: CliArgs["provider"];
    if (providerRaw === undefined) {
      provider = undefined;
    } else if (providerRaw === "wasm" || providerRaw === "webgpu") {
      provider = providerRaw;
    } else {
      throw new Error(
        `--provider must be 'wasm' or 'webgpu', got '${String(providerRaw)}'. No auto-detection.`,
      );
    }
    return {
      bundle: parsed.values["bundle"],
      manifestSha256: parsed.values["manifest-sha256"],
      provider,
      help: Boolean(parsed.values.help),
    };
  } catch (cause) {
    printUsage();
    process.stderr.write(`\nargument error: ${(cause as Error).message}\n`);
    process.exit(2);
  }
}

function diag(level: "debug" | "info" | "warn" | "error", message: string): void {
  process.stderr.write(`[vons-mcp:${level}] ${message}\n`);
}

function makeBundleLocalFsFetch(
  bundleRootRealPath: string,
  manifest: BundleManifest,
): typeof fetch {
  const bundlePrefix = "vons-bundle-fs://local/";
  const validRelPaths = new Set(manifest.files.map((f) => f.path));
  const fetchImpl: typeof fetch = async (input, init) => {
    const urlStr = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
    if (typeof urlStr !== "string") {
      throw new TypeError("invalid fetch input in local-fs bundle adapter");
    }
    if (init && "signal" in init && init.signal && init.signal.aborted) {
      throw new DOMException("Aborted", "AbortError");
    }
    if (!urlStr.startsWith(bundlePrefix)) {
      throw new Error(
        `stdios bundle fetch adapter only accepts ${bundlePrefix} URLs; refusing to fetch: ${urlStr.slice(0, 64)}`,
      );
    }
    const relEncoded = urlStr.slice(bundlePrefix.length);
    const rel = decodeURIComponent(relEncoded);
    if (isForbiddenBundlePath(rel) || !validRelPaths.has(rel)) {
      throw new Error(`bundle-local fetch refused: path not declared in manifest: ${rel}`);
    }
    const abs = join(bundleRootRealPath, ...rel.split("/"));
    let absRealPath: string | null = null;
    try {
      absRealPath = realpathSync(abs);
    } catch {
      throw new Error(`bundle-local fetch refused: declared file not found on disk (realpath failed): ${rel}`);
    }
    if (
      absRealPath !== bundleRootRealPath &&
      !absRealPath.startsWith(bundleRootRealPath + sep)
    ) {
      throw new Error(`bundle-local fetch refused: file escapes bundle root via symlink: ${rel}`);
    }
    const bytes = readFileSync(absRealPath);
    const body = new Uint8Array(bytes);
    return new Response(body.buffer as ArrayBuffer, {
      status: 200,
      headers: {
        "content-type": "application/octet-stream",
        "content-length": String(body.byteLength),
      },
    }) as Response;
  };
  return fetchImpl;
}

function bundleLocalBaseHref(bundleRootRealPath: string): string {
  return "vons-bundle-fs://local/";
}

async function main(argv: string[]): Promise<number> {
  const args = parseCliArgs(argv);
  if (args.help) {
    printUsage(process.stdout);
    return 0;
  }
  if (!args.bundle || args.bundle.length === 0) {
    printUsage();
    diag("error", "--bundle <dir> is required. No implicit or auto-discovered bundle path is used.");
    return 2;
  }
  if (args.provider !== undefined && args.provider !== "wasm" && args.provider !== "webgpu") {
    diag("error", `--provider must be 'wasm' or 'webgpu', got '${String(args.provider)}'. No auto-detection.`);
    return 2;
  }
  const provider = args.provider ?? "wasm";
  const URI_SCHEME_RE = /^(https?|file|ftp|data):(\/\/)?/i;
  if (URI_SCHEME_RE.test(args.bundle.trimStart())) {
    const trimmed = args.bundle.trim();
    const m = trimmed.match(URI_SCHEME_RE);
    const scheme = m ? m[1].toLowerCase() : "unknown";
    diag(
      "error",
      `--bundle rejected: URI scheme '${scheme}://' is not supported. ` +
        `The --bundle argument must be a local filesystem directory path (shell-resolved relative paths including ../.. are supported). ` +
        `Passed value: '${trimmed.slice(0, 120)}'${trimmed.length > 120 ? " (truncated)" : ""}. ` +
        `No silent fallback; no request contents were processed.`,
    );
    return 3;
  }
  const bundleDir = resolve(args.bundle);
  let loaded: Awaited<ReturnType<typeof loadBundleManifestStrict>>;
  try {
    loaded = loadBundleManifestStrict(bundleDir, args.manifestSha256);
  } catch (cause) {
    diag(
      "error",
      `bundle load failed: ${cause instanceof Error ? cause.message : String(cause)}. ` +
        `No silent fallback; the user must repair the bundle or path. No request contents were processed.`,
    );
    return 3;
  }
  const manifest: BundleManifest = loaded.manifest;
  if (provider === "webgpu") {
    diag(
      "error",
      "--provider webgpu was selected, but the stdio CLI cannot obtain a browser WebGPU adapter. " +
        "WebGPU is only supported inside browser/extension host contexts with an explicit requestAdapter() call. " +
        "Either re-run with --provider wasm, or invoke the SDK's browser host integration instead.",
    );
    return 4;
  }
  const md = manifest.metadata;
  diag(
    "info",
    `bundle loaded: backend=${md.backend}, option_count=${md.option_count ?? "null"}, ` +
      `sequence_length=${md.sequence_length ?? "null"}, model_id=${md.model_id ?? "(none declared)"}. ` +
      `manifest.sha256=${loaded.manifestSha256.slice(0, 12)}... (full digest omitted from logs). ` +
      `Starting stdio MCP server; MCP JSON-RPC only on stdout, diagnostics only on stderr.`,
  );
  diag(
    "info",
    `provider=${provider}. Wire ONNX Web backend with bundle-local fs fetch adapter; ` +
      `actual ONNX runtime loading only runs for real user bundles (not in synthetic protocol tests).`,
  );
  const bundleFetch = makeBundleLocalFsFetch(loaded.rootRealPath, manifest);
  let backend: Awaited<ReturnType<typeof createOnnxWebBackend>>;
  try {
    backend = await createOnnxWebBackend({
      manifest,
      baseUrl: bundleLocalBaseHref(loaded.rootRealPath),
      provider,
      fetch: bundleFetch,
    });
  } catch (cause) {
    diag(
      "error",
      `failed to create ONNX Web backend for provider=${provider}: ${cause instanceof Error ? cause.message : String(cause)}. ` +
        `No fallback backend or synthetic choice substituted; MCP server startup aborted.`,
    );
    return 5;
  }
  try {
    await runStdioMcpServer({
      backend,
      manifest,
      manifestSha256: loaded.manifestSha256,
      bundleRoot: loaded.rootRealPath,
      logger: (level, message) => diag(level, message),
    });
  } catch (cause) {
    diag("error", `stdio MCP server terminated with error: ${cause instanceof Error ? cause.message : String(cause)}`);
    return 1;
  } finally {
    try {
      await backend.dispose();
    } catch {
      void 0;
    }
  }
  return 0;
}

const _argv1 = typeof process !== "undefined" && process.argv && process.argv[1] ? process.argv[1] : null;
const _isDirectCliInvoke =
  _argv1 !== null &&
  typeof import.meta !== "undefined" &&
  import.meta.url !== null &&
  typeof import.meta.url === "string" &&
  pathToFileURL(resolve(_argv1)).href === import.meta.url;

if (_isDirectCliInvoke) {
  void main(process.argv.slice(2)).then((code) => {
    if (code !== 0) {
      process.exitCode = code;
    }
  });
}

export {
  parseCliArgs,
  printUsage,
  diag,
  main,
  isForbiddenBundlePath,
  loadBundleManifestStrict,
};