Skip to content

Commit e23efdb

Browse files
committed
feat(builtin-servers): per-Space built-in MCP server framework
Generalize the self-management mcpmux_* tools into a framework of McpMux-bundled "built-in servers" whose enablement and per-tool toggles are scoped PER SPACE (not per workspace root). Tool Optimization (mcpmux_*) is the first concrete server; Memory/Skills/Plugins slot into the same shell later. core: BuiltinServerDescriptor/builtin_servers() single source of truth for built-in ids + tool sets; SpaceBuiltinConfigRepository trait; replace the global MetaToolsEnabledChanged event with per-Space BuiltinServerConfigChanged. storage: migration 016 (space_builtin_servers / space_builtin_tools; only deviations from default stored; preserves a prior global "off" then drops the global key) + SqliteSpaceBuiltinConfigRepository. gateway: registry is space-aware (list_as_tools_for_space / is_tool_enabled_for_space / is_server_enabled_for_space); handler advertises & gates mcpmux_* per resolved Space; notifier fans out list_changed per Space. Removed the global is_enabled()/META_TOOLS_ENABLED_KEY switch. tauri: list_builtin_servers / set_builtin_server_enabled / set_builtin_tool_enabled per Space; dropped global get/set_meta_tools_enabled; AppState + deps wired. ui: Built-in Servers page is per-Space (server toggle + per-tool toggles, live-syncs on the Space config event) + new builtinServers API. tests: per_space_config_controls_registry_visibility (enable/disable + per-tool filtering); e2e meta-tools spec moved to the per-Space commands. Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent 7eba623 commit e23efdb

25 files changed

Lines changed: 891 additions & 372 deletions

