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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user