ai-cli-mcp 2.4.0 → 2.5.0

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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "acm-dev": {
4
+ "command": "npm",
5
+ "args": [
6
+ "run",
7
+ "dev"
8
+ ]
9
+ }
10
+ }
11
+ }
package/.mcp.json CHANGED
@@ -3,7 +3,8 @@
3
3
  "acm-dev": {
4
4
  "command": "npm",
5
5
  "args": [
6
- "start"
6
+ "run",
7
+ "dev"
7
8
  ]
8
9
  }
9
10
  }
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ # [2.5.0](https://github.com/mkXultra/claude-code-mcp/compare/v2.4.0...v2.5.0) (2026-01-24)
2
+
3
+
4
+ ### Features
5
+
6
+ * enhance output parsers for Codex and Claude with tool usage extraction ([b6410a1](https://github.com/mkXultra/claude-code-mcp/commit/b6410a104666eca592735acea093b877c0f03f64))
7
+ * track command execution in Codex output and include .gemini config ([91f7f06](https://github.com/mkXultra/claude-code-mcp/commit/91f7f067a1d453fd8e3a5a95bb90f21b7df0af8a))
8
+ * update Claude CLI args to stream-json and add verbose option to get_result ([b7f9abc](https://github.com/mkXultra/claude-code-mcp/commit/b7f9abc11c56ad0c8c95e90a614d1d869d8a3bfa))
9
+
1
10
  # [2.4.0](https://github.com/mkXultra/claude-code-mcp/compare/v2.3.3...v2.4.0) (2026-01-24)
2
11
 
3
12
 
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseCodexOutput, parseClaudeOutput } from '../parsers.js';
3
+ describe('parseCodexOutput', () => {
4
+ it('should parse basic Codex output with message and session_id', () => {
5
+ const output = `
6
+ {"type":"thread.started","thread_id":"test-session-id"}
7
+ {"type":"turn.started"}
8
+ {"type":"item.completed","item":{"type":"agent_message","text":"Hello world"}}
9
+ {"type":"turn.completed"}
10
+ `;
11
+ const result = parseCodexOutput(output);
12
+ expect(result).toEqual({
13
+ message: "Hello world",
14
+ session_id: "test-session-id",
15
+ token_count: null,
16
+ tools: undefined
17
+ });
18
+ });
19
+ it('should extract MCP tool calls', () => {
20
+ const output = `
21
+ {"type":"thread.started","thread_id":"tool-test-id"}
22
+ {"type":"turn.started"}
23
+ {"type":"item.completed","item":{"id":"item_1","type":"mcp_tool_call","server":"acm","tool":"run","arguments":{"model":"gemini-2.5-flash","prompt":"hi"},"result":{"content":[{"text":"started","type":"text"}]},"status":"completed"}}
24
+ {"type":"item.completed","item":{"type":"agent_message","text":"Tool executed"}}
25
+ {"type":"turn.completed"}
26
+ `;
27
+ const result = parseCodexOutput(output);
28
+ expect(result.message).toBe("Tool executed");
29
+ expect(result.session_id).toBe("tool-test-id");
30
+ expect(result.tools).toHaveLength(1);
31
+ expect(result.tools[0]).toEqual({
32
+ tool: "run",
33
+ server: "acm",
34
+ input: { model: "gemini-2.5-flash", prompt: "hi" },
35
+ output: { content: [{ text: "started", type: "text" }] }
36
+ });
37
+ });
38
+ it('should handle multiple tool calls', () => {
39
+ const output = `
40
+ {"type":"item.completed","item":{"type":"mcp_tool_call","tool":"tool1","arguments":{"arg":1},"result":"res1"}}
41
+ {"type":"item.completed","item":{"type":"mcp_tool_call","tool":"tool2","arguments":{"arg":2},"result":"res2"}}
42
+ `;
43
+ const result = parseCodexOutput(output);
44
+ expect(result.tools).toHaveLength(2);
45
+ expect(result.tools[0].tool).toBe("tool1");
46
+ expect(result.tools[1].tool).toBe("tool2");
47
+ });
48
+ it('should return null for empty input', () => {
49
+ expect(parseCodexOutput("")).toBeNull();
50
+ });
51
+ it('should handle invalid JSON gracefully', () => {
52
+ const output = `
53
+ {"type":"valid"}
54
+ INVALID_JSON
55
+ {"type":"item.completed","item":{"type":"agent_message","text":"Still parses valid lines"}}
56
+ `;
57
+ const result = parseCodexOutput(output);
58
+ expect(result.message).toBe("Still parses valid lines");
59
+ });
60
+ });
61
+ describe('parseClaudeOutput', () => {
62
+ it('should parse legacy JSON output', () => {
63
+ const output = JSON.stringify({
64
+ content: [{ type: 'text', text: 'Hello' }]
65
+ });
66
+ const result = parseClaudeOutput(output);
67
+ expect(result).toEqual({
68
+ content: [{ type: 'text', text: 'Hello' }]
69
+ });
70
+ });
71
+ it('should parse stream-json (NDJSON) output', () => {
72
+ const output = `
73
+ {"type":"system","session_id":"test-claude-session"}
74
+ {"type":"assistant","message":{"content":[{"type":"text","text":"Thinking..."}]}}
75
+ {"type":"assistant","message":{"content":[{"type":"tool_use","id":"call_1","name":"mcp__acm__run","input":{"prompt":"hi"}}]}}
76
+ {"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"call_1","content":"done"}]}}
77
+ {"type":"result","result":"Final Answer","is_error":false}
78
+ `;
79
+ const result = parseClaudeOutput(output);
80
+ expect(result.message).toBe("Final Answer");
81
+ expect(result.session_id).toBe("test-claude-session");
82
+ expect(result.tools).toHaveLength(1);
83
+ expect(result.tools[0]).toEqual({
84
+ tool: "mcp__acm__run",
85
+ input: { prompt: "hi" },
86
+ output: "done"
87
+ });
88
+ });
89
+ it('should handle invalid NDJSON lines gracefully', () => {
90
+ const output = `
91
+ {"type":"system"}
92
+ INVALID_LINE
93
+ {"type":"result","result":"Success"}
94
+ `;
95
+ const result = parseClaudeOutput(output);
96
+ expect(result.message).toBe("Success");
97
+ });
98
+ });
package/dist/parsers.js CHANGED
@@ -10,6 +10,7 @@ export function parseCodexOutput(stdout) {
10
10
  let lastMessage = null;
11
11
  let tokenCount = null;
12
12
  let threadId = null;
13
+ const tools = [];
13
14
  for (const line of lines) {
14
15
  if (line.trim()) {
15
16
  try {
@@ -29,6 +30,22 @@ export function parseCodexOutput(stdout) {
29
30
  else if (parsed.msg?.type === 'token_count') {
30
31
  tokenCount = parsed.msg;
31
32
  }
33
+ else if (parsed.type === 'item.completed' && parsed.item?.type === 'mcp_tool_call') {
34
+ tools.push({
35
+ server: parsed.item.server,
36
+ tool: parsed.item.tool,
37
+ input: parsed.item.arguments, // Map arguments to input to match common patterns
38
+ output: parsed.item.result
39
+ });
40
+ }
41
+ else if (parsed.type === 'item.completed' && parsed.item?.type === 'command_execution') {
42
+ tools.push({
43
+ tool: 'command_execution',
44
+ input: { command: parsed.item.command },
45
+ output: parsed.item.aggregated_output,
46
+ exit_code: parsed.item.exit_code
47
+ });
48
+ }
32
49
  }
33
50
  catch (e) {
34
51
  // Skip invalid JSON lines
@@ -36,11 +53,12 @@ export function parseCodexOutput(stdout) {
36
53
  }
37
54
  }
38
55
  }
39
- if (lastMessage || tokenCount || threadId) {
56
+ if (lastMessage || tokenCount || threadId || tools.length > 0) {
40
57
  return {
41
58
  message: lastMessage,
42
59
  token_count: tokenCount,
43
- session_id: threadId
60
+ session_id: threadId,
61
+ tools: tools.length > 0 ? tools : undefined
44
62
  };
45
63
  }
46
64
  }
@@ -50,18 +68,86 @@ export function parseCodexOutput(stdout) {
50
68
  return null;
51
69
  }
52
70
  /**
53
- * Parse Claude JSON output
71
+ * Parse Claude Output (supports both JSON and stream-json/NDJSON)
54
72
  */
55
73
  export function parseClaudeOutput(stdout) {
56
74
  if (!stdout)
57
75
  return null;
76
+ // First try parsing as a single JSON object (backward compatibility)
58
77
  try {
59
78
  return JSON.parse(stdout);
60
79
  }
61
80
  catch (e) {
62
- debugLog(`[Debug] Failed to parse Claude JSON output: ${e}`);
81
+ // If not valid single JSON, proceed to parse as NDJSON
82
+ }
83
+ try {
84
+ const lines = stdout.trim().split('\n');
85
+ let lastMessage = null;
86
+ let sessionId = null;
87
+ const toolsMap = new Map(); // Map by tool_use id for matching results
88
+ for (const line of lines) {
89
+ if (!line.trim())
90
+ continue;
91
+ try {
92
+ const parsed = JSON.parse(line);
93
+ // Extract session ID from any message that has it
94
+ if (parsed.session_id) {
95
+ sessionId = parsed.session_id;
96
+ }
97
+ // Extract final result message
98
+ if (parsed.type === 'result' && parsed.result) {
99
+ lastMessage = parsed.result;
100
+ }
101
+ // Extract tool usage from assistant messages
102
+ if (parsed.type === 'assistant' && parsed.message?.content) {
103
+ for (const content of parsed.message.content) {
104
+ if (content.type === 'tool_use') {
105
+ toolsMap.set(content.id, {
106
+ tool: content.name,
107
+ input: content.input,
108
+ output: null // Will be filled when tool_result is found
109
+ });
110
+ }
111
+ }
112
+ }
113
+ // Match tool results from user messages
114
+ if (parsed.type === 'user' && parsed.message?.content) {
115
+ for (const content of parsed.message.content) {
116
+ if (content.type === 'tool_result' && content.tool_use_id) {
117
+ const tool = toolsMap.get(content.tool_use_id);
118
+ if (tool) {
119
+ // Extract text from content array
120
+ if (Array.isArray(content.content)) {
121
+ const textContent = content.content.find((c) => c.type === 'text');
122
+ tool.output = textContent?.text || null;
123
+ }
124
+ else {
125
+ tool.output = content.content;
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+ catch (e) {
133
+ debugLog(`[Debug] Skipping invalid JSON line in Claude output: ${line}`);
134
+ }
135
+ }
136
+ // Convert Map to array
137
+ const tools = Array.from(toolsMap.values());
138
+ if (lastMessage || sessionId || tools.length > 0) {
139
+ return {
140
+ message: lastMessage, // This is the final result text
141
+ session_id: sessionId,
142
+ tools: tools.length > 0 ? tools : undefined
143
+ };
144
+ }
145
+ }
146
+ catch (e) {
147
+ debugLog(`[Debug] Failed to parse Claude NDJSON output: ${e}`);
63
148
  return null;
64
149
  }
150
+ return null;
65
151
  }
66
152
  /**
67
153
  * Parse Gemini JSON output
package/dist/server.js CHANGED
@@ -329,6 +329,10 @@ export class ClaudeCodeServer {
329
329
  type: 'number',
330
330
  description: 'The process ID returned by run tool.',
331
331
  },
332
+ verbose: {
333
+ type: 'boolean',
334
+ description: 'Optional: If true, returns detailed execution information including tool usage history. Defaults to false.',
335
+ }
332
336
  },
333
337
  required: ['pid'],
334
338
  },
@@ -506,7 +510,7 @@ export class ClaudeCodeServer {
506
510
  else {
507
511
  // Handle Claude (default)
508
512
  cliPath = this.claudeCliPath;
509
- processArgs = ['--dangerously-skip-permissions', '--output-format', 'json'];
513
+ processArgs = ['--dangerously-skip-permissions', '--output-format', 'stream-json', '--verbose'];
510
514
  // Add session_id if provided (Claude only)
511
515
  if (toolArguments.session_id && typeof toolArguments.session_id === 'string') {
512
516
  processArgs.push('-r', toolArguments.session_id);
@@ -604,18 +608,20 @@ export class ClaudeCodeServer {
604
608
  /**
605
609
  * Helper to get process result object
606
610
  */
607
- getProcessResultHelper(pid) {
611
+ getProcessResultHelper(pid, verbose = false) {
608
612
  const process = processManager.get(pid);
609
613
  if (!process) {
610
614
  throw new McpError(ErrorCode.InvalidParams, `Process with PID ${pid} not found`);
611
615
  }
612
616
  // Parse output based on agent type
613
617
  let agentOutput = null;
614
- if (process.stdout) {
615
- if (process.toolType === 'codex') {
616
- agentOutput = parseCodexOutput(process.stdout);
617
- }
618
- else if (process.toolType === 'claude') {
618
+ if (process.toolType === 'codex') {
619
+ // Codex may output structured logs to stderr
620
+ const combinedOutput = (process.stdout || '') + '\n' + (process.stderr || '');
621
+ agentOutput = parseCodexOutput(combinedOutput);
622
+ }
623
+ else if (process.stdout) {
624
+ if (process.toolType === 'claude') {
619
625
  agentOutput = parseClaudeOutput(process.stdout);
620
626
  }
621
627
  else if (process.toolType === 'gemini') {
@@ -635,7 +641,14 @@ export class ClaudeCodeServer {
635
641
  };
636
642
  // If we have valid output from agent, include it
637
643
  if (agentOutput) {
638
- response.agentOutput = agentOutput;
644
+ // Filter out tools if not verbose
645
+ if (!verbose && agentOutput.tools) {
646
+ const { tools, ...rest } = agentOutput;
647
+ response.agentOutput = rest;
648
+ }
649
+ else {
650
+ response.agentOutput = agentOutput;
651
+ }
639
652
  // Extract session_id if available
640
653
  if (agentOutput.session_id) {
641
654
  response.session_id = agentOutput.session_id;
@@ -656,7 +669,8 @@ export class ClaudeCodeServer {
656
669
  throw new McpError(ErrorCode.InvalidParams, 'Missing or invalid required parameter: pid');
657
670
  }
658
671
  const pid = toolArguments.pid;
659
- const response = this.getProcessResultHelper(pid);
672
+ const verbose = !!toolArguments.verbose;
673
+ const response = this.getProcessResultHelper(pid, verbose);
660
674
  return {
661
675
  content: [{
662
676
  type: 'text',
@@ -706,8 +720,8 @@ export class ClaudeCodeServer {
706
720
  catch (error) {
707
721
  throw new McpError(ErrorCode.InternalError, error.message);
708
722
  }
709
- // Collect results
710
- const results = pids.map(pid => this.getProcessResultHelper(pid));
723
+ // Collect results (verbose=false for wait)
724
+ const results = pids.map(pid => this.getProcessResultHelper(pid, false));
711
725
  return {
712
726
  content: [{
713
727
  type: 'text',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-cli-mcp",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "MCP server for AI CLI tools (Claude, Codex, and Gemini) with background process management",
5
5
  "author": "mkXultra",
6
6
  "license": "MIT",
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { parseCodexOutput, parseClaudeOutput } from '../parsers.js';
3
+
4
+ describe('parseCodexOutput', () => {
5
+ it('should parse basic Codex output with message and session_id', () => {
6
+ const output = `
7
+ {"type":"thread.started","thread_id":"test-session-id"}
8
+ {"type":"turn.started"}
9
+ {"type":"item.completed","item":{"type":"agent_message","text":"Hello world"}}
10
+ {"type":"turn.completed"}
11
+ `;
12
+ const result = parseCodexOutput(output);
13
+ expect(result).toEqual({
14
+ message: "Hello world",
15
+ session_id: "test-session-id",
16
+ token_count: null,
17
+ tools: undefined
18
+ });
19
+ });
20
+
21
+ it('should extract MCP tool calls', () => {
22
+ const output = `
23
+ {"type":"thread.started","thread_id":"tool-test-id"}
24
+ {"type":"turn.started"}
25
+ {"type":"item.completed","item":{"id":"item_1","type":"mcp_tool_call","server":"acm","tool":"run","arguments":{"model":"gemini-2.5-flash","prompt":"hi"},"result":{"content":[{"text":"started","type":"text"}]},"status":"completed"}}
26
+ {"type":"item.completed","item":{"type":"agent_message","text":"Tool executed"}}
27
+ {"type":"turn.completed"}
28
+ `;
29
+ const result = parseCodexOutput(output);
30
+
31
+ expect(result.message).toBe("Tool executed");
32
+ expect(result.session_id).toBe("tool-test-id");
33
+ expect(result.tools).toHaveLength(1);
34
+ expect(result.tools[0]).toEqual({
35
+ tool: "run",
36
+ server: "acm",
37
+ input: { model: "gemini-2.5-flash", prompt: "hi" },
38
+ output: { content: [{ text: "started", type: "text" }] }
39
+ });
40
+ });
41
+
42
+ it('should handle multiple tool calls', () => {
43
+ const output = `
44
+ {"type":"item.completed","item":{"type":"mcp_tool_call","tool":"tool1","arguments":{"arg":1},"result":"res1"}}
45
+ {"type":"item.completed","item":{"type":"mcp_tool_call","tool":"tool2","arguments":{"arg":2},"result":"res2"}}
46
+ `;
47
+ const result = parseCodexOutput(output);
48
+ expect(result.tools).toHaveLength(2);
49
+ expect(result.tools[0].tool).toBe("tool1");
50
+ expect(result.tools[1].tool).toBe("tool2");
51
+ });
52
+
53
+ it('should return null for empty input', () => {
54
+ expect(parseCodexOutput("")).toBeNull();
55
+ });
56
+
57
+ it('should handle invalid JSON gracefully', () => {
58
+ const output = `
59
+ {"type":"valid"}
60
+ INVALID_JSON
61
+ {"type":"item.completed","item":{"type":"agent_message","text":"Still parses valid lines"}}
62
+ `;
63
+ const result = parseCodexOutput(output);
64
+ expect(result.message).toBe("Still parses valid lines");
65
+ });
66
+ });
67
+
68
+ describe('parseClaudeOutput', () => {
69
+ it('should parse legacy JSON output', () => {
70
+ const output = JSON.stringify({
71
+ content: [{ type: 'text', text: 'Hello' }]
72
+ });
73
+ const result = parseClaudeOutput(output);
74
+ expect(result).toEqual({
75
+ content: [{ type: 'text', text: 'Hello' }]
76
+ });
77
+ });
78
+
79
+ it('should parse stream-json (NDJSON) output', () => {
80
+ const output = `
81
+ {"type":"system","session_id":"test-claude-session"}
82
+ {"type":"assistant","message":{"content":[{"type":"text","text":"Thinking..."}]}}
83
+ {"type":"assistant","message":{"content":[{"type":"tool_use","id":"call_1","name":"mcp__acm__run","input":{"prompt":"hi"}}]}}
84
+ {"type":"user","message":{"content":[{"type":"tool_result","tool_use_id":"call_1","content":"done"}]}}
85
+ {"type":"result","result":"Final Answer","is_error":false}
86
+ `;
87
+ const result = parseClaudeOutput(output);
88
+
89
+ expect(result.message).toBe("Final Answer");
90
+ expect(result.session_id).toBe("test-claude-session");
91
+ expect(result.tools).toHaveLength(1);
92
+ expect(result.tools[0]).toEqual({
93
+ tool: "mcp__acm__run",
94
+ input: { prompt: "hi" },
95
+ output: "done"
96
+ });
97
+ });
98
+
99
+ it('should handle invalid NDJSON lines gracefully', () => {
100
+ const output = `
101
+ {"type":"system"}
102
+ INVALID_LINE
103
+ {"type":"result","result":"Success"}
104
+ `;
105
+ const result = parseClaudeOutput(output);
106
+ expect(result.message).toBe("Success");
107
+ });
108
+ });
package/src/parsers.ts CHANGED
@@ -11,6 +11,7 @@ export function parseCodexOutput(stdout: string): any {
11
11
  let lastMessage = null;
12
12
  let tokenCount = null;
13
13
  let threadId = null;
14
+ const tools: any[] = [];
14
15
 
15
16
  for (const line of lines) {
16
17
  if (line.trim()) {
@@ -26,6 +27,20 @@ export function parseCodexOutput(stdout: string): any {
26
27
  // Ignore reasoning-only items for message selection.
27
28
  } else if (parsed.msg?.type === 'token_count') {
28
29
  tokenCount = parsed.msg;
30
+ } else if (parsed.type === 'item.completed' && parsed.item?.type === 'mcp_tool_call') {
31
+ tools.push({
32
+ server: parsed.item.server,
33
+ tool: parsed.item.tool,
34
+ input: parsed.item.arguments, // Map arguments to input to match common patterns
35
+ output: parsed.item.result
36
+ });
37
+ } else if (parsed.type === 'item.completed' && parsed.item?.type === 'command_execution') {
38
+ tools.push({
39
+ tool: 'command_execution',
40
+ input: { command: parsed.item.command },
41
+ output: parsed.item.aggregated_output,
42
+ exit_code: parsed.item.exit_code
43
+ });
29
44
  }
30
45
  } catch (e) {
31
46
  // Skip invalid JSON lines
@@ -34,11 +49,12 @@ export function parseCodexOutput(stdout: string): any {
34
49
  }
35
50
  }
36
51
 
37
- if (lastMessage || tokenCount || threadId) {
52
+ if (lastMessage || tokenCount || threadId || tools.length > 0) {
38
53
  return {
39
54
  message: lastMessage,
40
55
  token_count: tokenCount,
41
- session_id: threadId
56
+ session_id: threadId,
57
+ tools: tools.length > 0 ? tools : undefined
42
58
  };
43
59
  }
44
60
  } catch (e) {
@@ -49,17 +65,93 @@ export function parseCodexOutput(stdout: string): any {
49
65
  }
50
66
 
51
67
  /**
52
- * Parse Claude JSON output
68
+ * Parse Claude Output (supports both JSON and stream-json/NDJSON)
53
69
  */
54
70
  export function parseClaudeOutput(stdout: string): any {
55
71
  if (!stdout) return null;
56
72
 
73
+ // First try parsing as a single JSON object (backward compatibility)
57
74
  try {
58
75
  return JSON.parse(stdout);
59
76
  } catch (e) {
60
- debugLog(`[Debug] Failed to parse Claude JSON output: ${e}`);
77
+ // If not valid single JSON, proceed to parse as NDJSON
78
+ }
79
+
80
+ try {
81
+ const lines = stdout.trim().split('\n');
82
+ let lastMessage = null;
83
+ let sessionId = null;
84
+ const toolsMap = new Map<string, any>(); // Map by tool_use id for matching results
85
+
86
+ for (const line of lines) {
87
+ if (!line.trim()) continue;
88
+
89
+ try {
90
+ const parsed = JSON.parse(line);
91
+
92
+ // Extract session ID from any message that has it
93
+ if (parsed.session_id) {
94
+ sessionId = parsed.session_id;
95
+ }
96
+
97
+ // Extract final result message
98
+ if (parsed.type === 'result' && parsed.result) {
99
+ lastMessage = parsed.result;
100
+ }
101
+
102
+ // Extract tool usage from assistant messages
103
+ if (parsed.type === 'assistant' && parsed.message?.content) {
104
+ for (const content of parsed.message.content) {
105
+ if (content.type === 'tool_use') {
106
+ toolsMap.set(content.id, {
107
+ tool: content.name,
108
+ input: content.input,
109
+ output: null // Will be filled when tool_result is found
110
+ });
111
+ }
112
+ }
113
+ }
114
+
115
+ // Match tool results from user messages
116
+ if (parsed.type === 'user' && parsed.message?.content) {
117
+ for (const content of parsed.message.content) {
118
+ if (content.type === 'tool_result' && content.tool_use_id) {
119
+ const tool = toolsMap.get(content.tool_use_id);
120
+ if (tool) {
121
+ // Extract text from content array
122
+ if (Array.isArray(content.content)) {
123
+ const textContent = content.content.find((c: any) => c.type === 'text');
124
+ tool.output = textContent?.text || null;
125
+ } else {
126
+ tool.output = content.content;
127
+ }
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ } catch (e) {
134
+ debugLog(`[Debug] Skipping invalid JSON line in Claude output: ${line}`);
135
+ }
136
+ }
137
+
138
+ // Convert Map to array
139
+ const tools = Array.from(toolsMap.values());
140
+
141
+ if (lastMessage || sessionId || tools.length > 0) {
142
+ return {
143
+ message: lastMessage, // This is the final result text
144
+ session_id: sessionId,
145
+ tools: tools.length > 0 ? tools : undefined
146
+ };
147
+ }
148
+
149
+ } catch (e) {
150
+ debugLog(`[Debug] Failed to parse Claude NDJSON output: ${e}`);
61
151
  return null;
62
152
  }
153
+
154
+ return null;
63
155
  }
64
156
 
65
157
  /**
package/src/server.ts CHANGED
@@ -429,6 +429,10 @@ export class ClaudeCodeServer {
429
429
  type: 'number',
430
430
  description: 'The process ID returned by run tool.',
431
431
  },
432
+ verbose: {
433
+ type: 'boolean',
434
+ description: 'Optional: If true, returns detailed execution information including tool usage history. Defaults to false.',
435
+ }
432
436
  },
433
437
  required: ['pid'],
434
438
  },
@@ -626,7 +630,7 @@ export class ClaudeCodeServer {
626
630
  } else {
627
631
  // Handle Claude (default)
628
632
  cliPath = this.claudeCliPath;
629
- processArgs = ['--dangerously-skip-permissions', '--output-format', 'json'];
633
+ processArgs = ['--dangerously-skip-permissions', '--output-format', 'stream-json', '--verbose'];
630
634
 
631
635
  // Add session_id if provided (Claude only)
632
636
  if (toolArguments.session_id && typeof toolArguments.session_id === 'string') {
@@ -740,7 +744,7 @@ export class ClaudeCodeServer {
740
744
  /**
741
745
  * Helper to get process result object
742
746
  */
743
- private getProcessResultHelper(pid: number): any {
747
+ private getProcessResultHelper(pid: number, verbose: boolean = false): any {
744
748
  const process = processManager.get(pid);
745
749
 
746
750
  if (!process) {
@@ -749,10 +753,12 @@ export class ClaudeCodeServer {
749
753
 
750
754
  // Parse output based on agent type
751
755
  let agentOutput: any = null;
752
- if (process.stdout) {
753
- if (process.toolType === 'codex') {
754
- agentOutput = parseCodexOutput(process.stdout);
755
- } else if (process.toolType === 'claude') {
756
+ if (process.toolType === 'codex') {
757
+ // Codex may output structured logs to stderr
758
+ const combinedOutput = (process.stdout || '') + '\n' + (process.stderr || '');
759
+ agentOutput = parseCodexOutput(combinedOutput);
760
+ } else if (process.stdout) {
761
+ if (process.toolType === 'claude') {
756
762
  agentOutput = parseClaudeOutput(process.stdout);
757
763
  } else if (process.toolType === 'gemini') {
758
764
  agentOutput = parseGeminiOutput(process.stdout);
@@ -773,7 +779,14 @@ export class ClaudeCodeServer {
773
779
 
774
780
  // If we have valid output from agent, include it
775
781
  if (agentOutput) {
776
- response.agentOutput = agentOutput;
782
+ // Filter out tools if not verbose
783
+ if (!verbose && agentOutput.tools) {
784
+ const { tools, ...rest } = agentOutput;
785
+ response.agentOutput = rest;
786
+ } else {
787
+ response.agentOutput = agentOutput;
788
+ }
789
+
777
790
  // Extract session_id if available
778
791
  if (agentOutput.session_id) {
779
792
  response.session_id = agentOutput.session_id;
@@ -796,7 +809,8 @@ export class ClaudeCodeServer {
796
809
  }
797
810
 
798
811
  const pid = toolArguments.pid;
799
- const response = this.getProcessResultHelper(pid);
812
+ const verbose = !!toolArguments.verbose;
813
+ const response = this.getProcessResultHelper(pid, verbose);
800
814
 
801
815
  return {
802
816
  content: [{
@@ -855,8 +869,8 @@ export class ClaudeCodeServer {
855
869
  throw new McpError(ErrorCode.InternalError, error.message);
856
870
  }
857
871
 
858
- // Collect results
859
- const results = pids.map(pid => this.getProcessResultHelper(pid));
872
+ // Collect results (verbose=false for wait)
873
+ const results = pids.map(pid => this.getProcessResultHelper(pid, false));
860
874
 
861
875
  return {
862
876
  content: [{