File tree

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//! Tauri commands for the per-Space built-in server config.
2+
//!
3+
//! Built-in servers (today: "Tool Optimization", the `mcpmux_*` tools) and
4+
//! their individual tools are enabled/disabled **per Space**. The descriptors
5+
//! (ids, names, tool sets) come from `mcpmux_core::builtin_servers()`; the
6+
//! per-Space enable state comes from `SpaceBuiltinConfigRepository`. Toggling
7+
//! emits `BuiltinServerConfigChanged` so the gateway re-pushes
8+
//! `tools/list_changed` to that Space's connected clients.
9+
10+
use std::sync::Arc;
11+
12+
use mcpmux_core::DomainEvent;
13+
use serde::Serialize;
14+
use tauri::State;
15+
use tokio::sync::RwLock;
16+
use uuid::Uuid;
17+
18+
use super::gateway::GatewayAppState;
19+
use crate::state::AppState;
20+
21+
/// One tool of a built-in server, with its per-Space enabled state.
22+
#[derive(Debug, Clone, Serialize)]
23+
pub struct BuiltinToolDto {
24+
pub name: String,
25+
pub description: String,
26+
/// Mutating tool — gated behind a native approval dialog at call time.
27+
pub write: bool,
28+
pub enabled: bool,
29+
}
30+
31+
/// A built-in server as configured for a specific Space.
32+
#[derive(Debug, Clone, Serialize)]
33+
pub struct BuiltinServerDto {
34+
pub id: String,
35+
pub name: String,
36+
pub description: String,
37+
/// Whether this built-in server is enabled for the Space.
38+
pub enabled: bool,
39+
pub tools: Vec<BuiltinToolDto>,
40+
}
41+
42+
/// Publish `BuiltinServerConfigChanged` so MCPNotifier re-pushes
43+
/// `tools/list_changed` to the Space's peers. Best-effort: gateway not running
44+
/// (no subscribers) is a normal startup condition and must not fail the toggle.
45+
async fn emit_builtin_changed(gateway_state: &Arc<RwLock<GatewayAppState>>, space_id: Uuid) {
46+
let gw_state = gateway_state.read().await;
47+
if let Some(ref gw) = gw_state.gateway_state {
48+
gw.read()
49+
.await
50+
.emit_domain_event(DomainEvent::BuiltinServerConfigChanged { space_id });
51+
}
52+
}
53+
54+
/// List every built-in server with its per-Space enable state and per-tool
55+
/// toggles. Combines the static descriptors with the Space's stored overrides
56+
/// (absence of an override = the descriptor default / tool-on).
57+
#[tauri::command]
58+
pub async fn list_builtin_servers(
59+
space_id: String,
60+
state: State<'_, AppState>,
61+
) -> Result<Vec<BuiltinServerDto>, String> {
62+
let repo = &state.space_builtin_config_repository;
63+
let mut out = Vec::new();
64+
for d in mcpmux_core::builtin_servers() {
65+
let enabled = repo
66+
.server_enabled_override(&space_id, d.id)
67+
.await
68+
.map_err(|e| e.to_string())?
69+
.unwrap_or(d.default_enabled);
70+
let disabled = repo
71+
.disabled_tools(&space_id, d.id)
72+
.await
73+
.map_err(|e| e.to_string())?;
74+
let tools = d
75+
.tools
76+
.iter()
77+
.map(|t| BuiltinToolDto {
78+
name: t.name.to_string(),
79+
description: t.description.to_string(),
80+
write: t.write,
81+
enabled: !disabled.iter().any(|n| n == t.name),
82+
})
83+
.collect();
84+
out.push(BuiltinServerDto {
85+
id: d.id.to_string(),
86+
name: d.name.to_string(),
87+
description: d.description.to_string(),
88+
enabled,
89+
tools,
90+
});
91+
}
92+
Ok(out)
93+
}
94+
95+
/// Enable/disable a built-in server for a Space.
96+
#[tauri::command]
97+
pub async fn set_builtin_server_enabled(
98+
space_id: String,
99+
server_id: String,
100+
enabled: bool,
101+
state: State<'_, AppState>,
102+
gateway_state: State<'_, Arc<RwLock<GatewayAppState>>>,
103+
) -> Result<(), String> {
104+
let sid = Uuid::parse_str(&space_id).map_err(|e| format!("bad space_id: {e}"))?;
105+
state
106+
.space_builtin_config_repository
107+
.set_server_enabled(&space_id, &server_id, enabled)
108+
.await
109+
.map_err(|e| e.to_string())?;
110+
emit_builtin_changed(gateway_state.inner(), sid).await;
111+
Ok(())
112+
}
113+
114+
/// Enable/disable a single tool of a built-in server for a Space.
115+
#[tauri::command]
116+
pub async fn set_builtin_tool_enabled(
117+
space_id: String,
118+
server_id: String,
119+
tool_name: String,
120+
enabled: bool,
121+
state: State<'_, AppState>,
122+
gateway_state: State<'_, Arc<RwLock<GatewayAppState>>>,
123+
) -> Result<(), String> {
124+
let sid = Uuid::parse_str(&space_id).map_err(|e| format!("bad space_id: {e}"))?;
125+
state
126+
.space_builtin_config_repository
127+
.set_tool_enabled(&space_id, &server_id, &tool_name, enabled)
128+
.await
129+
.map_err(|e| e.to_string())?;
130+
emit_builtin_changed(gateway_state.inner(), sid).await;
131+
Ok(())
132+
}

apps/desktop/src-tauri/src/commands/gateway.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -700,13 +700,13 @@ fn map_domain_event_to_ui(event: &DomainEvent) -> (&'static str, serde_json::Val
700700
}),
701701
),
702702

703-
// The global Tool Optimization (mcpmux_*) master switch flipped. The
704-
// gateway-side MCPNotifier handles the `tools/list_changed` push to
705-
// connected MCP clients; this forwards the new value to the desktop UI
706-
// so any open Built-in Servers / Settings view reflects it live.
707-
DomainEvent::MetaToolsEnabledChanged { enabled } => (
708-
"meta-tools-changed",
709-
serde_json::json!({ "enabled": enabled }),
703+
// A Space's built-in-server config changed. The gateway-side
704+
// MCPNotifier handles the `tools/list_changed` push to that Space's
705+
// MCP clients; this forwards it to the desktop UI so an open Built-in
706+
// Servers view for that Space reflects the change live.
707+
DomainEvent::BuiltinServerConfigChanged { space_id } => (
708+
"builtin-server-config-changed",
709+
serde_json::json!({ "space_id": space_id }),
710710
),
711711
}
712712
}

