@aiwg/cli 2026.10.7 → 2026.10.8

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.
@@ -7,7 +7,7 @@ import { manageGrokBuildMcp } from './grok-build-config.mjs';
7
7
  import { replaceServer } from './toml-editor.mjs';
8
8
  import { resolveOmpPaths } from '../providers/omp-paths.mjs';
9
9
  import { applyJsonToolFilterPlan, claudeSettingsPath, prepareClaudePermissions, planToolFilters } from './tool-filters.mjs';
10
- import { assertCredentialPolicy, resolveCredentialPolicy, validateEnvReferenceName } from './credentials.mjs';
10
+ import { assertCredentialPolicy, resolveCredentialPolicy, validateEnvReferenceName, credentialReferenceInput, validateCredentialReferences, assertEnvironmentCredentialReferences } from './credentials.mjs';
11
11
  /**
12
12
  * MCP Server Registry
13
13
  *
@@ -23,6 +23,7 @@ import { qualifyWorkerInvocationProfile, requiredWorkerControls } from '../model
23
23
  import { dirname, isAbsolute, resolve } from 'path';
24
24
  import { homedir } from 'os';
25
25
  import { resolveConfigDir } from '../config/user-config.js';
26
+ import { resolveSensitiveInput } from '../auth/sensitive-input.js';
26
27
  // ============================================
27
28
  // Registry
28
29
  // ============================================
@@ -32,22 +33,24 @@ const DEFAULT_REGISTRY = {
32
33
  kind: 'McpServerRegistry',
33
34
  servers: {},
34
35
  };
35
- const ENV_REFERENCE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
36
- function validateCredentialReferences(def) {
37
- for (const [header, envName] of Object.entries(def.headerEnv ?? {})) {
38
- if (!header.trim())
39
- throw new Error('MCP header-env header name must not be empty');
40
- if (!ENV_REFERENCE_NAME.test(envName)) {
41
- throw new Error(`Invalid MCP header environment variable reference "${envName}"`);
42
- }
43
- }
44
- for (const [key, envName] of Object.entries(def.envFrom ?? {})) {
45
- if (!ENV_REFERENCE_NAME.test(key))
46
- throw new Error(`Invalid MCP env-from variable name "${key}"`);
47
- if (!ENV_REFERENCE_NAME.test(envName)) {
48
- throw new Error(`Invalid MCP env-from environment variable reference "${envName}"`);
36
+ /** Resolve references only for AIWG's in-memory transport, never for a config writer. */
37
+ export async function resolveMcpCredentialMaps(server, dependencies = {}) {
38
+ const result = { env: server.env ? { ...server.env } : undefined, headers: server.headers ? { ...server.headers } : undefined };
39
+ for (const [field, target, flag] of [['envFrom', 'env', '--env-from'], ['headerEnv', 'headers', '--header-env']]) {
40
+ for (const [key, reference] of Object.entries(server[field] ?? {})) {
41
+ const input = credentialReferenceInput(reference, flag);
42
+ // Explicit locators prevent terminal prompts and implicit stdin consumption.
43
+ try {
44
+ const value = await resolveSensitiveInput({ ...input, prefix: 'value' }, dependencies);
45
+ result[target] = { ...result[target], [key]: value };
46
+ }
47
+ catch (error) {
48
+ const message = error instanceof Error ? error.message : 'MCP credential input failed';
49
+ throw new Error(message.replace(/--value-(file|store|env)/g, (_, source) => `${flag} KEY=${source === 'env' ? 'ENV_NAME' : source === 'file' ? 'file:ABSOLUTE_PATH' : 'store:NAME'}`));
50
+ }
49
51
  }
50
52
  }
53
+ return result;
51
54
  }
