Skip to content
This repository was archived by the owner on Aug 7, 2025. It is now read-only.

Latest commit

 

History

History
336 lines (277 loc) · 9.08 KB

File metadata and controls

336 lines (277 loc) · 9.08 KB

MCP Server Plugin Implementation Plan for Tauri

Overview

This document outlines a concrete implementation plan for adding MCP (Model Context Protocol) server capabilities to the Tauri application, enabling AI agents to interact with all available plugins.

Architecture Design

Core Components

  1. MCP Server Plugin (tauri-plugin-mcp-server)

    • Implements MCP protocol using jsonrpsee or custom JSON-RPC
    • Manages client connections and authentication
    • Provides introspection and command routing
  2. Plugin Discovery System

    • Event-based plugin registration
    • Static configuration fallback
    • Runtime capability detection
  3. Command Proxy Layer

    • Translates MCP tool calls to Tauri commands
    • Handles async command execution
    • Manages response transformation

Implementation Steps

Phase 1: Basic MCP Server Infrastructure

// tauri-plugin-mcp-server/src/lib.rs
pub struct McpServerPlugin<R: Runtime> {
    server: Arc<Mutex<Option<McpServer>>>,
    app_handle: AppHandle<R>,
    plugin_registry: Arc<Mutex<PluginRegistry>>,
}

pub struct PluginRegistry {
    plugins: HashMap<String, PluginMetadata>,
    command_map: HashMap<String, CommandInfo>,
}

#[derive(Serialize, Deserialize)]
pub struct PluginMetadata {
    name: String,
    version: String,
    commands: Vec<String>,
    permissions: Vec<String>,
}

Phase 2: Plugin Discovery Implementation

Option A: Static Discovery (Simpler, More Secure)

impl McpServerPlugin {
    fn discover_static_plugins(&self) -> Result<()> {
        // Use the existing get_enabled_plugins() pattern
        let enabled = self.get_enabled_plugins();
        
        // Hardcode known plugin commands based on permissions
        if enabled.workerd {
            self.register_plugin("workerd", vec![
                "check_workerd_installed",
                "start_worker",
                "stop_worker",
                // ... other commands from permissions file
            ]);
        }
        
        if enabled.podman {
            self.register_plugin("podman", vec![
                "check_podman_installed",
                "create_container",
                // ...
            ]);
        }
        
        Ok(())
    }
}

Option B: Dynamic Discovery (More Flexible)

impl McpServerPlugin {
    fn setup_dynamic_discovery<R: Runtime>(&self, app: &AppHandle<R>) {
        // Emit discovery request
        app.emit("mcp:discover_plugins", json!({}));
        
        // Listen for plugin announcements
        app.listen("plugin:announce", |event| {
            if let Ok(metadata) = serde_json::from_str::<PluginMetadata>(&event.payload()) {
                self.plugin_registry.lock().unwrap().register(metadata);
            }
        });
    }
}

Phase 3: MCP Protocol Implementation

// MCP tool definition
#[derive(Serialize, Deserialize)]
struct McpTool {
    name: String,
    description: String,
    input_schema: serde_json::Value,
}

// Convert Tauri commands to MCP tools
impl McpServerPlugin {
    fn commands_to_tools(&self) -> Vec<McpTool> {
        let registry = self.plugin_registry.lock().unwrap();
        let mut tools = vec![];
        
        for (plugin_name, metadata) in &registry.plugins {
            for command in &metadata.commands {
                tools.push(McpTool {
                    name: format!("{}:{}", plugin_name, command),
                    description: format!("Execute {} command from {} plugin", command, plugin_name),
                    input_schema: self.get_command_schema(plugin_name, command),
                });
            }
        }
        
        tools
    }
}

Phase 4: Command Routing and Execution

#[async_trait]
impl McpHandler for McpServerPlugin {
    async fn handle_tool_call(&self, name: &str, args: Value) -> Result<Value> {
        // Parse plugin:command format
        let parts: Vec<&str> = name.split(':').collect();
        if parts.len() != 2 {
            return Err(Error::InvalidToolName);
        }
        
        let (plugin, command) = (parts[0], parts[1]);
        
        // Route to frontend for execution
        let result = self.app_handle
            .emit("mcp:execute_command", json!({
                "plugin": plugin,
                "command": command,
                "args": args
            }))
            .await?;
        
        // Wait for response via event listener
        let response = self.await_command_response(plugin, command).await?;
        
        Ok(response)
    }
}

