Introduce `McpCommand` enum and handlers in `owlen-cli` to manage MCP server registrations, including adding, listing, and removing servers across configuration scopes. Add scoped configuration support (`ScopedMcpServer`, `McpConfigScope`) and OAuth token handling in core config, alongside runtime refresh of MCP servers. Implement toast notifications in the TUI (`render_toasts`, `Toast`, `ToastLevel`) and integrate async handling for session events. Update config loading, validation, and schema versioning to accommodate new MCP scopes and resources. Add `httpmock` as a dev dependency for testing.
109 lines
3.3 KiB
Rust
109 lines
3.3 KiB
Rust
use std::sync::Arc;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{Error, Result, oauth::OAuthToken, storage::StorageManager};
|
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
pub struct ApiCredentials {
|
|
pub api_key: String,
|
|
pub endpoint: String,
|
|
}
|
|
|
|
pub const OLLAMA_CLOUD_CREDENTIAL_ID: &str = "provider_ollama_cloud";
|
|
|
|
pub struct CredentialManager {
|
|
storage: Arc<StorageManager>,
|
|
master_key: Arc<Vec<u8>>,
|
|
namespace: String,
|
|
}
|
|
|
|
impl CredentialManager {
|
|
pub fn new(storage: Arc<StorageManager>, master_key: Arc<Vec<u8>>) -> Self {
|
|
Self {
|
|
storage,
|
|
master_key,
|
|
namespace: "owlen".to_string(),
|
|
}
|
|
}
|
|
|
|
fn namespaced_key(&self, tool_name: &str) -> String {
|
|
format!("{}_{}", self.namespace, tool_name)
|
|
}
|
|
|
|
fn oauth_storage_key(&self, resource: &str) -> String {
|
|
self.namespaced_key(&format!("oauth_{resource}"))
|
|
}
|
|
|
|
pub async fn store_credentials(
|
|
&self,
|
|
tool_name: &str,
|
|
credentials: &ApiCredentials,
|
|
) -> Result<()> {
|
|
let key = self.namespaced_key(tool_name);
|
|
let payload = serde_json::to_vec(credentials).map_err(|e| {
|
|
Error::Storage(format!(
|
|
"Failed to serialize credentials for secure storage: {e}"
|
|
))
|
|
})?;
|
|
self.storage
|
|
.store_secure_item(&key, &payload, &self.master_key)
|
|
.await
|
|
}
|
|
|
|
pub async fn get_credentials(&self, tool_name: &str) -> Result<Option<ApiCredentials>> {
|
|
let key = self.namespaced_key(tool_name);
|
|
match self
|
|
.storage
|
|
.load_secure_item(&key, &self.master_key)
|
|
.await?
|
|
{
|
|
Some(bytes) => {
|
|
let creds = serde_json::from_slice(&bytes).map_err(|e| {
|
|
Error::Storage(format!("Failed to deserialize stored credentials: {e}"))
|
|
})?;
|
|
Ok(Some(creds))
|
|
}
|
|
None => Ok(None),
|
|
}
|
|
}
|
|
|
|
pub async fn delete_credentials(&self, tool_name: &str) -> Result<()> {
|
|
let key = self.namespaced_key(tool_name);
|
|
self.storage.delete_secure_item(&key).await
|
|
}
|
|
|
|
pub async fn store_oauth_token(&self, resource: &str, token: &OAuthToken) -> Result<()> {
|
|
let key = self.oauth_storage_key(resource);
|
|
let payload = serde_json::to_vec(token).map_err(|err| {
|
|
Error::Storage(format!(
|
|
"Failed to serialize OAuth token for secure storage: {err}"
|
|
))
|
|
})?;
|
|
self.storage
|
|
.store_secure_item(&key, &payload, &self.master_key)
|
|
.await
|
|
}
|
|
|
|
pub async fn load_oauth_token(&self, resource: &str) -> Result<Option<OAuthToken>> {
|
|
let key = self.oauth_storage_key(resource);
|
|
let raw = self
|
|
.storage
|
|
.load_secure_item(&key, &self.master_key)
|
|
.await?;
|
|
if let Some(bytes) = raw {
|
|
let token = serde_json::from_slice(&bytes).map_err(|err| {
|
|
Error::Storage(format!("Failed to deserialize stored OAuth token: {err}"))
|
|
})?;
|
|
Ok(Some(token))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub async fn delete_oauth_token(&self, resource: &str) -> Result<()> {
|
|
let key = self.oauth_storage_key(resource);
|
|
self.storage.delete_secure_item(&key).await
|
|
}
|
|
}
|