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.
-
MCP Server Plugin (
tauri-plugin-mcp-server)- Implements MCP protocol using
jsonrpseeor custom JSON-RPC - Manages client connections and authentication
- Provides introspection and command routing
- Implements MCP protocol using
-
Plugin Discovery System
- Event-based plugin registration
- Static configuration fallback
- Runtime capability detection
-
Command Proxy Layer
- Translates MCP tool calls to Tauri commands
- Handles async command execution
- Manages response transformation
// 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>,
}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(())
}
}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);
}
});
}
}// 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 ®istry.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
}
}#[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)
}
}// 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
});
}
});
}
}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
})
}
}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(())
}
}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,
}
}
}- Test plugin discovery mechanisms
- Test command routing logic
- Test permission enforcement
#[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"}));
}
}- Test unauthorized access attempts
- Test rate limiting
- Test input validation
[features]
plugin-mcp-server = ["jsonrpsee", "tokio"]
mcp-dynamic-discovery = [] # Optional dynamic discovery
mcp-tls = ["rustls"] # Optional TLS support{
"mcp_server": {
"enabled": true,
"bind_address": "127.0.0.1:8080",
"require_auth": true,
"allowed_origins": ["http://localhost:*"],
"plugin_whitelist": ["workerd", "podman", "bun"]
}
}- 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
| 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 |
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.