# Performance Implications of `mcp_server_search` versus `datasource_search` in Coco App

> Discover the performance differences between mcp_server_search and datasource_search in the Coco App. Understand caching and network latency impacts for optimal performance.

- Repository: [INFINI Labs/coco-app](https://github.com/infinilabs/coco-app)
- Tags: performance
- Published: 2026-03-04

---

**The primary performance difference stems from caching: `datasource_search` writes results to an in-memory `DATASOURCE_CACHE` (protected by `RwLock`) enabling faster subsequent reads, while `mcp_server_search` intentionally disables caching, incurring full network latency on every call but avoiding lock contention.**

The Coco App backend, implemented in the **infinilabs/coco-app** repository, provides two distinct search commands for retrieving server information. Understanding the performance implications of `mcp_server_search` versus `datasource_search` is critical for optimizing UI responsiveness and backend scalability. Both functions share identical network request patterns but diverge significantly in their caching strategies and concurrency characteristics.

## Backend Architecture and Request Flow

Both commands follow the same high-level workflow implemented in [`src-tauri/src/server/datasource.rs`](https://github.com/infinilabs/coco-app/blob/main/src-tauri/src/server/datasource.rs). They issue HTTP POST requests to their respective endpoints, validate responses using `status_code_check`, and parse JSON via `parse_search_results`.

- **`datasource_search`**: Posts to `/datasource/_search`
- **`mcp_server_search`**: Posts to `/mcp_server/_search`

The shared `HttpClient::post` implementation in [`src-tauri/src/server/http_client.rs`](https://github.com/infinilabs/coco-app/blob/main/src-tauri/src/server/http_client.rs) handles the actual network I/O for both commands, meaning initial request latency is identical until the post-processing phase.

## Caching Strategy: The Critical Performance Difference

The core performance divergence occurs after JSON parsing, where `datasource_search` implements a caching layer that `mcp_server_search` deliberately omits.

### `datasource_search`: In-Memory Cache Writes

After parsing results, this command calls `save_datasource_to_cache` to store the `Vec<DataSource>` in a global `DATASOURCE_CACHE` protected by an `RwLock`:

```rust
// src-tauri/src/server/datasource.rs (lines 90-111)
#[tauri::command]
pub async fn datasource_search(
    id: &str,
    query_params: Option<Vec<String>>,
) -> Result<Vec<DataSource>, String> {
    let resp = HttpClient::post(id, "/datasource/_search", query_params, None).await?;
    status_code_check(&resp, &[StatusCode::OK, StatusCode::CREATED])?;
    let datasources = parse_search_results(resp).await?;
    save_datasource_to_cache(&id, datasources.clone()); // ← cache write enabled
    Ok(datasources)
}

```

This adds a small overhead on the first call but enables **subsequent reads from memory** instead of issuing another network request, significantly reducing UI latency during repeated queries.

### `mcp_server_search`: Stateless Execution

The cache write line is intentionally commented out, forcing every request to hit the remote service:

```rust
// src-tauri/src/server/datasource.rs (lines 113-133)
#[tauri::command]
pub async fn mcp_server_search(
    id: &str,
    query_params: Option<Vec<String>>,
) -> Result<Vec<DataSource>, String> {
    let resp = HttpClient::post(id, "/mcp_server/_search", query_params, None).await?;
    status_code_check(&resp, &[StatusCode::OK, StatusCode::CREATED])?;
    let mcp_server = parse_search_results(resp).await?;
    // save_datasource_to_cache(&id, mcp_server.clone()); // ← intentionally disabled
    Ok(mcp_server)
}

```

This design ensures **always-fresh results** but means each search pays the full round-trip cost, making it suitable for ad-hoc user-triggered searches rather than frequent UI refresh cycles.

## Concurrency Characteristics and Lock Contention

The caching decision creates fundamentally different concurrency behaviors under high load.

### Write Lock Bottlenecks in `datasource_search`

The `DATASOURCE_CACHE.write()` lock can become a bottleneck if many concurrent data-source searches are performed. Multiple simultaneous calls must serialize on the write lock before the cache updates, potentially creating contention points in high-traffic scenarios.

### Lock-Free Scaling in `mcp_server_search`

Without cache writes, `mcp_server_search` takes no locks and remains fully asynchronous. This allows better horizontal scaling under high concurrency, though it may overload the remote service if called excessively since every request generates unique network traffic.

## Frontend Adapter Implementation

Both commands are exposed to the frontend through identical adapter patterns in [`src/utils/webAdapter.ts`](https://github.com/infinilabs/coco-app/blob/main/src/utils/webAdapter.ts). The adapters perform simple response transformation, mapping the Elasticsearch-style hits to flat item lists with `id` and `name` properties.

**MCP Server Adapter:**

```typescript
// src/utils/webAdapter.ts (lines 73-91)
async searchMCPServers(_serverId, queryParams) {
  const [error, res] = await Post(
    `/mcp_server/_search?${queryParams?.join("&")}`,
    undefined
  );
  if (error) { console.error("_search", error); return []; }
  return res?.hits?.hits?.map(item => ({
    ...item,
    id: item._source.id,
    name: item._source.name,
  })) ?? [];
}

```

**Data Source Adapter:**

```typescript
// src/utils/webAdapter.ts (lines 93-111)
async searchDataSources(_serverId, queryParams) {
  const [error, res] = await Post(
    `/datasource/_search?${queryParams?.join("&")}`,
    undefined
  );
  if (error) { console.error("_search", error); return []; }
  return res?.hits?.hits?.map(item => ({
    ...item,
    id: item._source.id,
    name: item._source.name,
  })) ?? [];
}

```

Since both adapters perform the same transformation logic, the **performance gap** is dictated entirely by the backend caching behavior described above.

## Performance Comparison Summary

| Aspect | `datasource_search` | `mcp_server_search` |
|--------|---------------------|---------------------|
| **First Call Cost** | Network I/O + JSON parsing + cache write overhead | Network I/O + JSON parsing only |
| **Repeated Calls** | Memory read (fast) | Full network round-trip (slow) |
| **Concurrency** | Subject to `RwLock` contention | Lock-free, better parallel scaling |
| **Data Freshness** | Potentially stale until cache refresh | Always fresh from source |
| **Typical Use Case** | Frequently accessed UI lists | Ad-hoc user-triggered queries |

## Summary

- **Initial request cost** is comparable for both functions, involving network I/O and JSON parsing through `HttpClient::post` and `parse_search_results`.
- **Subsequent reads** favor `datasource_search` due to in-memory caching via `DATASOURCE_CACHE`, while `mcp_server_search` consistently incurs network latency.
- **Concurrency scaling** favors `mcp_server_search` because it avoids the `RwLock` write contention present in `datasource_search`.
- **Data staleness** is a trade-off: cached data sources may become outdated unless refreshed via `refresh_all_datasources`, whereas MCP server results are always current.
- **Implementation location**: Both commands reside in [`src-tauri/src/server/datasource.rs`](https://github.com/infinilabs/coco-app/blob/main/src-tauri/src/server/datasource.rs), with frontend adapters in [`src/utils/webAdapter.ts`](https://github.com/infinilabs/coco-app/blob/main/src/utils/webAdapter.ts).

## Frequently Asked Questions

### Why does `mcp_server_search` disable caching?

The cache write line is intentionally commented out in [`src-tauri/src/server/datasource.rs`](https://github.com/infinilabs/coco-app/blob/main/src-tauri/src/server/datasource.rs) because MCP server searches are typically **ad-hoc operations** triggered by user input rather than repeated UI refresh cycles. Disabling caching ensures users always see current server states without requiring explicit cache invalidation logic, trading performance for data freshness.

### Can `datasource_search` cache cause memory pressure?

The global `DATASOURCE_CACHE` uses an `RwLock` for thread-safe access, but memory pressure depends on the size of the `Vec<DataSource>` being stored and the number of unique server IDs cached. The implementation in [`src-tauri/src/server/datasource.rs`](https://github.com/infinilabs/coco-app/blob/main/src-tauri/src/server/datasource.rs) does not show explicit cache eviction policies, suggesting potential growth under heavy usage patterns with many unique data sources.

### Which function should I use for real-time search interfaces?

For real-time interfaces requiring immediate feedback on every keystroke, **`mcp_server_search`** provides more predictable latency without lock contention, though you must account for consistent network overhead. For static lists that refresh periodically, **`datasource_search`** delivers superior performance after the initial population due to memory-resident results.

### How does lock contention manifest in high-concurrency scenarios?

When multiple `datasource_search` calls execute simultaneously, they serialize on the `DATASOURCE_CACHE.write()` lock in [`src-tauri/src/server/datasource.rs`](https://github.com/infinilabs/coco-app/blob/main/src-tauri/src/server/datasource.rs). This creates a bottleneck where threads queue to acquire the write lock before updating the cache, potentially degrading throughput under heavy parallel load compared to the lock-free `mcp_server_search` implementation.