|
| 1 | +import { |
| 2 | + defineCommand, |
| 3 | + McpClient, |
| 4 | + bailianMcpUrl, |
| 5 | + detectOutputFormat, |
| 6 | + type Config, |
| 7 | + type GlobalFlags, |
| 8 | +} from "bailian-cli-core"; |
| 9 | +import { failIfMissing } from "../../output/prompt.ts"; |
| 10 | +import { emitResult } from "../../output/output.ts"; |
| 11 | +import { ensureApiKey } from "../../utils/ensure-key.ts"; |
| 12 | + |
| 13 | +function parseArgFlags(raw: string[]): Record<string, unknown> { |
| 14 | + const out: Record<string, unknown> = {}; |
| 15 | + for (const item of raw) { |
| 16 | + const idx = item.indexOf("="); |
| 17 | + if (idx <= 0) { |
| 18 | + process.stderr.write(`Error: --arg must be in K=V form, got: ${item}\n`); |
| 19 | + process.exit(1); |
| 20 | + } |
| 21 | + const key = item.slice(0, idx).trim(); |
| 22 | + const rawVal = item.slice(idx + 1); |
| 23 | + try { |
| 24 | + out[key] = JSON.parse(rawVal); |
| 25 | + } catch { |
| 26 | + out[key] = rawVal; |
| 27 | + } |
| 28 | + } |
| 29 | + return out; |
| 30 | +} |
| 31 | + |
| 32 | +export default defineCommand({ |
| 33 | + name: "mcp call", |
| 34 | + description: "Call a tool on an MCP server (tools/call)", |
| 35 | + usage: "bl mcp call <server-code>.<tool> [--arg k=v ...] [--json '{...}'] [--url <url>]", |
| 36 | + options: [ |
| 37 | + { |
| 38 | + flag: "<server-code>.<tool>", |
| 39 | + description: |
| 40 | + "Server code and tool name joined by a dot, e.g. market-cmapi00073529.SmartStockSelection", |
| 41 | + required: true, |
| 42 | + }, |
| 43 | + { |
| 44 | + flag: "--arg <kv>", |
| 45 | + description: "Tool argument (repeatable). Values parsed as JSON if possible, else string.", |
| 46 | + type: "array", |
| 47 | + }, |
| 48 | + { |
| 49 | + flag: "--json <obj>", |
| 50 | + description: "Full arguments object as JSON; merged with --arg (arg wins).", |
| 51 | + }, |
| 52 | + { |
| 53 | + flag: "--query <text>", |
| 54 | + description: "Shortcut for --arg query=<text> (mirrors many DashScope MCP tools).", |
| 55 | + }, |
| 56 | + { flag: "--url <url>", description: "Override the MCP endpoint URL (for non-Bailian servers)" }, |
| 57 | + ], |
| 58 | + examples: [ |
| 59 | + 'bl mcp call market-cmapi00073529.SmartStockSelection --query "筛选ROE>15%的消费股"', |
| 60 | + 'bl mcp call market-cmapi00073529.FinQuery --json \'{"q":"贵州茅台","limit":5}\'', |
| 61 | + "bl mcp call market-cmapi00073529.SmartFundSelection --arg riskLevel=R3 --arg minScale=10", |
| 62 | + ], |
| 63 | + async run(config: Config, flags: GlobalFlags) { |
| 64 | + const positional = |
| 65 | + ((flags as Record<string, unknown>)._positional as string[] | undefined) ?? []; |
| 66 | + const target = positional[0]; |
| 67 | + if (!target) failIfMissing("<server-code>.<tool>", "bl mcp call <server-code>.<tool>"); |
| 68 | + |
| 69 | + const dot = target!.indexOf("."); |
| 70 | + if (dot <= 0 || dot === target!.length - 1) { |
| 71 | + process.stderr.write(`Error: target must be <server-code>.<tool>, got "${target}".\n`); |
| 72 | + process.exit(1); |
| 73 | + } |
| 74 | + const serverCode = target!.slice(0, dot); |
| 75 | + const toolName = target!.slice(dot + 1); |
| 76 | + |
| 77 | + let toolArgs: Record<string, unknown> = {}; |
| 78 | + if (flags.json) { |
| 79 | + try { |
| 80 | + const parsed = JSON.parse(flags.json as string); |
| 81 | + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { |
| 82 | + process.stderr.write("Error: --json must decode to an object.\n"); |
| 83 | + process.exit(1); |
| 84 | + } |
| 85 | + toolArgs = parsed as Record<string, unknown>; |
| 86 | + } catch (err) { |
| 87 | + process.stderr.write(`Error: --json is not valid JSON — ${(err as Error).message}\n`); |
| 88 | + process.exit(1); |
| 89 | + } |
| 90 | + } |
| 91 | + Object.assign(toolArgs, parseArgFlags((flags.arg as string[] | undefined) ?? [])); |
| 92 | + if (flags.query !== undefined) toolArgs.query = flags.query; |
| 93 | + |
| 94 | + const url = (flags.url as string) || bailianMcpUrl(config.baseUrl, serverCode); |
| 95 | + const format = detectOutputFormat(config.output); |
| 96 | + |
| 97 | + if (config.dryRun) { |
| 98 | + emitResult( |
| 99 | + { |
| 100 | + server: serverCode, |
| 101 | + url, |
| 102 | + tool: toolName, |
| 103 | + arguments: toolArgs, |
| 104 | + }, |
| 105 | + format, |
| 106 | + ); |
| 107 | + return; |
| 108 | + } |
| 109 | + |
| 110 | + await ensureApiKey(config); |
| 111 | + const client = new McpClient(config, url); |
| 112 | + await client.initialize(); |
| 113 | + const result = await client.callTool(toolName, toolArgs); |
| 114 | + |
| 115 | + if (result.isError) { |
| 116 | + const errText = result.content.map((c) => c.text || "").join("\n"); |
| 117 | + process.stderr.write(`Tool error: ${errText}\n`); |
| 118 | + process.exit(1); |
| 119 | + } |
| 120 | + |
| 121 | + emitResult(result, format); |
| 122 | + }, |
| 123 | +}); |
0 commit comments