Restrict MCP runtime auth to the NEAR AI companion

This commit is contained in:
Coffee
2026-03-19 12:58:45 +08:00
parent 386ed298c1
commit 6f687aabd2
2 changed files with 42 additions and 3 deletions
+2 -2
View File
@@ -11,7 +11,6 @@ use clap::{Args, Subcommand};
use crate::config::{Config, LlmConfig};
use crate::db::Database;
use crate::secrets::SecretsStore;
use crate::settings::Settings;
use crate::tools::mcp::{
McpClient, McpProcessManager, McpServerConfig, McpSessionManager, OAuthConfig,
auth::{authorize_mcp_server, is_authenticated},
@@ -728,7 +727,8 @@ async fn get_secrets_store() -> anyhow::Result<Arc<dyn SecretsStore + Send + Syn
}
fn resolve_llm_from_env() -> Result<LlmConfig, crate::error::ConfigError> {
LlmConfig::resolve(&Settings::default())
let settings = crate::config::load_bootstrap_settings(None)?;
LlmConfig::resolve(&settings)
}
#[cfg(test)]
+40 -1
View File
@@ -55,7 +55,10 @@ pub struct McpServerConfig {
///
/// This is used for companion MCP servers that should reuse an existing
/// provider identity instead of running their own MCP OAuth flow.
#[serde(default, skip_serializing_if = "Option::is_none")]
///
/// Security: this field is runtime-only. Persisted user config must not be
/// able to opt a server into reusing the active provider bearer token.
#[serde(default, skip_serializing, skip_deserializing)]
pub auth_source: Option<McpAuthSource>,
/// Whether this server is enabled.
@@ -178,6 +181,15 @@ impl McpServerConfig {
});
}
if self.uses_runtime_auth_source() && !is_nearai_companion_server_name(&self.name) {
return Err(ConfigError::InvalidConfig {
reason: format!(
"Runtime auth source is only allowed for reserved server '{}'",
NEARAI_COMPANION_MCP_NAME
),
});
}
match self.effective_transport() {
EffectiveTransport::Http => {
if self.url.is_empty() {
@@ -872,6 +884,19 @@ mod tests {
);
}
#[test]
fn test_deserialize_ignores_persisted_auth_source() {
let raw = serde_json::json!({
"name": "user-managed",
"url": "https://mcp.example.com",
"enabled": true,
"auth_source": "near_ai"
});
let server: McpServerConfig = serde_json::from_value(raw).expect("server");
assert_eq!(server.auth_source, None);
}
#[cfg(feature = "libsql")]
#[test]
fn test_derive_nearai_companion_mcp_server_strips_trailing_v1() {
@@ -934,6 +959,20 @@ mod tests {
assert!(config.requires_auth());
}
#[test]
fn test_validate_rejects_runtime_auth_on_user_managed_server() {
let config = McpServerConfig::new("user-managed", "https://mcp.example.com")
.with_auth_source(McpAuthSource::NearAi);
let err = config
.validate()
.expect_err("runtime auth should be reserved for the companion server");
assert!(
err.to_string().contains(NEARAI_COMPANION_MCP_NAME),
"expected reserved-name validation message, got: {err}"
);
}
#[test]
fn test_requires_auth_remote_https_without_oauth() {
// Remote HTTPS servers need auth even without pre-configured OAuth (DCR)