UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

90 lines • 3.81 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import { timedOut } from "../../../accessor/mcp.js"; import { compactJsonText } from "../../../core/render/json.js"; import { IOResult } from "../../../io/types.js"; const ENCODER = new TextEncoder(); /** The leaf errors inside an aggregate error, one each, as `<name>: <message>`. */ export function causes(error) { if (error instanceof AggregateError) return error.errors.flatMap(causes); if (error instanceof Error) return [`${error.name}: ${error.message}`]; return [String(error)]; } /** The subcommand a tool becomes: underscores read as dashes. */ export function commandName(toolName) { return toolName.replaceAll('_', '-'); } /** A tool's answer as text: text blocks as they are, others as compact JSON. */ export function render(result) { const parts = result.content.map((block) => block.type === 'text' ? block.text : compactJsonText(block)); if (parts.length === 0 && result.structuredContent !== undefined) { parts.push(compactJsonText(result.structuredContent)); } const text = parts.join('\n'); return text === '' || text.endsWith('\n') ? text : `${text}\n`; } /** Refuse a number too large for a double, which JSON.parse reads as Infinity. */ function finite(_key, value) { if (typeof value === 'number' && !Number.isFinite(value)) throw new RangeError('not finite'); return value; } function refuse(message, code) { return [null, new IOResult({ exitCode: code, stderr: ENCODER.encode(`${message}\n`) })]; } /** * Run one MCP tool with the JSON object given as its operand. * * @param tool the tool's MCP name. * @param accessor the install's server. * @param _paths unused; a tool takes no path. * @param texts at most one JSON object. * @param ctx the command context; its signal abandons the call. */ export async function callTool(tool, accessor, _paths, texts, ctx) { const command = commandName(tool); if (texts.length > 1) return refuse(`${command}: takes one JSON object`, 2); let args; try { args = texts.length > 0 ? JSON.parse(texts[0] ?? '', finite) : {}; } catch { // The parser's own wording differs by host, so neither host prints it. return refuse(`${command}: invalid JSON`, 2); } if (args === null || typeof args !== 'object' || Array.isArray(args)) { return refuse(`${command}: JSON must be an object`, 2); } let result; try { result = await accessor.callTool(tool, args, ctx.signal); } catch (error) { if (ctx.signal?.aborted === true) throw error; if (await timedOut(error)) return refuse(`${command}: MCP server timed out`, 1); return refuse(`${command}: MCP server failed: ${causes(error).join('; ')}`, 1); } const out = ENCODER.encode(render(result)); if (result.isError === true) { const stderr = out.length > 0 ? out : ENCODER.encode(`${command}: the tool reported an error\n`); return [null, new IOResult({ exitCode: 1, stderr })]; } return [out, new IOResult()]; } //# sourceMappingURL=call.js.map