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

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. 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 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:

// 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:

// 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.

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.

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. The adapters perform simple response transformation, mapping the Elasticsearch-style hits to flat item lists with id and name properties.

MCP Server Adapter:

// 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:

// 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, with frontend adapters in 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 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 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. 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →