feat(mcp): Connectors — catalog + global vs per-user runtimes (§7/§14/§15)

Re-architects MCP from one owner table + agent-written registration into an
admin-curated catalog with two runtimes unioned per session, surfaced in the UI
as "Connectors" (mcp/schema stays neutral, §0.1).

Two runtimes behind one seam (§7):
- Global runtime: shared, stateless connectors (web-search, Tavily…) on the
  HOST, connected at boot from mcp_global_servers, access-filtered per user via
  mcp_global_access.
- Per-user runtime: a user's activated connectors run INSIDE their container,
  started at first login from mcp_user_servers and living until restart (§9);
  docker exec -i children die via kill_on_drop when the UserContext drops.
- McpProvider trait (mcp/provider.rs): the session round-loop never learns which
  runtime owns a server. McpManager implements it directly (inert ownerless
  bundle); UserMcpView implements global ∪ user with an accessible_global
  snapshot. Both share McpManager::connect_all; McpServerSpec +
  global_row_spec/user_row_spec turn a DB row into a connectable spec.
- mcp-client: McpServerConfig.launch_in runs a stdio command inside a container
  via docker exec -i (set at runtime, never parsed from config).

Authorization is a capability on the role, not `if role==admin` (§0.1/§14):
role_capabilities table + db/role_capabilities.rs — register_remote and
register_local_from_catalog are self-service (seeded on every new role), while
register_local_script and manage_catalog are admin-only. admin holds every
capability by construction. This removes the agent-facing register_mcp/delete_mcp
tools and the mcp kinds of list_items/toggle_item, closing the §14 RCE vector.

Schema:
- Registry: mcp_catalog (vetted templates — schema only, no live creds),
  mcp_global_servers + mcp_global_access, role_capabilities.
- Owner: mcp_user_servers (per-user activations; api_key encrypted at rest,
  catalog_name a bare TEXT snapshot, never an owner→registry FK).
- Drops the old owner table mcp_servers.

API + UI: src/frontend/api/mcp.rs (admin catalog/global/access + user
available/activate/activated, all capability-gated via require_cap);
web/components/connectors.js (<connectors-page>) renders the user view always
and the admin view for role_id === 'admin'.

Deferred: interactive per-user auth (OAuth callback / QR / SSH elicitation, §15)
— only none/api_key wired; no boot seed of catalog presets; per-(user, session)
MCP grant model still open.
This commit is contained in:
2026-07-16 16:44:12 +01:00
parent 8dac783878
commit 6d299472e3
36 changed files with 2100 additions and 484 deletions
@@ -3,7 +3,7 @@ use std::sync::{Arc, RwLock};
use serde_json::Value;
use crate::mcp::McpManager;
use crate::mcp::McpProvider;
use crate::tools::Tool;
use crate::tools::tool_names as tn;
@@ -51,8 +51,9 @@ pub struct AgentRunConfig {
pub memory_tools: Vec<Arc<dyn Tool>>,
/// Image generation tools — present only when at least one provider is registered.
pub image_tools: Vec<Arc<dyn Tool>>,
/// MCP manager — used by `all_tool_defs()` to resolve which tools to include.
pub mcp: Arc<McpManager>,
/// MCP provider (global per-user) — used by `all_tool_defs()` to resolve
/// which tools to include.
pub mcp: Arc<dyn McpProvider>,
/// Set of MCP server names currently granted (activated) for this agent run.
///
/// - Root agents: pre-populated from `session_mcp_grants` DB at config-build time;
@@ -7,7 +7,7 @@ use sqlx::SqlitePool;
use crate::compactor::{ContextCompactor, SUMMARY_PREFIX};
use crate::config::DatetimeConfig;
use crate::db::{chat_history, chat_llm_tools, chat_summaries};
use crate::mcp::McpManager;
use crate::mcp::McpProvider;
use crate::tools::tool_names as tn;
/// Registry of installed skills, relative to Skald's process cwd. Injected into agents
@@ -38,7 +38,7 @@ pub struct MessageBuilder {
/// owner `pool` above backs `user-memory/`.
pub shared_pool: Arc<SqlitePool>,
pub session_id: i64,
pub mcp: Arc<McpManager>,
pub mcp: Arc<dyn McpProvider>,
pub datetime_config: DatetimeConfig,
pub max_history_messages: usize,
pub max_tool_result_chars: Option<usize>,
+3 -3
View File
@@ -22,7 +22,7 @@ use crate::events::ServerEvent;
use core_api::message_meta::MessageMetadata;
use core_api::user_fs::UserFs;
use crate::llm::LlmManager;
use crate::mcp::McpManager;
use crate::mcp::McpProvider;
use crate::image_generate::ImageGeneratorManager;
use crate::memory::MemoryManager;
use crate::tool_discovery::ToolDiscovery;
@@ -292,7 +292,7 @@ pub struct ChatSessionHandler {
/// True for short-lived automated sessions (cron, tic).
pub(super) is_ephemeral: bool,
pub(super) tools: Arc<ToolRegistry>,
pub(super) mcp: Arc<McpManager>,
pub(super) mcp: Arc<dyn McpProvider>,
/// Records tools offered to the LLM each round so the Security-groups UI can
/// list/gate dynamically-injected tools (interface/plugin/provider tools).
pub(super) tool_discovery: Arc<ToolDiscovery>,
@@ -354,7 +354,7 @@ impl ChatSessionHandler {
is_interactive: bool,
is_ephemeral: bool,
tools: Arc<ToolRegistry>,
mcp: Arc<McpManager>,
mcp: Arc<dyn McpProvider>,
approval: Arc<ApprovalManager>,
clarification: Arc<ClarificationManager>,
event_bus: Arc<ChatEventBus>,
+5 -3
View File
@@ -13,7 +13,7 @@ use crate::compactor::ContextCompactor;
use crate::config::DatetimeConfig;
use crate::db::{chat_sessions, chat_sessions_stack};
use crate::llm::LlmManager;
use crate::mcp::McpManager;
use crate::mcp::McpProvider;
use crate::image_generate::ImageGeneratorManager;
use crate::memory::MemoryManager;
use crate::run_context::{RunContext, RunContextManager};
@@ -38,7 +38,9 @@ pub struct ChatSessionManager {
max_tool_result_chars: Option<usize>,
datetime_config: DatetimeConfig,
tools: Arc<ToolRegistry>,
mcp: Arc<McpManager>,
/// The MCP tools visible to this owner: the access-filtered global runtime
/// unioned with their per-user runtime (blueprint §7), behind one trait.
mcp: Arc<dyn McpProvider>,
approval: Arc<ApprovalManager>,
clarification: Arc<ClarificationManager>,
event_bus: Arc<ChatEventBus>,
@@ -66,7 +68,7 @@ impl ChatSessionManager {
max_tool_result_chars: Option<usize>,
datetime_config: DatetimeConfig,
tools: Arc<ToolRegistry>,
mcp: Arc<McpManager>,
mcp: Arc<dyn McpProvider>,
approval: Arc<ApprovalManager>,
clarification: Arc<ClarificationManager>,
event_bus: Arc<ChatEventBus>,