Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions scripts/core-boundaries/rules/crate-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ export const noCoreDependencyCrates = [
];

export const forbiddenManifestDependencyRules = [
{
dependencyNames: ['rmcp'],
scanRoots: ['src/apps', 'src/crates', 'BitFun-Installer/src-tauri'],
workspaceManifestPath: 'Cargo.toml',
forbidWorkspaceAliases: false,
allowManifestPaths: [
'src/crates/services/services-integrations/Cargo.toml',
],
reason: 'the RMCP SDK is a concrete MCP integration service dependency',
message: 'rmcp must stay in services-integrations and be consumed through its MCP owner facade',
},
{
dependencyNames: ['bitfun-agent-runtime-ipc'],
scanRoots: ['src/apps', 'src/crates', 'BitFun-Installer/src-tauri'],
Expand Down
1 change: 0 additions & 1 deletion scripts/core-boundaries/rules/feature-rules.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ export const optionalDependencyFeatureOwnerRules = [
{ depName: 'indexmap', ownerFeatures: ['product-full'] },
{ depName: 'md5', ownerFeatures: ['product-full'] },
{ depName: 'reqwest', ownerFeatures: ['ai-adapter-runtime', 'product-full'] },
{ depName: 'rmcp', ownerFeatures: ['product-full'] },
{ depName: 'rusqlite', ownerFeatures: ['product-full'] },
{ depName: 'serde_yaml', ownerFeatures: ['workspace-runtime'] },
{ depName: 'similar', ownerFeatures: ['product-full'] },
Expand Down
11 changes: 10 additions & 1 deletion scripts/core-boundaries/self-test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@ export function runManifestParserSelfTest({
'qrcode',
'rand',
'readability-js',
'rmcp',
'russh',
'rustls',
'rustls-native-certs',
Expand All @@ -779,7 +780,7 @@ export function runManifestParserSelfTest({
throw new Error(`core optional dependency owner rule must cover forbidden dependency ${dep}`);
}
}
for (const dep of ['rmcp', 'image', 'tool-runtime']) {
for (const dep of ['image', 'tool-runtime']) {
if (!coreOptionalOwnerDeps.has(dep)) {
throw new Error(`core optional dependency owner rule must cover ${dep}`);
}
Expand Down Expand Up @@ -1530,6 +1531,14 @@ export function runManifestParserSelfTest({
)) {
throw new Error('speech engine manifest guard must allow only its integration service owner');
}
const rmcpManifestRule = forbiddenManifestDependencyRules.find((rule) =>
rule.dependencyNames?.includes('rmcp'),
);
if (!rmcpManifestRule?.allowManifestPaths?.includes(
'src/crates/services/services-integrations/Cargo.toml',
)) {
throw new Error('RMCP manifest guard must allow only its integration service owner');
}
const coreSpeechOwnerRule = forbiddenContentUnderRules.find(
(rule) => rule.path === 'src/crates/assembly/core/src/service',
);
Expand Down
8 changes: 4 additions & 4 deletions src/apps/desktop/src/api/mcp_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

use crate::api::app_state::AppState;
use crate::startup_trace::DesktopStartupTrace;
use bitfun_core::service::mcp::auth::{
has_stored_oauth_credentials, MCPRemoteOAuthSessionSnapshot,
};
use bitfun_core::service::mcp::auth::MCPRemoteOAuthSessionSnapshot;
use bitfun_core::service::mcp::config::MCPConfigService;
use bitfun_core::service::mcp::protocol::{
MCPPrompt, MCPResource, PromptsGetResult, ResourcesReadResult,
Expand Down Expand Up @@ -203,6 +201,7 @@ pub async fn get_mcp_servers(state: State<'_, AppState>) -> Result<Vec<MCPServer

let mut infos = Vec::new();
let runtime_manager = RuntimeManager::new().ok();
let mcp_server_manager = mcp_service.server_manager();

for config in configs {
let transport = config.resolved_transport();
Expand All @@ -214,7 +213,8 @@ pub async fn get_mcp_servers(state: State<'_, AppState>) -> Result<Vec<MCPServer
let oauth_enabled =
matches!(config.server_type, MCPServerType::Remote) && config.remote_oauth_enabled();
let oauth_auth_configured = if oauth_enabled {
has_stored_oauth_credentials(&config.id)
mcp_server_manager
.has_remote_oauth_credentials(&config.id)
.await
.unwrap_or(false)
} else {
Expand Down
5 changes: 0 additions & 5 deletions src/crates/assembly/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ include_dir = { workspace = true, optional = true }
similar = { workspace = true, optional = true }
urlencoding = { workspace = true }

# MCP Streamable HTTP client (official rust-sdk)
rmcp = { workspace = true, features = [
"transport-streamable-http-client-reqwest",
], optional = true }
# Shared AI protocol adapters
bitfun-ai-adapters = { path = "../../adapters/ai-adapters", optional = true }

Expand Down Expand Up @@ -156,7 +152,6 @@ product-full = [
"dep:md5",
"dep:reqwest",
"dep:rusqlite",
"dep:rmcp",
"dep:similar",
"dep:tokio-tungstenite",
"dep:tower-http",
Expand Down
25 changes: 12 additions & 13 deletions src/crates/assembly/core/src/service/mcp/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
//! module keeps the legacy core entrypoints and injects the product data directory.

use async_trait::async_trait;
use rmcp::transport::auth::{AuthorizationManager, CredentialStore, StoredCredentials};
use std::path::PathBuf;

use crate::infrastructure::try_get_path_manager_arc;
use crate::service::mcp::server::MCPServerConfig;
use crate::util::errors::{BitFunError, BitFunResult};
use bitfun_services_integrations::mcp::auth::rmcp_compat::{
AuthError, AuthorizationManager, CredentialStore, StoredCredentials,
};

pub use bitfun_services_integrations::mcp::auth::{
MCPRemoteOAuthSessionSnapshot, MCPRemoteOAuthStatus, PreparedMCPRemoteOAuthAuthorization,
Expand Down Expand Up @@ -79,31 +81,28 @@ impl MCPRemoteOAuthCredentialStore {
#[allow(deprecated)]
#[async_trait]
impl CredentialStore for MCPRemoteOAuthCredentialStore {
async fn load(&self) -> Result<Option<StoredCredentials>, rmcp::transport::auth::AuthError> {
async fn load(&self) -> Result<Option<StoredCredentials>, AuthError> {
MCPRemoteOAuthCredentialVault::new()
.map_err(|error| rmcp::transport::auth::AuthError::InternalError(error.to_string()))?
.map_err(|error| AuthError::InternalError(error.to_string()))?
.load(&self.server_id)
.await
.map_err(|error| rmcp::transport::auth::AuthError::InternalError(error.to_string()))
.map_err(|error| AuthError::InternalError(error.to_string()))
}

async fn save(
&self,
credentials: StoredCredentials,
) -> Result<(), rmcp::transport::auth::AuthError> {
async fn save(&self, credentials: StoredCredentials) -> Result<(), AuthError> {
MCPRemoteOAuthCredentialVault::new()
.map_err(|error| rmcp::transport::auth::AuthError::InternalError(error.to_string()))?
.map_err(|error| AuthError::InternalError(error.to_string()))?
.store(&self.server_id, &credentials)
.await
.map_err(|error| rmcp::transport::auth::AuthError::InternalError(error.to_string()))
.map_err(|error| AuthError::InternalError(error.to_string()))
}

async fn clear(&self) -> Result<(), rmcp::transport::auth::AuthError> {
async fn clear(&self) -> Result<(), AuthError> {
MCPRemoteOAuthCredentialVault::new()
.map_err(|error| rmcp::transport::auth::AuthError::InternalError(error.to_string()))?
.map_err(|error| AuthError::InternalError(error.to_string()))?
.clear(&self.server_id)
.await
.map_err(|error| rmcp::transport::auth::AuthError::InternalError(error.to_string()))
.map_err(|error| AuthError::InternalError(error.to_string()))
}
}

Expand Down
10 changes: 9 additions & 1 deletion src/crates/assembly/core/src/service/mcp/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,15 @@ impl MCPService {
config_service: Arc<crate::service::config::ConfigService>,
) -> crate::util::errors::BitFunResult<Self> {
let mcp_config_service = Arc::new(MCPConfigService::new(config_service)?);
let server_manager = Arc::new(MCPServerManager::new(mcp_config_service.clone()));
// Keep service startup compatible when strict path initialization is
// unavailable; OAuth operations retain the existing lazy error path.
let oauth_data_dir = crate::infrastructure::try_get_path_manager_arc()
.ok()
.map(|manager| manager.user_data_dir());
let server_manager = Arc::new(MCPServerManager::assemble(
mcp_config_service.clone(),
oauth_data_dir,
));
let context_provider = Arc::new(MCPContextProvider::new(server_manager.clone()));

Ok(Self {
Expand Down
Loading