From 3d2363d1b26d60c98547427422a59e19dfffe4de Mon Sep 17 00:00:00 2001 From: philschmid Date: Thu, 5 Feb 2026 14:55:23 +0000 Subject: [PATCH] fix: call command outputs raw text content instead of MCP envelope Fixes #25 The call command was outputting the full MCP protocol envelope: { "content": [{ "type": "text", "text": "..." }] } Now outputs raw text content directly, making it easier to pipe to grep, head, jq, and other CLI tools without needing .content[0].text. Changes: - Use existing formatToolResult helper in call.ts - Update SKILL.md examples (remove jq extraction patterns) - Update CHANGELOG.md to reflect correct behavior - Add e2e test to prevent regression --- CHANGELOG.md | 4 ++-- SKILL.md | 16 +++++++--------- src/commands/call.ts | 7 ++++--- tests/integration/cli.test.ts | 22 ++++++++++++++++++++++ 4 files changed, 35 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c56163..f071d58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,8 +22,8 @@ - **3-Subcommand Architecture** - `info`, `grep`, `call` - Flexible format support: `server tool` and `server/tool` - - `call` always outputs raw JSON (for piping/scripting) - - `info`/`grep` always output human-readable format + - `call` outputs raw text content (CLI-friendly, pipe to grep/head/etc.) + - `info`/`grep` output human-readable format - **Improved Error Messages for LLMs** - AMBIGUOUS_COMMAND: Shows both `call` and `info` options diff --git a/SKILL.md b/SKILL.md index effd740..eb5c222 100644 --- a/SKILL.md +++ b/SKILL.md @@ -52,8 +52,8 @@ cat args.json | mcp-cli call filesystem read_file # Search for tools mcp-cli grep "*file*" -# Extract text from result -mcp-cli call filesystem read_file '{"path": "./file"}' | jq -r '.content[0].text' +# Output is raw text (pipe-friendly) +mcp-cli call filesystem read_file '{"path": "./file"}' | head -10 ``` ## Advanced Chaining @@ -61,31 +61,29 @@ mcp-cli call filesystem read_file '{"path": "./file"}' | jq -r '.content[0].text ```bash # Chain: search files → read first match mcp-cli call filesystem search_files '{"path": ".", "pattern": "*.md"}' \ - | jq -r '.content[0].text | split("\n")[0]' \ + | head -1 \ | xargs -I {} mcp-cli call filesystem read_file '{"path": "{}"}' # Loop: process multiple files mcp-cli call filesystem list_directory '{"path": "./src"}' \ - | jq -r '.content[0].text | split("\n")[]' \ | while read f; do mcp-cli call filesystem read_file "{\"path\": \"$f\"}"; done # Conditional: check before reading mcp-cli call filesystem list_directory '{"path": "."}' \ - | jq -e '.content[0].text | contains("README")' \ + | grep -q "README" \ && mcp-cli call filesystem read_file '{"path": "./README.md"}' # Multi-server aggregation { mcp-cli call github search_repositories '{"query": "mcp", "per_page": 3}' mcp-cli call filesystem list_directory '{"path": "."}' -} | jq -s '.' +} # Save to file -mcp-cli call github get_file_contents '{"owner": "x", "repo": "y", "path": "z"}' \ - | jq -r '.content[0].text' > output.txt +mcp-cli call github get_file_contents '{"owner": "x", "repo": "y", "path": "z"}' > output.txt ``` -**jq tips:** `-r` raw output, `-e` exit 1 if false, `-s` slurp multiple inputs +**Note:** `call` outputs raw text content directly (no jq needed for text extraction) ## Options diff --git a/src/commands/call.ts b/src/commands/call.ts index 9edcaad..4579847 100644 --- a/src/commands/call.ts +++ b/src/commands/call.ts @@ -29,7 +29,7 @@ import { toolExecutionError, toolNotFoundError, } from '../errors.js'; -import { formatJson } from '../output.js'; +import { formatJson, formatToolResult } from '../output.js'; export interface CallOptions { target: string; // "server/tool" @@ -161,8 +161,9 @@ export async function callCommand(options: CallOptions): Promise { try { const result = await connection.callTool(toolName, args); - // Always output raw JSON for programmatic use - console.log(formatJson(result)); + // Extract text content from MCP response for CLI-friendly output + // Uses formatToolResult which extracts text from MCP content array + console.log(formatToolResult(result)); } catch (error) { // Try to get available tools for better error message let availableTools: string[] | undefined; diff --git a/tests/integration/cli.test.ts b/tests/integration/cli.test.ts index 21f4722..afb1e78 100644 --- a/tests/integration/cli.test.ts +++ b/tests/integration/cli.test.ts @@ -241,6 +241,28 @@ describe('CLI Integration Tests', () => { // We just verify it doesn't crash expect(typeof result.exitCode).toBe('number'); }); + + test('outputs raw text content, not MCP envelope (issue #25)', async () => { + // This test ensures the call command outputs raw text content + // instead of the full MCP protocol envelope like: + // { "content": [{ "type": "text", "text": "..." }] } + const result = await runCli([ + 'call', + 'filesystem', + 'read_file', + JSON.stringify({ path: testFilePath }), + ]); + + expect(result.exitCode).toBe(0); + + // Output should be the raw file content + expect(result.stdout).toContain('Hello from test file!'); + + // Output should NOT contain MCP envelope structure + expect(result.stdout).not.toContain('"content"'); + expect(result.stdout).not.toContain('"type"'); + expect(result.stdout).not.toContain('"text"'); + }); }); describe('error handling', () => {