# Troubleshooting Tool Execution Errors and Timeouts in aisuite: A Complete Guide

> Troubleshoot aisuite tool execution errors and timeouts. Learn how MCPToolWrapper MCPClient and LocalExecutor handle errors for efficient problem solving.

- Repository: [Andrew Ng/aisuite](https://github.com/andrewyng/aisuite)
- Tags: how-to-guide
- Published: 2026-06-15

---

**aisuite handles tool execution through a layered architecture involving `MCPToolWrapper` for JSON-Schema translation, `MCPClient` for transport management, and `LocalExecutor` for shell command timeouts, with specific error handling paths for HTTP and stdio transports.**

aisuite is an open-source Python library that standardizes interactions with multiple AI providers and external tools through a unified interface. When integrating MCP (Model Context Protocol) servers or local shell commands, developers frequently encounter execution failures, connection errors, and timeout issues. Understanding the internal architecture of tool wrappers, client transports, and shell executors is essential for diagnosing these errors effectively.

## Understanding the Tool Execution Architecture

### Tool Wrappers and Schema Translation

The `aisuite.mcp.tool_wrapper.MCPToolWrapper` class creates Python callables that translate a tool's JSON-Schema into proper signatures, docstrings, and type annotations. When invoked, the wrapper forwards requests to the underlying MCP client via `self.mcp_client.call_tool`. This abstraction layer ensures that LLM-generated tool calls match the expected schema while providing clean Python interfaces for developers.

### MCP Client Transport Layer

The `aisuite.mcp.client.MCPClient` manages transport connections via stdio or HTTP protocols. The `call_tool` method at line 616 determines which transport to use and delegates to async helpers `_async_call_tool` or `_async_call_tool_http`. For HTTP transport, the client handles JSON-RPC request formatting and response parsing, while stdio transport manages subprocess communication with persistent MCP servers.

### Local Shell Execution Engine

For local shell tools, aisuite uses `platform.coworker.tools.shell.LocalExecutor`. The `run` method at line 166 spawns a persistent shell process, injects a completion marker (trailer), and enforces per-command timeouts. This executor maintains shell state across multiple commands, enabling operations that rely on environment variables or working directory persistence.

### Timeout Enforcement Mechanisms

Timeout handling differs by operating system:

- **POSIX systems**: After the deadline expires, the executor sends `SIGINT` to the foreground process via `_interrupt`. If the completion marker never arrives, the executor hard-closes the shell using the `close` method.
- **Windows**: The shell is killed outright because PowerShell lacks a reliable interrupt mechanism that preserves the REPL state.

Timeout values are bounded by `_DEFAULT_TIMEOUT` (120 seconds) and `_MAX_TIMEOUT` (600 seconds) defined at line 41 in [`platform/coworker/tools/shell.py`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/tools/shell.py). User-provided timeouts outside these bounds are automatically clamped.

## Common Failure Modes and Diagnostic Approaches

**Tool not found / "nonexistent_tool_xyz_123"**
- **Likely cause**: The tool name is missing from the MCP server's advertised list or filtered by the `allowed_tools` argument.
- **Where to look**: Check `MCPClient.list_tools` (cached after connection) and the `allowed_tools` parameter in `MCPClient.get_callable_tools`.

**`RuntimeError: MCP server error`**
- **Likely cause**: The server responded with a JSON-RPC error, typically indicating invalid arguments or server-side failures.
- **Where to look**: Inspect the error handling block in `_async_call_tool_http` at lines 88-96 in [`aisuite/mcp/client.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/client.py).

**`HTTP request to MCP server failed`**
- **Likely cause**: Network connectivity issues, incorrect URL configuration, or missing authentication headers.
- **Where to look**: Examine the exception handling in `_async_send_http_request` at lines 52-55 in [`client.py`](https://github.com/andrewyng/aisuite/blob/main/client.py).

**Shell command times out**
- **Likely cause**: Command execution exceeded the configured timeout limit.
- **Where to look**: Review the timeout handling logic in `LocalExecutor.run` at lines 38-62 in [`platform/coworker/tools/shell.py`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/tools/shell.py).

**Shell session becomes unusable after timeout**
- **Likely cause**: The shell was hard-closed due to unresponsive processes.
- **Where to look**: The next call automatically triggers respawn via `LocalExecutor._spawn` at lines 64-67.

**Background task never finishes**
- **Likely cause**: Tasks started with `run_in_background=True` remain detached without explicit cleanup.
- **Where to look**: Check `LocalExecutor.run_background` and `background_kill` method calls in your implementation.

## Step-by-Step Debugging Workflow

1. **Verify tool availability** before invocation:
   ```python
   from aisuite.mcp.client import MCPClient
   
   mcp = MCPClient.from_config(config)
   print("Available tools:", [t["name"] for t in mcp.list_tools()])
   ```

2. **Inspect HTTP traffic** when using HTTP transport by enabling `httpx` debug logging or running the mock client tests in [`tests/mcp/test_http_transport.py`](https://github.com/andrewyng/aisuite/blob/main/tests/mcp/test_http_transport.py).

3. **Check timeout boundaries** to ensure your values are within supported limits:
   ```python
   from platform.coworker.tools.shell import _DEFAULT_TIMEOUT, _MAX_TIMEOUT
   print(f"Default: {_DEFAULT_TIMEOUT}s, Max: {_MAX_TIMEOUT}s")
   ```

4. **Observe shell completion markers** in output logs. The trailer line format is `__COWORKER_DONE_<id>__ <exit_code> <cwd>`. Missing markers indicate commands hung beyond the grace period.

5. **Run integration tests** to isolate specific failure modes:
   - [`tests/mcp/test_http_transport.py`](https://github.com/andrewyng/aisuite/blob/main/tests/mcp/test_http_transport.py) for HTTP transport validation
   - [`tests/mcp/test_client.py`](https://github.com/andrewyng/aisuite/blob/main/tests/mcp/test_client.py) for stdio tool call verification
   - [`tests/platform/tests/test_shell.py`](https://github.com/andrewyng/aisuite/blob/main/tests/platform/tests/test_shell.py) for timeout recovery scenarios

## Practical Code Examples

### Handling Tool Execution Errors

Wrap tool calls to capture MCP server errors and transport failures:

```python
from aisuite.mcp.client import MCPClient

try:
    result = mcp.call_tool("read_file", {"path": "/nonexistent"})
except RuntimeError as err:
    # err contains the MCP error message from lines 88-96 in client.py

    print("Tool failed:", err)

```

### Configuring Command Timeouts

Adjust timeout values for long-running operations while respecting the 600-second maximum:

```python
from platform.coworker.tools.shell import LocalExecutor

# Initialize with 30-second default

executor = LocalExecutor(cwd=".", default_timeout=30)

# Override for specific long-running command (5 minutes)

out = executor.run("make && make test", timeout=300)
print(out["output"])

```

### Setting Up HTTP Transport with Authentication

Configure the MCP client for HTTP endpoints with custom headers and timeout overrides:

```python
from aisuite.mcp.client import MCPClient

config = {
    "type": "mcp",
    "name": "my-api",
    "server_url": "http://localhost:8000",
    "headers": {"Authorization": "Bearer abc123"},
    "timeout": 45.0,
}

client = MCPClient.from_config(config)
tools = client.get_callable_tools()

# Invoke first available tool

result = tools[0](arg="value")
print(result)

```

## Key Source Files Reference

- **[`aisuite/mcp/client.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/client.py)**: Core MCP client handling connection management, request/response parsing, and transport abstraction.
- **[`aisuite/mcp/tool_wrapper.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/tool_wrapper.py)**: Generates Python-callable wrappers around MCP tools with proper type signatures.
- **[`platform/coworker/tools/shell.py`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/tools/shell.py)**: Implements persistent shell execution, timeout enforcement, and process lifecycle management.
- **[`tests/mcp/test_http_transport.py`](https://github.com/andrewyng/aisuite/blob/main/tests/mcp/test_http_transport.py)**: Validates HTTP transport error handling and timeout configuration.
- **[`tests/platform/tests/test_shell.py`](https://github.com/andrewyng/aisuite/blob/main/tests/platform/tests/test_shell.py)**: Verifies timeout recovery mechanisms and shell respawn behavior.

## Summary

- **MCPToolWrapper** translates JSON-Schema definitions into Python callables that forward requests to `MCPClient.call_tool`.
- **MCPClient** manages stdio and HTTP transports, with error handling concentrated in `_async_call_tool_http` at lines 88-96.
- **LocalExecutor** enforces timeout limits between 120 and 600 seconds, using `SIGINT` on POSIX and process termination on Windows.
- **Error propagation** converts `subprocess.CalledProcessError`, `httpx.HTTPError`, and JSON-RPC errors into `RuntimeError` with descriptive messages.
- **Automatic recovery** occurs when shells become unresponsive, with `_spawn` creating new sessions automatically.

## Frequently Asked Questions

### Why does my shell command hang indefinitely despite setting a timeout?

The executor may have sent `SIGINT` (POSIX) or killed the process (Windows), but the completion marker `__COWORKER_DONE_<id>__` never arrived. Check if the command spawns child processes that ignore interrupt signals. On POSIX systems, the executor waits for the marker after sending `SIGINT`; if it never arrives, the shell closes hard. Review `LocalExecutor.run` at lines 38-62 in [`platform/coworker/tools/shell.py`](https://github.com/andrewyng/aisuite/blob/main/platform/coworker/tools/shell.py) for the specific timeout logic.

### How do I verify if an MCP tool is available before calling it?

Call `MCPClient.list_tools()` after establishing the connection. This method caches the server's advertised tool list. Compare your target tool name against this list, and verify it isn't filtered out by the `allowed_tools` parameter in `MCPClient.get_callable_tools`. The tool list is populated immediately after the client connects to the MCP server.

### What causes "MCP server error" RuntimeError exceptions?

This error originates in `_async_call_tool_http` at lines 88-96 in [`aisuite/mcp/client.py`](https://github.com/andrewyng/aisuite/blob/main/aisuite/mcp/client.py) when the MCP server returns a JSON-RPC error field. Common causes include invalid argument types, missing required parameters, or server-side execution failures. The error message includes the server's specific error description to help diagnose schema mismatches.

### How does aisuite handle timeout recovery on Windows versus Linux?

On POSIX systems (Linux/macOS), the executor attempts graceful shutdown using `SIGINT` before forcibly closing the shell. On Windows, PowerShell lacks reliable interrupt handling that preserves the REPL state, so the executor kills the shell process immediately. In both cases, the next command invocation automatically triggers `LocalExecutor._spawn` to create a fresh shell session, ensuring subsequent commands execute in a clean environment.