feat: reason-specific warnings for MCP servers that fail OAuth at startup

Distinguish why an OAuth MCP server was not started: never authenticated
(no stored credentials), stored token expired and refresh failed, or the
server rejected a token that looked valid. McpTokenStatus replaces the
Option<String> return of load_or_refresh_mcp_token, and McpAuthRequired
carries the reason across the error boundary via anyhow context.
This commit is contained in:
2026-08-14 11:07:56 -06:00
parent d31110cd67
commit d791098e51
4 changed files with 192 additions and 43 deletions
+11 -9
View File
@@ -1,6 +1,7 @@
use crate::mcp::oauth::McpTokenStatus;
use crate::mcp::{
ConnectedServer, JsonField, McpServer, McpTransportType, is_auth_required_error, oauth,
spawn_mcp_server,
ConnectedServer, JsonField, McpAuthReason, McpAuthRequired, McpServer, McpTransportType,
is_auth_required_error, oauth, spawn_mcp_server,
};
use anyhow::Result;
@@ -102,19 +103,20 @@ impl McpFactory {
return Ok(existing);
}
let bearer_token = if spec.is_remote() {
let token_status = if spec.is_remote() {
oauth::load_or_refresh_mcp_token(name).await
} else {
None
McpTokenStatus::NotAuthenticated
};
let handle = spawn_mcp_server(spec, log_path, bearer_token)
let auth_reason = McpAuthReason::from_token_status(&token_status);
let handle = spawn_mcp_server(spec, log_path, token_status.into_token())
.await
.map_err(|e| {
if is_auth_required_error(&e) {
e.context(format!(
"MCP server '{name}' requires OAuth authentication. \
Run `coyote --auth-mcp {name}` or `.mcp auth {name}` in the REPL to authenticate."
))
e.context(McpAuthRequired {
server: name.to_string(),
reason: auth_reason,
})
} else {
e
}
+8 -7
View File
@@ -22,7 +22,7 @@ use crate::function::{
};
use crate::mcp::{
MCP_DESCRIBE_META_FUNCTION_NAME_PREFIX, MCP_INVOKE_META_FUNCTION_NAME_PREFIX,
MCP_SEARCH_META_FUNCTION_NAME_PREFIX, is_auth_required_error,
MCP_SEARCH_META_FUNCTION_NAME_PREFIX, McpAuthReason, McpAuthRequired, is_auth_required_error,
};
use crate::rag::Rag;
use crate::supervisor::Supervisor;
@@ -3427,7 +3427,11 @@ impl RequestContext {
{
Ok(handle) => handles.push((id.clone(), handle)),
Err(e) if is_auth_required_error(&e) => {
auth_required.push(id.clone())
let reason = e
.downcast_ref::<McpAuthRequired>()
.map(|a| a.reason)
.unwrap_or(McpAuthReason::NotAuthenticated);
auth_required.push((id.clone(), reason));
}
Err(e) => return Err(e),
}
@@ -3444,11 +3448,8 @@ impl RequestContext {
for (id, handle) in handles {
mcp_runtime.insert(id, handle);
}
for id in auth_required {
eprintln!(
"Warning: MCP server '{id}' requires OAuth authentication and was not started. \
Run `.mcp auth {id}` (or `coyote --auth-mcp {id}`) to authenticate and attach it."
);
for (id, reason) in auth_required {
eprintln!("Warning: {}", McpAuthRequired { server: id, reason });
}
}
}