Hmbown/CodeWhale · error

MCP consent history exceeds size limit

Error message

MCP consent history exceeds size limit

What it means

MCP consent decisions are persisted into the MCP config JSON, which shares the 1 MiB size cap (`MAX_MCP_CONFIG_BYTES`). After recording decisions and computing a JSON delta against the previous store, `persist_decisions` re-serializes the file and bails if the rendered output would exceed the limit, preventing unbounded consent-history growth.

Solutions

  1. Trim the existing config: remove stale servers and obsolete consent history entries before re-running the import
  2. Reduce the size of the import batch (fewer, smaller candidate lists)
  3. Start from a fresh/minimal mcp.json if the consent history has accumulated beyond usefulness
  4. Compact env/arg data in the shared file so consent history has room

Example fix

// before
manual: keep importing batches into a 950 KB mcp.json
// after
prune unused servers first so rendered size stays under 1 MiB, then re-run apply_reviewed_import
Defensive patterns

Strategy: validation

Validate before calling

const MAX_MCP_CONFIG_BYTES: u64 = 1_048_576;
let size = std::fs::metadata(path)?.len();
if size + estimated_new_decisions > MAX_MCP_CONFIG_BYTES {
    eprintln!("prune consent history before importing");
}

Try / catch

match persist_decisions(&path, &candidates, &decisions, now) {
    Ok(()) => {},
    Err(e) if e.to_string().contains("size limit") => {
        prune_stale_decisions(&path)?;
        persist_decisions(&path, &candidates, &decisions, now)?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling `persist_decisions` (via `apply_reviewed_import`) when the resulting pretty-printed consent/config JSON exceeds 1,048,576 bytes (crates/tui/src/mcp/external_import.rs:313) — typically after recording many decisions on an already-large config.

Common situations: Importing plugins with very long server name/URL lists repeatedly, accumulating decision records; a consent store already near the cap plus one large import; long-lived config where every import appends history.

Understand the failure class

Background: "File too large" / "file size exceeds limit" errors: why libraries cap file sizes and how to fix them — this error's family across 46 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/c09acba7d5a4aeef. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/mcp/external_import.rs:313

        let mut raw: Value = match original.as_deref() {
            Some(raw) => serde_json::from_str(raw)
                .map_err(|_| anyhow::anyhow!("Invalid MCP consent history; contents omitted"))?,
            None => serde_json::json!({}),
        };
        anyhow::ensure!(raw.is_object(), "MCP consent history must be an object");
        let mut store: ImportConsentStore = if original.is_none() {
            ImportConsentStore::default()
        } else {
            serde_json::from_value(raw.clone())
                .map_err(|_| anyhow::anyhow!("Invalid MCP consent history; contents omitted"))?
        };
        let before = serde_json::to_value(&store)?;
        record_decisions(&mut store, candidates, decisions, now_unix);
        let after = serde_json::to_value(&store)?;
        super::apply_json_delta(&mut raw, &before, &after);
        let rendered = serde_json::to_vec_pretty(&raw)?;
        if rendered.len() as u64 > super::MAX_MCP_CONFIG_BYTES {
            anyhow::bail!("MCP consent history exceeds size limit");
        }
        crate::utils::write_atomic(path, &rendered)?;
        Ok(())
    })
}

/// Filter candidates that still need a user decision for this content hash.
#[allow(dead_code)] // used by future selector UI + unit tests
pub fn candidates_needing_consent(
    candidates: &[ImportCandidate],
    store: &ImportConsentStore,
) -> Vec<ImportCandidate> {
    candidates
        .iter()
        .filter(|c| {
            let key = consent_key(&c.source_path, &c.content_hash);
            match store.entries.get(&key) {
                Some(entry) if entry.decision == ImportDecision::Decline => false,

View on GitHub (pinned to 73e0f67d83)