52
55
  export function workerServerDigest(server) {
53
56
  const { workerQualification, description, injectedProviders, addedAt, updatedAt, ...execution } = server;
@@ -300,6 +303,7 @@ function tomlInlineTable(values) {
300
303
  return `{ ${Object.entries(values).map(([name, value]) => `${tomlKey(name)} = ${tomlString(value)}`).join(', ')} }`;
301
304
  }
302
305
  export function buildServerToml(server) {
306
+ assertEnvironmentCredentialReferences(server, 'codex');
303
307
  const lines = [];
304
308
  lines.push(`[mcp_servers.${tomlKey(server.name)}]`);
305
309
  if (server.type === 'stdio') {
@@ -8,7 +8,7 @@ import { manageGrokBuildMcp } from './grok-build-config.mjs';
8
8
  import { replaceServer } from './toml-editor.mjs';
9
9
  import { resolveOmpPaths } from '../providers/omp-paths.mjs';
10
10
  import { applyJsonToolFilterPlan, claudeSettingsPath, prepareClaudePermissions, planToolFilters } from './tool-filters.mjs';
11
- import { assertCredentialPolicy, resolveCredentialPolicy, validateEnvReferenceName } from './credentials.mjs';
11
+ import { assertCredentialPolicy, resolveCredentialPolicy, validateEnvReferenceName, validateCredentialReferences, assertEnvironmentCredentialReferences } from './credentials.mjs';
12
12
  /**
13
13
  * MCP Server Registry (Runtime ESM)
14
14
  *
@@ -72,23 +72,6 @@ const DEFAULT_REGISTRY = {
72
72
  servers: {},
73
73
  };
74
74
 
75
- const ENV_REFERENCE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
76
-
77
- function validateCredentialReferences(def) {
78
- for (const [header, envName] of Object.entries(def.headerEnv || {})) {
79
- if (!header.trim()) throw new Error('MCP header-env header name must not be empty');
80
- if (!ENV_REFERENCE_NAME.test(envName)) {
81
- throw new Error(`Invalid MCP header environment variable reference "${envName}"`);
82
- }
83
- }
84
- for (const [key, envName] of Object.entries(def.envFrom || {})) {
85
- if (!ENV_REFERENCE_NAME.test(key)) throw new Error(`Invalid MCP env-from variable name "${key}"`);
86
- if (!ENV_REFERENCE_NAME.test(envName)) {
87
- throw new Error(`Invalid MCP env-from environment variable reference "${envName}"`);
88
- }
89
- }
90
- }
91
-
92
75
  // The typed worker dispatch boundary owns qualification. Legacy runtime edits
93
76
  // invalidate its binding without importing the TypeScript policy owner.
94
77
  export function workerServerDigest(server) {
@@ -280,6 +263,7 @@ function tomlInlineTable(values) {
280
263
  }
281
264
 
282
265
  export function buildServerToml(server) {
266
+ assertEnvironmentCredentialReferences(server, 'codex');
283
267
  const lines = [];
284
268
  lines.push(`[mcp_servers.${tomlKey(server.name)}]`);
285
269
 
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Governed Agentic Sandbox fleet and activity MCP surface.
3
3
  *
4
- * Credentials are file-backed server configuration, never tool arguments.
4
+ * Credentials are private-file or native-store configuration, never tool arguments.
5
5
  * Every tool in this module uses the management bearer domain; executor-plane
6
6
  * credentials are deliberately out of scope and cannot be substituted.
7
7
  *
8
8
  * @implements #2015
9
9
  */
10
10
 
11
- import { readFile, stat } from 'node:fs/promises';
11
+ import { access } from 'node:fs/promises';
12
12
  import { z } from 'zod';
13
13
  import { integer, dateTime } from '../../schema/zod-validation.mjs';
14
14
  import { assertNoWorkerBinding, mcpError, mcpJson } from '../helpers.mjs';
@@ -36,17 +36,27 @@ function configuredBaseUrl(env = process.env) {
36
36
  return url.toString().replace(/\/$/, '');
37
37
  }
38
38
 
39
- async function managementBearer(env = process.env) {
39
+ async function sensitiveInputModule() {
40
+ // Source MCP modules and the published dist tree share the compiled owner.
41
+ for (const location of [new URL('../../auth/sensitive-input.js', import.meta.url), new URL('../../../dist/src/auth/sensitive-input.js', import.meta.url)]) {
42
+ try { await access(location); } catch { continue; }
43
+ return import(location.href);
44
+ }
45
+ throw new Error('Sandbox credentials require the compiled AIWG auth module, which this installation is missing. Run `aiwg doctor` to repair the installation.');
46
+ }
47
+
48
+ async function managementBearer(env = process.env, readStore) {
40
49
  const tokenFile = String(env.AIWG_SANDBOX_MANAGEMENT_TOKEN_FILE ?? '').trim();
41
- if (!tokenFile) throw new Error('AIWG_SANDBOX_MANAGEMENT_TOKEN_FILE is required for the sandbox toolset');
42
- const metadata = await stat(tokenFile);
43
- if (!metadata.isFile()) throw new Error('sandbox management token path is not a regular file');
44
- if (process.platform !== 'win32' && (metadata.mode & 0o077) !== 0) {
45
- throw new Error('sandbox management token file must not be accessible by group or other users');
50
+ const tokenStore = String(env.AIWG_SANDBOX_MANAGEMENT_TOKEN_STORE ?? '').trim();
51
+ if (!tokenFile && !tokenStore) {
52
+ throw new Error('Sandbox management bearer requires AIWG_SANDBOX_MANAGEMENT_TOKEN_FILE or AIWG_SANDBOX_MANAGEMENT_TOKEN_STORE.');
46
53
  }
47
- const token = String(await readFile(tokenFile, 'utf8')).trim();
48
- if (!token || /[\r\n]/.test(token)) throw new Error('sandbox management token file must contain one non-empty bearer token');
49
- return token;
54
+ const { readPrivateValueFile, resolveSensitiveInput } = await sensitiveInputModule();
55
+ return resolveSensitiveInput(tokenFile ? { prefix: 'token', file: tokenFile } : { prefix: 'token', store: tokenStore }, {
56
+ isTTY: false,
57
+ readFile: (pathname) => readPrivateValueFile(pathname, 'AIWG_SANDBOX_MANAGEMENT_TOKEN_FILE'),
58
+ ...(readStore ? { readStore } : {}),
59
+ });
50
60
  }
51
61
 
52
62
  function containsRestricted(value) {
@@ -138,32 +148,40 @@ function typedResult(body, status, validate) {
138
148
  }
139
149
 
140
150
  export class AgenticSandboxMcpClient {
141
- constructor({ env = process.env, fetch = globalThis.fetch.bind(globalThis) } = {}) {
151
+ constructor({ env = process.env, fetch = globalThis.fetch.bind(globalThis), readStore } = {}) {
142
152
  this.env = env;
143
153
  this.fetch = fetch;
154
+ this.readStore = readStore;
144
155
  }
145
156
 
146
157
  async request(path, { method = 'GET', body, headers = {}, validate = (value) => safeResponse(value) } = {}) {
147
158
  assertNoWorkerBinding(body, 'mcp');
148
159
  const baseUrl = configuredBaseUrl(this.env);
149
- const token = await managementBearer(this.env);
150
- const response = await this.fetch(`${baseUrl}${path}`, {
151
- method,
152
- headers: { accept: 'application/json', authorization: `Bearer ${token}`, ...(body === undefined ? {} : { 'content-type': 'application/json' }), ...headers },
153
- ...(body === undefined ? {} : { body: JSON.stringify(body) }),
154
- });
155
- let parsed;
156
- try {
157
- parsed = await response.json();
158
- } catch {
159
- if (response.status === 404 || response.status === 405) parsed = {};
160
- else return mcpError(`sandbox returned non-JSON HTTP ${response.status}`);
161
- }
160
+ const token = await managementBearer(this.env, this.readStore);
161
+ const redact = (text) => String(text).split(token).join('[REDACTED]');
162
162
  try {
163
- assertNoWorkerBinding(parsed, 'mcp');
164
- return typedResult(parsed, response.status, validate);
163
+ const response = await this.fetch(`${baseUrl}${path}`, {
164
+ method,
165
+ headers: { accept: 'application/json', authorization: `Bearer ${token}`, ...(body === undefined ? {} : { 'content-type': 'application/json' }), ...headers },
166
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
167
+ });
168
+ let parsed;
169
+ try {
170
+ const value = await response.json();
171
+ // Upstream errors and metadata can reflect the authorization header.
172
+ parsed = JSON.parse(JSON.stringify(value).split(JSON.stringify(token).slice(1, -1)).join('[REDACTED]'));
173
+ } catch {
174
+ if (response.status === 404 || response.status === 405) parsed = {};
175
+ else return mcpError(`sandbox returned non-JSON HTTP ${response.status}`);
176
+ }
177
+ try {
178
+ assertNoWorkerBinding(parsed, 'mcp');
179
+ return typedResult(parsed, response.status, validate);
180
+ } catch (error) {
181
+ return mcpError(`${error.code ?? 'sandbox_malformed_response'}: ${error.message}`);
182
+ }
165
183
  } catch (error) {
166
- return mcpError(`${error.code ?? 'sandbox_malformed_response'}: ${error.message}`);
184
+ throw new Error(redact(error instanceof Error ? error.message : 'Sandbox management request failed'));
167
185
  }
168
186
  }
169
187
  }
@@ -83,6 +83,13 @@ export function parseGrokBuildModelConfig(text, scope) {
83
83
  parsed.projectModelFields = true;
84
84
  continue;
85
85
  }
86
+ // Presence only: never retain the command or endpoint in discovery output.
87
+ if ((table === 'auth' || table === 'grok_com_config') && key === 'auth_provider_command') {
88
+ parsed.hasExternalAuthProvider = Boolean(unquote(value)?.trim());
89
+ }
90
+ if (table === 'endpoints' && ['models_base_url', 'models_list_url', 'models_endpoint'].includes(key)) {
91
+ parsed.hasCustomModelsEndpoint ||= Boolean(unquote(value)?.trim());
92
+ }
86
93
  if (table === 'models') {
87
94
  if (key === 'default')
88
95
  parsed.defaultModel = unquote(value);
@@ -299,6 +306,19 @@ export async function discoverGrokBuildModels(options = {}) {
299
306
  const discovered = inspectModels(report, validationDiagnostics);
300
307
  const parsedByLayer = await Promise.all(layers.map(layer => layer.role === 'env_overlay'
301
308
  && layer.path.includes('(inline)') ? parseEnvOverlay(env) : readLayer(layer)));
309
+ // Reviewed upstream 2bdd1d6a: external auth plus a custom models endpoint
310
+ // discards [model.*] and ordinary allowed_models. Inspect has no effective
311
+ // catalog, so config reconstruction cannot establish selectable aliases.
312
+ // Conservatively withhold discovery when any active layer names this setup;
313
+ // do not execute the auth provider or fetch its endpoint to fill the gap.
314
+ const externalAuth = Boolean(env.GROK_AUTH_PROVIDER_COMMAND?.trim())
315
+ || parsedByLayer.some(layer => layer?.hasExternalAuthProvider);
316
+ const customEndpoint = Boolean(env.GROK_MODELS_BASE_URL?.trim() || env.GROK_MODELS_LIST_URL?.trim())
317
+ || parsedByLayer.some(layer => layer?.hasCustomModelsEndpoint);
318
+ if (externalAuth && customEndpoint) {
319
+ return { ...base, ...(runtimeVersion ? { runtimeVersion } : {}), errorKind: 'invalid-output',
320
+ error: 'Grok Build external auth with a custom models endpoint requires its runtime catalog; config model aliases and ordinary allowed_models are ignored upstream. Automatic discovery is deferred for this configuration.' };
321
+ }
302
322
  let requirementsDefault;
303
323
  let requirementsAllowed;
304
324
  for (let i = 0; i < layers.length; i++) {
@@ -687,9 +687,9 @@ const BUILT_IN_SEEDS = [
687
687
  source: 'https://github.com/xai-org/grok-build',
688
688
  version: '1.0.38',
689
689
  // Reviewed public tree (docs + inspect schema), not an all-zero placeholder.
690
- revision: '4247f661689354b831191f11eeeac8424993fe3d',
690
+ revision: '2bdd1d6a6369de0e8c68132ea4539e9abd9e14a8',
691
691
  runtime: 'Grok Build CLI',
692
- lastVerified: '2026-09-20',
692
+ lastVerified: '2026-10-26',
693
693
  },
694
694
  surfaces: {
695
695
  primary: 'grok-build',
@@ -233,7 +233,10 @@ export class FileAdmissionStore {
233
233
  const file = openSync(this.path, constants.O_RDONLY | constants.O_NOFOLLOW);
234
234
  try {
235
235
  const stat = fstatSync(file);
236
- if (!stat.isFile() || stat.nlink !== 1 || (stat.mode & 0o077) !== 0 || stat.uid !== process.getuid?.() || stat.size > 16 * 1024 * 1024)
236
+ // nlink is 0 when a writer atomically renamed a new snapshot over the
237
+ // file after this reader opened it; the open descriptor is still a
238
+ // complete earlier snapshot. Only extra hard links are refused.
239
+ if (!stat.isFile() || stat.nlink > 1 || (stat.mode & 0o077) !== 0 || stat.uid !== process.getuid?.() || stat.size > 16 * 1024 * 1024)
237
240
  throw new Error('host authority must be a private bounded regular file');
238
241
  return JSON.parse(readFileSync(file, 'utf8'));
239
242
  }
@@ -37,6 +37,7 @@
37
37
  * @issue #972
38
38
  */
39
39
  import { createHash } from 'node:crypto';
40
+ import { debuglog } from 'node:util';
40
41
  const FORTEMI_QUERY_LIMIT = 50;
41
42
  const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
42
43
  function schemaProperties(tool) {
@@ -371,8 +372,8 @@ export class FortemiAdapter {
371
372
  * Implemented as a lazy import so tests that inject a stub never load
372
373
  * the SDK or touch the registry.
373
374
  */
374
- export const createDefaultMcpClient = async (serverName, registryOverride, environment = process.env) => {
375
- const { McpServerRegistry } = await import('../../mcp/registry.js');
375
+ export const createDefaultMcpClient = async (serverName, registryOverride, environment = process.env, sensitiveInput = {}) => {
376
+ const { McpServerRegistry, resolveMcpCredentialMaps } = await import('../../mcp/registry.js');
376
377
  const registry = registryOverride ?? new McpServerRegistry();
377
378
  const server = await registry.get(serverName);
378
379
  if (!server) {
@@ -381,60 +382,117 @@ export const createDefaultMcpClient = async (serverName, registryOverride, envir
381
382
  }
382
383
  // Lazy imports keep unit tests that inject a stub isolated from transports.
383
384
  const { Client } = await import('@modelcontextprotocol/sdk/client/index.js');
384
- let transport;
385
- if (server.type === 'stdio') {
386
- const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js');
387
- transport = new StdioClientTransport({
388
- command: server.command ?? '',
389
- args: server.args ?? [],
390
- env: server.env,
391
- });
385
+ const hasNewReferences = [...Object.values(server.headerEnv ?? {}), ...Object.values(server.envFrom ?? {})]
386
+ .some(reference => /^(?:file|store):/.test(reference));
387
+ // Keep legacy header/environment behavior unchanged. New locators are resolved
388
+ // only at connection time and never fed back to the registry or config writers.
389
+ if (hasNewReferences && server.type === 'stdio' && debuglog('child_process').enabled) {
390
+ throw new Error('MCP file/store stdio credentials require NODE_DEBUG child_process logging to be disabled.');
392
391
  }
393
- else {
394
- if (!server.url) {
395
- throw new Error(`storage(fortemi): MCP server "${serverName}" has no URL`);
392
+ const resolved = hasNewReferences
393
+ ? await resolveMcpCredentialMaps(server, { ...sensitiveInput, environment })
394
+ : undefined;
395
+ const secrets = resolved ? [
396
+ ...Object.keys(server.envFrom ?? {}).map(key => resolved.env?.[key]),
397
+ ...Object.keys(server.headerEnv ?? {}).map(key => resolved.headers?.[key]),
398
+ ].filter((value) => Boolean(value)).sort((a, b) => b.length - a.length) : [];
399
+ const redact = (text) => secrets.reduce((text, secret) => text.split(secret).join('[REDACTED]'), text);
400
+ const protect = async (operation) => {
401
+ try {
402
+ return await operation();
396
403
  }
397
- const url = validateRemoteMcpUrl(server.url);
398
- const headers = resolveMcpRequestHeaders(server, environment);
399
- if (server.type === 'http') {
400
- const { StreamableHTTPClientTransport } = await import('@modelcontextprotocol/sdk/client/streamableHttp.js');
401
- transport = new StreamableHTTPClientTransport(url, {
402
- requestInit: { headers },
403
- });
404
+ catch (error) {
405
+ throw new Error(redact(error instanceof Error ? error.message : 'MCP transport failed'));
404
406
  }
405
- else if (server.type === 'sse') {
406
- const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js');
407
- transport = new SSEClientTransport(url, {
408
- requestInit: { headers },
409
- eventSourceInit: {
410
- fetch: async (input, init) => {
411
- const merged = new Headers(init?.headers);
412
- for (const [name, value] of Object.entries(headers))
413
- merged.set(name, value);
414
- return fetch(input, { ...init, headers: merged });
415
- },
416
- },
407
+ };
408
+ const protectResult = (value) => {
409
+ if (typeof value === 'string')
410
+ return redact(value);
411
+ if (Array.isArray(value))
412
+ return value.map(protectResult);
413
+ if (value && typeof value === 'object')
414
+ return Object.fromEntries(Object.entries(value).map(([key, value]) => [redact(key), protectResult(value)]));
415
+ return value;
416
+ };
417
+ try {
418
+ let transport;
419
+ if (server.type === 'stdio') {
420
+ const { StdioClientTransport } = await import('@modelcontextprotocol/sdk/client/stdio.js');
421
+ transport = new StdioClientTransport({
422
+ command: server.command ?? '',
423
+ args: server.args ?? [],
424
+ env: resolved?.env ?? server.env,
425
+ ...(hasNewReferences ? { stderr: 'pipe' } : {}),
417
426
  });
418
427
  }
419
428
  else {
420
- throw new Error(`storage(fortemi): unsupported MCP transport "${String(server.type)}"`);
429
+ if (!server.url) {
430
+ throw new Error(`storage(fortemi): MCP server "${serverName}" has no URL`);
431
+ }
432
+ const url = validateRemoteMcpUrl(server.url);
433
+ const headers = resolved?.headers ?? resolveMcpRequestHeaders(server, environment);
434
+ if (resolved) {
435
+ for (const header of Object.keys(server.headerEnv ?? {})) {
436
+ if (header.toLowerCase() === 'authorization')
437
+ headers[header] = `Bearer ${headers[header]}`;
438
+ }
439
+ }
440
+ if (server.type === 'http') {
441
+ const { StreamableHTTPClientTransport } = await import('@modelcontextprotocol/sdk/client/streamableHttp.js');
442
+ transport = new StreamableHTTPClientTransport(url, {
443
+ requestInit: { headers },
444
+ });
445
+ }
446
+ else if (server.type === 'sse') {
447
+ const { SSEClientTransport } = await import('@modelcontextprotocol/sdk/client/sse.js');
448
+ transport = new SSEClientTransport(url, {
449
+ requestInit: { headers },
450
+ eventSourceInit: {
451
+ fetch: async (input, init) => {
452
+ const merged = new Headers(init?.headers);
453
+ for (const [name, value] of Object.entries(headers))
454
+ merged.set(name, value);
455
+ return fetch(input, { ...init, headers: merged });
456
+ },
457
+ },
458
+ });
459
+ }
460
+ else {
461
+ throw new Error(`storage(fortemi): unsupported MCP transport "${String(server.type)}"`);
462
+ }
421
463
  }
464
+ const client = new Client({ name: 'aiwg-storage-fortemi-adapter', version: '1.0.0' }, { capabilities: {} });
465
+ if (hasNewReferences && server.type === 'stdio') {
466
+ // The recipient's stderr can contain credentials. Drain without forwarding.
467
+ transport.stderr?.on('data', () => { });
468
+ }
469
+ if (hasNewReferences)
470
+ await protect(() => client.connect(transport));
471
+ else
472
+ await client.connect(transport);
473
+ return {
474
+ async callTool(name, args) {
475
+ const call = async () => unwrapMcpToolResult(await client.callTool({ name, arguments: args }));
476
+ return hasNewReferences ? protectResult(await protect(call)) : call();
477
+ },
478
+ async listTools() {
479
+ return hasNewReferences ? protectResult(await protect(() => client.listTools())) : client.listTools();
480
+ },
481
+ serverVersion() {
482
+ return hasNewReferences ? protectResult(client.getServerVersion()) : client.getServerVersion();
483
+ },
484
+ async close() {
485
+ if (hasNewReferences)
486
+ await protect(() => client.close());
487
+ else
488
+ await client.close();
489
+ },
490
+ };
491
+ }
492
+ catch (error) {
493
+ if (!hasNewReferences)
494
+ throw error;
495
+ throw new Error(redact(error instanceof Error ? error.message : 'MCP transport failed'));
422
496
  }
423
- const client = new Client({ name: 'aiwg-storage-fortemi-adapter', version: '1.0.0' }, { capabilities: {} });
424
- await client.connect(transport);
425
- return {
426
- async callTool(name, args) {
427
- return unwrapMcpToolResult(await client.callTool({ name, arguments: args }));
428
- },
429
- async listTools() {
430
- return client.listTools();
431
- },
432
- serverVersion() {
433
- return client.getServerVersion();
434
- },
435
- async close() {
436
- await client.close();
437
- },
438
- };
439
497
  };
440
498
  //# sourceMappingURL=fortemi.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiwg/cli",
3
- "version": "2026.10.7",
3
+ "version": "2026.10.8",
4
4
  "description": "Lightweight AIWG CLI for signed, versioned web-backed resources.",
5
5
  "type": "module",
6
6
  "license": "MIT",