Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 7 additions & 9 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,40 +52,38 @@ 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

```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

Expand Down
7 changes: 4 additions & 3 deletions src/commands/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -161,8 +161,9 @@ export async function callCommand(options: CallOptions): Promise<void> {
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;
Expand Down
22 changes: 22 additions & 0 deletions tests/integration/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down