apps/desktop/src-tauri/src/commands/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//! This module contains all commands that can be invoked from the frontend.
44
//! Commands are organized by feature area.
55
6+
pub mod builtin_servers;
67
pub mod client;
78
pub mod client_install;
89
pub mod config_export;
@@ -22,6 +23,7 @@ pub mod space;
2223
pub mod workspace_binding;
2324

2425
// Re-export commands for convenience
26+
pub use builtin_servers::*;
2527
pub use client::*;
2628
pub use client_install::*;
2729
pub use config_export::*;

apps/desktop/src-tauri/src/commands/settings.rs

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
//! Settings commands for auto-start and system tray behavior
22
3-
use std::sync::Arc;
4-
5-
use mcpmux_core::DomainEvent;
63
use serde::{Deserialize, Serialize};
74
use tauri::State;
85
use tauri_plugin_autostart::AutoLaunchManager;
9-
use tokio::sync::RwLock;
106
use tracing::{debug, info};
117

12-
use super::gateway::GatewayAppState;
138
use crate::state::AppState;
149

1510
/// Startup and system tray settings
@@ -133,57 +128,10 @@ pub fn should_start_hidden() -> bool {
133128
args.contains(&"--hidden".to_string())
134129
}
135130

136-
/// Get the current value of the meta-tools master switch.
137-
///
138-
/// When disabled, the gateway hides the entire `mcpmux_*` namespace from
139-
/// connected MCP clients — no introspection, no self-management. Default
140-
/// ON.
141-
#[tauri::command]
142-
pub async fn get_meta_tools_enabled(app_state: State<'_, AppState>) -> Result<bool, String> {
143-
match app_state
144-
.settings_repository
145-
.get("gateway.meta_tools_enabled")
146-
.await
147-
{
148-
Ok(Some(v)) => Ok(!matches!(v.as_str(), "false" | "0")),
149-
_ => Ok(true),
150-
}
151-
}
152-
153-
/// Flip the meta-tools master switch. The change takes effect on the NEXT
154-
/// `list_tools` / `call_tool` from any connected client — existing cached
155-
/// tool lists are invalidated by the usual `tools/list_changed` push.
156-
#[tauri::command]
157-
pub async fn set_meta_tools_enabled(
158-
enabled: bool,
159-
app_state: State<'_, AppState>,
160-
gateway_state: State<'_, Arc<RwLock<GatewayAppState>>>,
161-
) -> Result<(), String> {
162-
app_state
163-
.settings_repository
164-
.set(
165-
"gateway.meta_tools_enabled",
166-
if enabled { "true" } else { "false" },
167-
)
168-
.await
169-
.map_err(|e| format!("Failed to save meta_tools_enabled: {}", e))?;
170-
info!("[Settings] meta_tools_enabled = {}", enabled);
171-
172-
// Push tools/list_changed to every connected session so the mcpmux_*
173-
// namespace appears / disappears immediately instead of on their next
174-
// list_tools. Best-effort: the gateway not running (no subscribers) is a
175-
// normal condition and must not fail the toggle.
176-
{
177-
let gw_state = gateway_state.read().await;
178-
if let Some(ref gw) = gw_state.gateway_state {
179-
gw.read()
180-
.await
181-
.emit_domain_event(DomainEvent::MetaToolsEnabledChanged { enabled });
182-
}
183-
}
184-
185-
Ok(())
186-
}
131+
// The meta-tools master switch moved out of global app-settings into per-Space
132+
// built-in-server config — see `commands::builtin_servers`
133+
// (`list_builtin_servers` / `set_builtin_server_enabled` /
134+
// `set_builtin_tool_enabled`).
187135