Phase 5: Frontend Integration

// Frontend command router
import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';

class McpCommandRouter {
  async initialize() {
    // Listen for MCP command execution requests
    await listen('mcp:execute_command', async (event) => {
      const { plugin, command, args } = event.payload;
      
      try {
        // Dynamically invoke the command
        const result = await invoke(`plugin:${plugin}:${command}`, args);
        
        // Send response back to MCP server
        await emit('mcp:command_response', {
          plugin,
          command,
          result,
          success: true
        });
      } catch (error) {
        await emit('mcp:command_response', {
          plugin,
          command,
          error: error.message,
          success: false
        });
      }
    });
  }
}

Security Considerations

1. Authentication and Authorization

pub struct McpAuthConfig {
    require_auth: bool,
    allowed_clients: Vec<String>,
    token_validation: Box<dyn Fn(&str) -> bool>,
}

impl McpServerPlugin {
    fn validate_client(&self, token: &str) -> Result<ClientContext> {
        // Implement token validation
        // Map client to allowed permissions
        Ok(ClientContext {
            client_id: "validated_client",
            allowed_plugins: vec!["workerd", "podman"],
            rate_limit: Some(100), // requests per minute
        })
    }
}

2. Permission Enforcement

impl McpServerPlugin {
    fn check_permission(&self, client: &ClientContext, plugin: &str, command: &str) -> Result<()> {
        // Check if client can access this plugin
        if !client.allowed_plugins.contains(&plugin.to_string()) {
            return Err(Error::PermissionDenied);
        }
        
        // Check specific command permission
        let permission = format!("{}:allow-{}", plugin, command.replace('_', "-"));
        if !self.has_permission(&permission) {
            return Err(Error::CommandNotAllowed);
        }
        
        Ok(())
    }
}

3. Network Security

pub struct McpServerConfig {
    bind_address: String,
    tls_config: Option<TlsConfig>,
    max_connections: usize,
}

impl Default for McpServerConfig {
    fn default() -> Self {
        Self {
            bind_address: "127.0.0.1:0".to_string(), // Local only by default
            tls_config: None, // Require explicit TLS configuration
            max_connections: 10,
        }
    }
}

Testing Strategy

1. Unit Tests

  • Test plugin discovery mechanisms
  • Test command routing logic
  • Test permission enforcement

2. Integration Tests

#[cfg(test)]
mod tests {
    #[test]
    async fn test_mcp_tool_execution() {
        let app = create_test_app();
        let mcp_plugin = McpServerPlugin::new();
        
        // Register test plugin
        mcp_plugin.register_plugin("test", vec!["echo"]);
        
        // Execute via MCP
        let result = mcp_plugin.handle_tool_call("test:echo", json!({"message": "hello"})).await;
        
        assert_eq!(result.unwrap(), json!({"response": "hello"}));
    }
}

3. Security Tests

  • Test unauthorized access attempts
  • Test rate limiting
  • Test input validation

Deployment Considerations

1. Feature Flag Integration

[features]
plugin-mcp-server = ["jsonrpsee", "tokio"]
mcp-dynamic-discovery = []  # Optional dynamic discovery
mcp-tls = ["rustls"]        # Optional TLS support

2. Configuration

{
  "mcp_server": {
    "enabled": true,
    "bind_address": "127.0.0.1:8080",
    "require_auth": true,
    "allowed_origins": ["http://localhost:*"],
    "plugin_whitelist": ["workerd", "podman", "bun"]
  }
}

Timeline

  • Week 1: Basic MCP server infrastructure and static plugin discovery
  • Week 2: Command routing and frontend integration
  • Week 3: Security implementation and testing
  • Week 4: Dynamic discovery (optional) and documentation

Risks and Mitigations

Risk Impact Mitigation
Security vulnerabilities High Mandatory auth, permission checks, audit logging
Performance overhead Medium Async execution, connection pooling, caching
Plugin compatibility Low Fallback to static discovery, version checking
Debugging complexity Medium Comprehensive logging, trace IDs, error context

Conclusion

This implementation plan provides a secure, scalable approach to adding MCP server capabilities to Tauri applications. The phased approach allows for incremental development while maintaining security throughout.