# How 5ire Manages and Controls Concurrent Tool Executions in MCP Environments

> Discover how 5ire effectively manages concurrent tool executions in MCP environments using async-await, explicit flags, abort controllers, and mutex protection to prevent race conditions and resource exhaustion.

- Repository: [Ironben/5ire](https://github.com/nanbingxyz/5ire)
- Tags: internals
- Published: 2026-03-07

---

**5ire prevents race conditions and resource exhaustion by combining single-threaded async-await per connection, explicit parallel-tool-call flags, abort-controller cancellation, and mutex-protected database access.**

The open-source application 5ire (nanbingxyz/5ire) executes Model Context Protocol (MCP) tools on remote servers. To maintain stability when handling multiple tool requests, the codebase implements a deliberate concurrency control strategy that avoids uncontrolled parallelism. This article examines the specific mechanisms used to serialize tool executions, cancel stale requests, and protect shared resources.

## Single-Threaded Execution per MCP Connection

At the core of 5ire's concurrency model is the `MCPToolsManager.call()` method in [`src/main/services/mcp-tools-manager.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/services/mcp-tools-manager.ts). When a tool is invoked, the manager parses the tool URI, looks up the appropriate connection, and executes the call with a single `await`.

```typescript
// src/main/services/mcp-tools-manager.ts (line 223)
async call(options: MCPToolsManager.CallOptions) {
  const toolURI = this.#parseToolURI(options.uri);
  // …validation omitted for brevity…
  const result = await connection.client.callTool({
    name: toolURI.name,
    arguments: options.input,
  });
  // result handling …
}

```

Because `callTool` is awaited, the JavaScript event loop processes each request sequentially for that connection. No additional queue or worker pool is created, so the runtime naturally serializes concurrent calls targeting the same MCP server. This **single-threaded tool call per request** pattern ensures that a busy server cannot be overwhelmed by parallel invocations from a single client instance.

## Disabling Parallel Tool Calls at the Model Level

Even when a large language model generates multiple tool calls in a single response, 5ire forces sequential execution through a **model-level parallel-tool flag**. The chat payload type in [`src/intellichat/types.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/intellichat/types.ts) declares `parallel_tool_calls?: boolean`, which downstream services honor.

Individual model adapters explicitly disable automatic parallelism. In [`src/intellichat/services/AnthropicChatService.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/intellichat/services/AnthropicChatService.ts), the adapter sets `disable_parallel_tool_use: true`:

```typescript
// src/intellichat/services/AnthropicChatService.ts
const request = {
  model: model.name,
  messages: [{ role: "user", content: prompt }],
  // The Anthropic API does not support parallel tool usage
  disable_parallel_tool_use: true,
};

```

This flag propagates through the chat service stack, ensuring that the MCP layer receives tool calls one-by-one even when the LLM suggests parallel execution.

## Abort-Controlled Fetching for Pagination

When loading tool lists, 5ire prevents runaway parallel pagination requests using **AbortController** cancellation. The `MCPToolsManager.#fetchTools()` method creates a new controller for each fetch operation and aborts any pending previous request for the same connection.

```typescript
// src/main/services/mcp-tools-manager.ts
const abortController = new AbortController();
collection.abortController?.abort();               // Cancel previous fetch
const result = await connection.client.listTools(
  { cursor },
  { signal: abortController.signal }            // New request bound to controller
);

```

The same pattern appears in resource and prompt managers. By binding the fetch signal to a controller and explicitly aborting stale requests, the system guarantees that only the latest pagination fetch proceeds, eliminating race conditions in list operations.

## Mutex-Protected Database Access

Certain background services must update the SQLite database while other asynchronous work runs concurrently. The `DocumentEmbedder` class in [`src/main/services/document-embedder.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/services/document-embedder.ts) uses a lightweight **Mutex** from [`src/main/internal/mutex.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/internal/mutex.ts) to serialize critical sections.

```typescript
// src/main/services/document-embedder.ts
await this.#mutex.acquire();                       // Wait for exclusive lock
try {
  await this.#database.run(/* update pending document status */);
} finally {
  this.#mutex.release();                         // Release for next waiter
}

```

The `Mutex.create()` factory provides an async lock that prevents multiple embedder loops from writing to the database simultaneously. This mechanism protects shared resources without blocking the main event loop.

## Pagination Limits to Bound Concurrency

To prevent a single request from spawning an unbounded cascade of parallel fetches, 5ire enforces strict **pagination caps**. The codebase defines constants such as `MAX_TOOLS_PAGE = 10` and `MAX_RESOURCES_PAGE = 20` that limit how many pages can be fetched in a single list operation.

By bounding the pagination loop in `MCPToolsManager`, the system guarantees that resource exhaustion cannot occur through deep pagination chains, even when multiple servers are being queried concurrently.

## Summary

- **Single-await pattern**: `MCPToolsManager.call()` uses `await connection.client.callTool()` to serialize per-connection tool executions naturally through the JavaScript event loop.
- **Explicit disable flags**: Model adapters like `AnthropicChatService` set `disable_parallel_tool_use: true` to prevent LLM-generated parallel tool calls.
- **Request cancellation**: `AbortController` instances in `#fetchTools()` abort stale pagination requests before starting new ones.
- **Database safety**: The `Mutex` class serializes SQLite updates in services like `DocumentEmbedder` to prevent race conditions.
- **Bounded workloads**: Pagination constants (`MAX_TOOLS_PAGE`, `MAX_RESOURCES_PAGE`) limit the total number of concurrent fetch operations per request.

## Frequently Asked Questions

### Does 5ire support true parallel tool execution across different MCP servers?

Yes. While 5ire serializes calls targeting the *same* MCP connection using `await`, independent connections to different servers operate on separate async flows. The concurrency controls described here prevent per-server overload, not cross-server parallelism.

### What happens if a tool call hangs or times out?

The `AbortController` pattern used in pagination extends to tool invocations through the MCP client transport layer. When a connection is closed or a new request supersedes a pending one, the in-flight promise is rejected via the abort signal, allowing the event loop to reclaim resources.

### Why does 5ire disable parallel tool calls for Anthropic models?

According to the source code comments in [`AnthropicChatService.ts`](https://github.com/nanbingxyz/5ire/blob/main/AnthropicChatService.ts), the Anthropic API does not support parallel tool usage. By setting `disable_parallel_tool_use: true`, 5ire ensures compatibility and prevents the LLM client from generating requests that the remote server cannot handle concurrently.

### Is the Mutex implementation based on Node.js primitives?

The `Mutex` class in [`src/main/internal/mutex.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/internal/mutex.ts) is a lightweight custom implementation using standard JavaScript Promise and queue mechanisms, not Node.js-specific primitives like `Atomics` or `worker_threads`. This keeps the codebase portable across Electron main process environments while still providing exclusive access guarantees.