188136
#[cfg(test)]
189137
mod tests {

apps/desktop/src-tauri/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -910,8 +910,10 @@ pub fn run() {
910910
commands::respond_to_meta_tool_approval,
911911
commands::list_meta_tool_grants,
912912
commands::revoke_meta_tool_grant,
913-
commands::get_meta_tools_enabled,
914-
commands::set_meta_tools_enabled,
913+
// Built-in servers (per-Space enablement + per-tool toggles)
914+
commands::list_builtin_servers,
915+
commands::set_builtin_server_enabled,
916+
commands::set_builtin_tool_enabled,
915917
// Config export commands
916918
commands::preview_config_export,
917919
commands::export_config_to_file,

apps/desktop/src-tauri/src/state/mod.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ use mcpmux_core::{
77
AppSettingsRepository, AppSettingsService, CredentialRepository, FeatureSetRepository,
88
GatewayPortService, InboundMcpClientRepository, InstalledServerRepository, LogConfig,
99
OutboundOAuthRepository, ServerDiscoveryService,
10-
ServerFeatureRepository as CoreServerFeatureRepository, ServerLogManager, SpaceRepository,
11-
SpaceService, WorkspaceBindingRepository,
10+
ServerFeatureRepository as CoreServerFeatureRepository, ServerLogManager,
11+
SpaceBuiltinConfigRepository, SpaceRepository, SpaceService, WorkspaceBindingRepository,
1212
};
1313
use mcpmux_storage::{
1414
Database, FieldEncryptor, SqliteAppSettingsRepository, SqliteCredentialRepository,
1515
SqliteFeatureSetRepository, SqliteInboundMcpClientRepository, SqliteInstalledServerRepository,
16-
SqliteOutboundOAuthRepository, SqliteServerFeatureRepository, SqliteSpaceRepository,
17-
SqliteWorkspaceBindingRepository,
16+
SqliteOutboundOAuthRepository, SqliteServerFeatureRepository,
17+
SqliteSpaceBuiltinConfigRepository, SqliteSpaceRepository, SqliteWorkspaceBindingRepository,
1818
};
1919
use std::path::PathBuf;
2020
use std::sync::Arc;
@@ -49,6 +49,8 @@ pub struct AppState {
4949
pub client_repository: Arc<dyn InboundMcpClientRepository>,
5050
/// Workspace-root -> FeatureSet bindings (resolver v2)
5151
pub workspace_binding_repository: Arc<dyn WorkspaceBindingRepository>,
52+
/// Per-Space built-in server config (Tool Optimization enablement + tool toggles)
53+
pub space_builtin_config_repository: Arc<dyn SpaceBuiltinConfigRepository>,
5254
/// Server feature repository for discovered MCP features (implements core trait)
5355
pub server_feature_repository: Arc<SqliteServerFeatureRepository>,
5456
/// Server feature repository cast to core trait (for gateway services)
@@ -107,6 +109,9 @@ impl AppState {
107109
let workspace_binding_repository: Arc<dyn WorkspaceBindingRepository> =
108110
Arc::new(SqliteWorkspaceBindingRepository::new(db.clone()));
109111

112+
let space_builtin_config_repository: Arc<dyn SpaceBuiltinConfigRepository> =
113+
Arc::new(SqliteSpaceBuiltinConfigRepository::new(db.clone()));
114+
110115
let server_feature_repository = Arc::new(SqliteServerFeatureRepository::new(db.clone()));
111116
let server_feature_repository_core: Arc<dyn CoreServerFeatureRepository> =
112117
server_feature_repository.clone();
@@ -164,6 +169,7 @@ impl AppState {
164169
feature_set_repository,
165170
client_repository,
166171
workspace_binding_repository,
172+
space_builtin_config_repository,
167173
server_feature_repository,
168174
server_feature_repository_core,
169175
encryptor,

0 commit comments

Comments
 (0)