fix(mcp): hand a call's warnings to the agent

idx-core reports what it works around (history falling back to yahoo when
Stockbit refuses the session, a stale cache served after a failed fetch, a
movements query cut short) as warnings on stderr. An agent never reads
idx-mcp's stderr, so it saw `source: "yahoo"` or `stale: true` with no reason.

- Each tool call collects its warnings with runtime::on_warning and returns
  them as `warnings` on the answer or the error, when there are any.
- The stale-cache warning names the failure behind it.
- The mock's session API 403 is the real session API's block message; the
  public page's block names IDX_PROVIDER=msn as well as `--provider msn`.

Goldens: error-intraday-blocked (json, table) carry the reworded block hint.

Claude-Session: https://claude.ai/code/session_01RqPPVeVDrnLoP7NUZmLRxY
This commit is contained in:
Rasyidan Akbar F. 2026-11-04 19:26:10 +07:00
commit 9b46161566
10 changed files with 145 additions and 18 deletions

View file

@ -257,11 +257,7 @@ fn classify(reply: Reply, subject: &str) -> Result<String, IdxError> {
Err(IdxError::SymbolNotFound(subject.to_string()))
}
429 => Err(IdxError::RateLimited),
403 => Err(IdxError::UpstreamBlocked(
"exodus.stockbit.com refused this client (HTTP 403 from its CDN); idx does not \
retry a block. Try again later or from another network"
.to_string(),
)),
403 => Err(blocked()),
status => Err(IdxError::Http(format!(
"stockbit api: HTTP {status}{}",
message(&reply.body)
@ -497,6 +493,16 @@ fn trim_to_period(mut bars: Vec<Ohlc>, period: &Period) -> Vec<Ohlc> {
}
}
/// A 403 from the CDN in front of the session API: a block, not a refused
/// session, so nothing is retried.
pub(crate) fn blocked() -> IdxError {
IdxError::UpstreamBlocked(
"exodus.stockbit.com refused this client (HTTP 403 from its CDN); idx does not \
retry a block. Try again later or from another network"
.to_string(),
)
}
#[cfg(test)]
mod tests {
use super::*;