소스 검색

Add MCP mode

clanker 1 개월 전
부모
커밋
1c625d9658
9개의 변경된 파일1163개의 추가작업 그리고 3개의 파일을 삭제
  1. 1 0
      .kilocode/mcp.json
  2. 552 0
      mcp-mode-design.md
  3. 9 2
      meson.build
  4. 18 1
      src/cli/argument-parser.vala
  5. 29 0
      src/cli/command-handler.vala
  6. 77 0
      src/mcp/command-handler.vala
  7. 123 0
      src/mcp/server.vala
  8. 200 0
      src/mcp/vapi-resource-provider.vala
  9. 154 0
      test_mcp_client.py

+ 1 - 0
.kilocode/mcp.json

@@ -0,0 +1 @@
+{"mcpServers":{}}

+ 552 - 0
mcp-mode-design.md

@@ -0,0 +1,552 @@
+# MCP Mode Implementation Design for Valaq
+
+## 1. Overview
+
+This document provides a comprehensive design for implementing MCP (Model Context Protocol) mode in valaq, enabling VAPI file exploration through the MCP protocol using STDIO communication. The design leverages the existing valaq architecture while adding MCP capabilities through the mcp-vala library.
+
+## 2. Architecture Overview
+
+### 2.1 High-Level Architecture
+
+```mermaid
+graph TD
+    A[valaq --mcp] --> B[ArgumentParser]
+    B --> C{MCP Mode?}
+    C -->|Yes| D[McpModeCommandHandler]
+    C -->|No| E[NormalCommandHandler]
+    D --> F[McpServer]
+    F --> G[Mc p.Core.Server]
+    G --> H[Resource Providers]
+    H --> I[VapiResourceProvider]
+    I --> J[JSON Output]
+    J --> K[STDIO Transport]
+    K --> L[MCP Client]
+```
+
+### 2.2 Component Integration
+
+The MCP mode integrates with existing valaq components:
+
+1. **ArgumentParser**: Extended to recognize `--mcp` flag
+2. **McpModeCommandHandler**: New component for MCP-specific command handling
+3. **McpServer**: Wrapper around `Mcp.Core.Server` with valaq-specific logic
+4. **VapiResourceProvider**: Custom resource provider for VAPI files
+5. **Existing Components**: Reuses VapiParser, SymbolNavigator, and JsonFormatter
+
+## 3. URI Scheme and Resource Mapping
+
+### 3.1 URI Scheme Design
+
+The MCP mode will use the `vapi://` URI scheme with the following patterns:
+
+1. **Root VAPI Listing**: `vapi://{vapi}` 
+   - Returns JSON formatted list of root symbols (namespaces)
+   - Example: `vapi://gtk+-3.0`
+
+2. **Symbol Navigation**: `vapi://{vapi}/{symbol-path}`
+   - Returns JSON formatted symbol children
+   - Uses dot-separated symbol paths
+   - Example: `vapi://gtk+-3.0/Gtk.Window.show`
+
+### 3.2 Resource Mapping Strategy
+
+| URI Pattern | Resource Type | Implementation |
+|-------------|-------------|--------------|
+| `vapi://{vapi}` | VAPI Root Symbols | List all root symbols from parsed VAPI |
+| `vapi://{vapi}/{symbol}` | Symbol Children | Navigate to symbol and return children |
+| `vapi://{vapi}/{symbol-path}` | Symbol Path | Navigate to nested symbol using dot notation |
+
+## 4. VAPI Resource Provider Class Structure
+
+### 4.1 VapiResourceProvider Class
+
+```vala
+public class VapiResourceProvider : Mcp.Resources.BaseProvider {
+    private VapiParser parser;
+    private SymbolNavigator navigator;
+    private JsonFormatter formatter;
+    private HashTable<string, Gee.ArrayList<Symbol>> vapi_cache;
+    
+    public VapiResourceProvider () {
+        // Initialize base provider
+        base ();
+        
+        // Initialize components
+        parser = new VapiParser ();
+        navigator = null;
+        formatter = new JsonFormatter ();
+        vapi_cache = new HashTable<string, Gee.ArrayList<Symbol>> ();
+    }
+    
+    public override async Gee.ArrayList<Mcp.Resources.Types.Resource> list_resources (string? cursor) throws Error {
+        var resources = new Gee.ArrayList<Mcp.Resources.Types.Resource> ();
+        
+        // Get list of available VAPI files
+        var vapi_files = get_available_vapi_files ();
+        
+        foreach (string vapi_file in vapi_files) {
+            string vapi_name = get_vapi_basename (vapi_file);
+            string uri = @"vapi://$(vapi_name)";
+            
+            var resource = new Mcp.Resources.Types.Resource ();
+            resource.uri = uri;
+            resource.name = vapi_name;
+            resource.mime_type = "application/json";
+            resource.description = @"VAPI file: $(vapi_name)";
+            
+            resources.add (resource);
+        }
+        
+        return resources;
+    }
+    
+    public override async Gee.ArrayList<Mcp.Types.Common.ResourceContents> read_resource (string uri) throws Error {
+        // Parse URI to extract VAPI name and symbol path
+        string[] uri_parts = uri.split ("://");
+        if (uri_parts.length != 2 || uri_parts[0] != "vapi") {
+            throw new Mcp.Core.McpError.INVALID_PARAMS ("Invalid VAPI URI format");
+        }
+        
+        string[] path_parts = uri_parts[1].split ("/");
+        string vapi_name = path_parts[0];
+        string? symbol_path = path_parts.length > 1 ? string.joinv (".", path_parts[1:]) : null;
+        
+        // Resolve VAPI file path
+        string vapi_path = resolve_vapi_file (vapi_name);
+        if (vapi_path == null) {
+            throw new Mcp.Core.McpError.RESOURCE_NOT_FOUND ("VAPI file not found: %s".printf (vapi_name));
+        }
+        
+        // Parse VAPI file (use cache if available)
+        if (!vapi_cache.contains (vapi_name)) {
+            if (!parser.parse_file (vapi_path)) {
+                throw new Mcp.Core.McpError.PARSE_ERROR ("Failed to parse VAPI file: %s".printf (vapi_name));
+            }
+            
+            var root_symbols = parser.get_root_symbols ();
+            vapi_cache.insert (vapi_name, root_symbols);
+            navigator = new SymbolNavigator (root_symbols);
+        }
+        
+        var root_symbols = vapi_cache.lookup (vapi_name);
+        
+        Gee.ArrayList<Mcp.Types.Common.ResourceContents> contents = new Gee.ArrayList<Mcp.Types.Common.ResourceContents> ();
+        
+        if (symbol_path == null) {
+            // Return root symbols as JSON
+            string json_data = formatter.format_symbol_list (root_symbols);
+            var content = new Mcp.Types.Common.TextContent (json_data);
+            contents.add (content);
+        } else {
+            // Navigate to symbol and return children
+            string[] symbol_parts = symbol_path.split (".");
+            Symbol? target_symbol = navigator.navigate_to_path (symbol_parts);
+            
+            if (target_symbol == null) {
+                throw new Mcp.Core.McpError.RESOURCE_NOT_FOUND ("Symbol not found: %s".printf (symbol_path));
+            }
+            
+            var child_symbols = navigator.get_child_symbols (target_symbol);
+            string json_data = formatter.format_symbol_list (child_symbols);
+            var content = new Mcp.Types.Common.TextContent (json_data);
+            contents.add (content);
+        }
+        
+        return contents;
+    }
+    
+    private string[] get_available_vapi_files () {
+        // Reuse existing file discovery logic
+        var file_utils = new FileUtils ();
+        return file_utils.find_all_vapi_files ();
+    }
+    
+    private string get_vapi_basename (string vapi_file) {
+        // Extract basename without extension
+        if (vapi_file.has_suffix (".vapi")) {
+            return vapi_file.substring (0, vapi_file.length - 5);
+        }
+        return vapi_file;
+    }
+    
+    private string? resolve_vapi_file (string vapi_name) {
+        // Reuse existing VAPI resolution logic
+        var file_utils = new FileUtils ();
+        return file_utils.resolve_vapi_file (vapi_name);
+    }
+}
+```
+
+### 4.2 Resource Content Structure
+
+The resource content follows the existing valaq JSON output format:
+
+```json
+{
+  "result_type": "symbol_list",
+  "symbols": [
+    {
+      "name": "Gtk",
+      "type": "namespace",
+      "access": "public",
+      "full_path": "Gtk",
+      "child_count": 15
+    }
+  ]
+}
+```
+
+## 5. MCP Server Integration
+
+### 5.1 McpModeCommandHandler Class
+
+```vala
+public class McpModeCommandHandler : Object {
+    private McpServer mcp_server;
+    
+    public McpModeCommandHandler (McpServer mcp_server) {
+        this.mcp_server = mcp_server;
+    }
+    
+    public int handle_mcp_command () {
+        try {
+            // Start MCP server in STDIO mode
+            bool started = yield mcp_server.start ();
+            
+            if (!started) {
+                stderr.printf ("Failed to start MCP server\n");
+                return 1;
+            }
+            
+            // Keep main loop running
+            var loop = new MainLoop ();
+            loop.run ();
+            
+            return 0;
+        } catch (Error e) {
+            stderr.printf ("MCP server error: %s\n", e.message);
+            return 1;
+        }
+    }
+}
+```
+
+### 5.2 McpServer Class
+
+```vala
+public class McpServer : Object {
+    private Mcp.Core.Server mcp_core_server;
+    private VapiResourceProvider vapi_provider;
+    
+    public McpServer () {
+        // Create server info
+        var server_info = new Mcp.Types.Protocol.ServerInfo (
+            "valaq",
+            "1.0.0",
+            "Valaq VAPI File Query Tool - MCP Server"
+        );
+        
+        // Create capabilities
+        var capabilities = new Mcp.Types.Protocol.ServerCapabilities ();
+        capabilities.resources = new Mcp.Types.Protocol.ResourcesCapabilities ();
+        capabilities.resources.subscribe = true;
+        capabilities.resources.list_changed = true;
+        
+        // Create MCP server
+        mcp_core_server = new Mcp.Core.Server (server_info, capabilities);
+        
+        // Create and register VAPI resource provider
+        vapi_provider = new VapiResourceProvider ();
+        mcp_core_server.resource_manager.register_provider ("vapi", vapi_provider);
+        
+        // Connect server signals
+        mcp_core_server.initialized.connect (on_server_initialized);
+        mcp_core_server.shutdown.connect (on_server_shutdown);
+    }
+    
+    public async bool start () throws Error {
+        return yield mcp_core_server.start ();
+    }
+    
+    public async void stop () {
+        yield mcp_core_server.stop ();
+    }
+    
+    private void on_server_initialized () {
+        // Server is ready for MCP communication
+        print ("MCP server initialized and ready\n");
+    }
+    
+    private void on_server_shutdown () {
+        // Cleanup resources
+        vapi_provider.cleanup ();
+    }
+}
+```
+
+## 6. Command-Line Argument Extension
+
+### 6.1 ArgumentParser Modifications
+
+```vala
+// In ArgumentParser class, add new field
+private bool mcp_mode;
+
+// In parse_long_option method, add:
+} else if (arg == "--mcp") {
+    mcp_mode = true;
+} else if (arg == "--help") {
+    show_help = true;
+}
+
+// Add new methods:
+public bool is_mcp_mode () {
+    return mcp_mode;
+}
+
+public void show_mcp_help_text () {
+    print ("Valaq MCP Server Mode\n");
+    print ("====================\n\n");
+    print ("USAGE:\n");
+    print ("    valaq --mcp\n\n");
+    print ("DESCRIPTION:\n");
+    print ("    Starts valaq as an MCP server using STDIO communication.\n");
+    print ("    Provides access to VAPI files through MCP protocol with vapi:// URI scheme.\n\n");
+    print ("RESOURCE ACCESS:\n");
+    print ("    vapi://{vapi_name}           - List root symbols in VAPI file\n");
+    print ("    vapi://{vapi_name}/{symbol}  - Navigate to symbol and list children\n");
+    print ("    vapi://{vapi_name}/{path.to.symbol} - Navigate to nested symbol\n\n");
+    print ("EXAMPLES:\n");
+    print ("    valaq --mcp\n");
+    print ("    # Server starts and listens for MCP requests\n\n");
+    print ("EXIT CODES:\n");
+    print ("    0    Success\n");
+    print ("    1    MCP server error\n");
+}
+```
+
+### 6.2 CommandHandler Integration
+
+```vala
+// In CommandHandler class, modify handle_command method:
+public int handle_command (ArgumentParser args) {
+    if (args.is_mcp_mode ()) {
+        // Create and run MCP server
+        var mcp_handler = new McpModeCommandHandler (mcp_server);
+        return mcp_handler.handle_mcp_command ();
+    }
+    
+    // ... existing command handling logic
+}
+```
+
+## 7. Build System Changes
+
+### 7.1 Meson.build Updates
+
+```meson
+# Add mcp-vala dependency
+mcp_dep = dependency('mcp-vala')
+
+# Add MCP mode source files
+sources += files(
+    'src/mcp/mcp-server.vala',
+    'src/mcp/mcp-command-handler.vala',
+    'src/mcp/vapi-resource-provider.vala'
+)
+
+# Update vala_args for MCP
+vala_args += [
+    '--pkg', 'mcp-vala'
+]
+
+# Update executable dependencies
+executable('valaq',
+    sources,
+    dependencies: [glib_dep, gobject_dep, json_glib_dep, gee_dep, libvala_dep, mcp_dep, jsonrpc_dep],
+    vala_args: vala_args,
+    install: true,
+)
+```
+
+### 7.2 New Directory Structure
+
+```
+src/
+├── mcp/
+│   ├── mcp-server.vala           # Main MCP server implementation
+│   ├── mcp-command-handler.vala  # MCP mode command handling
+│   └── vapi-resource-provider.vala # VAPI resource provider
+├── cli/
+│   ├── argument-parser.vala       # Extended for --mcp flag
+│   └── command-handler.vala        # Modified for MCP mode
+└── [existing files...]
+```
+
+## 8. Error Handling and Logging
+
+### 8.1 Error Handling Strategy
+
+1. **MCP Protocol Errors**: Use `Mcp.Core.McpError` domain with proper error codes
+2. **VAPI Parsing Errors**: Wrap existing `ValaqError` with MCP error responses
+3. **Resource Not Found**: Return proper MCP error codes for invalid URIs
+4. **STDIO Transport Errors**: Handle JSON-RPC communication failures
+
+### 8.2 Logging Strategy
+
+1. **MCP Server Logs**: Use `print()` for server status messages
+2. **Error Logging**: Use `stderr.printf()` for error messages
+3. **Debug Logging**: Optional verbose mode for troubleshooting
+4. **MCP Protocol Logging**: Leverage mcp-vala's built-in logging capabilities
+
+## 9. Implementation Roadmap
+
+### 9.1 Phase 1: Core MCP Infrastructure
+1. **Create MCP Server Classes**
+   - Implement `McpServer` wrapper class
+   - Implement `McpModeCommandHandler` for MCP mode
+   - Set up basic server initialization and lifecycle
+
+2. **Implement VAPI Resource Provider**
+   - Create `VapiResourceProvider` class
+   - Implement resource listing and reading
+   - Integrate with existing VAPI parsing logic
+
+3. **Extend Command-Line Interface**
+   - Add `--mcp` flag to `ArgumentParser`
+   - Update `CommandHandler` for MCP mode detection
+   - Add help text for MCP mode
+
+### 9.2 Phase 2: Integration and Testing
+1. **Integrate Components**
+   - Connect MCP server to resource provider
+   - Implement proper error handling
+   - Add resource caching for performance
+
+2. **Testing**
+   - Unit tests for VAPI resource provider
+   - Integration tests for MCP protocol
+   - Manual testing with MCP clients
+
+3. **Documentation**
+   - Update README with MCP mode instructions
+   - Add examples of MCP client usage
+   - Document URI scheme and resource access
+
+### 9.3 Phase 3: Optimization and Polish
+1. **Performance Optimization**
+   - Implement resource caching
+   - Optimize VAPI parsing for large files
+   - Add lazy loading for symbol details
+
+2. **Advanced Features**
+   - Resource change notifications
+   - Symbol search capabilities
+   - Custom resource templates
+
+## 10. Testing Strategy
+
+### 10.1 Unit Testing
+
+```vala
+// Test VAPI resource provider
+class VapiResourceProviderTest : TestCase {
+    private VapiResourceProvider provider;
+    
+    protected override void set_up () {
+        provider = new VapiResourceProvider ();
+    }
+    
+    public void test_list_resources () {
+        var resources = provider.list_resources (null);
+        assert (resources.size > 0);
+        
+        // Test specific VAPI URI
+        var contents = provider.read_resource ("vapi://gtk+-3.0");
+        assert (contents != null);
+        assert (contents.size > 0);
+    }
+    
+    public void test_read_resource_invalid_uri () {
+        try {
+            provider.read_resource ("invalid://format");
+            assert_not_reached ();
+        } catch (Mcp.Core.McpError e) {
+            assert (e.code == -32603); // INVALID_PARAMS
+        }
+    }
+}
+```
+
+### 10.2 Integration Testing
+
+```bash
+# Test MCP server with sample client
+./valaq --mcp &
+echo '{"jsonrpc": "2.0", "method": "resources/list", "id": "test1"}' | ./valaq --mcp
+
+# Expected response
+{
+  "result": {
+    "resources": [
+      {
+        "uri": "vapi://gtk+-3.0",
+        "name": "gtk+-3.0",
+        "mime_type": "application/json",
+        "description": "VAPI file: gtk+-3.0"
+      }
+    ]
+  }
+}
+```
+
+### 10.3 Performance Testing
+
+1. **Large VAPI Files**: Test with complex VAPIs like GTK
+2. **Concurrent Access**: Test multiple simultaneous requests
+3. **Memory Usage**: Monitor memory consumption
+4. **Response Times**: Ensure timely responses
+
+## 11. Security Considerations
+
+### 11.1 Input Validation
+
+1. **URI Validation**: Validate `vapi://` URIs strictly
+2. **Path Traversal**: Prevent directory traversal attacks
+3. **VAPI Names**: Only allow known VAPI file names
+4. **Symbol Paths**: Validate symbol path components
+
+### 11.2 Resource Access Control
+
+1. **VAPI Directories**: Restrict to standard VAPI locations
+2. **File Permissions**: Ensure proper read access
+3. **Resource Limits**: Implement rate limiting if needed
+
+## 12. Migration and Compatibility
+
+### 12.1 Backward Compatibility
+
+1. **Existing CLI**: Maintain full compatibility with current CLI
+2. **JSON Format**: Use existing JSON output structure
+3. **VAPI Parsing**: Leverage existing libvala integration
+
+### 12.2 MCP Protocol Compliance
+
+1. **Standard MCP**: Follow MCP v2025-11-25 specification
+2. **JSON-RPC 2.0**: Use mcp-vala's implementation
+3. **Error Codes**: Use standard MCP error codes
+4. **Resource Types**: Implement standard resource content types
+
+## 13. Summary
+
+This design provides a comprehensive approach to implementing MCP mode in valaq that:
+
+1. **Leverages Existing Architecture**: Reuses proven VAPI parsing and navigation logic
+2. **Clean Integration**: Minimal changes to existing components
+3. **Standard MCP Compliance**: Uses mcp-vala library for protocol implementation
+4. **Flexible Resource Access**: Provides intuitive VAPI exploration through MCP protocol
+5. **Robust Error Handling**: Comprehensive error handling and logging
+6. **Performance Optimized**: Caching and efficient resource access
+7. **Thoroughly Tested**: Complete testing strategy for reliability
+
+The implementation follows valaq's modular design principles while adding MCP capabilities through well-defined interfaces and standard protocols.

+ 9 - 2
meson.build

@@ -10,6 +10,8 @@ gobject_dep = dependency('gobject-2.0', version: '>= 2.56')
 json_glib_dep = dependency('json-glib-1.0', version: '>= 1.0')
 gee_dep = dependency('gee-0.8', version: '>= 0.20.0')
 libvala_dep = dependency('libvala-0.56', version: '>= 0.56.0')
+jsonrpc_dep = dependency('jsonrpc-glib-1.0')
+mcp_dep = dependency('mcp-vala')
 
 # Source files
 sources = files(
@@ -24,6 +26,9 @@ sources = files(
   'src/output/json-formatter.vala',
   'src/utils/file-utils.vala',
   'src/utils/error-handling.vala',
+  'src/mcp/server.vala',
+  'src/mcp/command-handler.vala',
+  'src/mcp/vapi-resource-provider.vala',
 )
 
 # Vala compilation flags
@@ -33,13 +38,15 @@ vala_args = [
   '--pkg', 'gobject-2.0',
   '--pkg', 'json-glib-1.0',
   '--pkg', 'gee-0.8',
+  '--pkg', 'jsonrpc-glib-1.0',
+  '--pkg', 'mcp-vala',
   '--pkg', 'libvala-0.56',
 ]
 
 # Executable
 executable('valaq',
   sources,
-  dependencies: [glib_dep, gobject_dep, json_glib_dep, gee_dep, libvala_dep],
+  dependencies: [glib_dep, gobject_dep, json_glib_dep, gee_dep, libvala_dep, mcp_dep, jsonrpc_dep],
   vala_args: vala_args,
   install: true,
 )
@@ -48,7 +55,7 @@ executable('valaq',
 install_man('man/valaq.1')
 
 # Test configuration
-test_dependencies = [glib_dep, gobject_dep, json_glib_dep, gee_dep, libvala_dep]
+test_dependencies = [glib_dep, gobject_dep, json_glib_dep, gee_dep, libvala_dep, mcp_dep, jsonrpc_dep]
 
 test_sources = [
   'tests/test-vapi-parser.vala',

+ 18 - 1
src/cli/argument-parser.vala

@@ -15,6 +15,7 @@ public class ArgumentParser : Object {
     private bool json_output;
     private bool show_help;
     private bool show_version;
+    private bool mcp_mode;
     private string error_message;
     
     /**
@@ -26,6 +27,7 @@ public class ArgumentParser : Object {
         json_output = false;
         show_help = false;
         show_version = false;
+        mcp_mode = false;
         error_message = "";
     }
     
@@ -78,6 +80,8 @@ public class ArgumentParser : Object {
             show_version = true;
         } else if (arg == "--json") {
             json_output = true;
+        } else if (arg == "--mcp") {
+            mcp_mode = true;
         } else {
             error_message = "Unknown option: %s".printf (arg);
             return false;
@@ -103,6 +107,9 @@ public class ArgumentParser : Object {
                 case 'j':
                     json_output = true;
                     break;
+                case 'm':
+                    mcp_mode = true;
+                    break;
                 default:
                     error_message = "Unknown option: -%c".printf (option);
                     return false;
@@ -233,6 +240,15 @@ public class ArgumentParser : Object {
         return show_version;
     }
     
+    /**
+     * Checks if MCP mode was requested.
+     *
+     * @return True if MCP mode requested, false otherwise
+     */
+    public bool is_mcp_mode () {
+        return mcp_mode;
+    }
+    
     
     /**
      * Shows comprehensive usage help.
@@ -250,7 +266,8 @@ public class ArgumentParser : Object {
         print ("OPTIONS:\n");
         print ("    -h, --help          Show this help message and exit\n");
         print ("    -v, --version       Show version information and exit\n");
-        print ("    -j, --json          Output in JSON format instead of human-readable text\n\n");
+        print ("    -j, --json          Output in JSON format instead of human-readable text\n");
+        print ("    -m, --mcp           Start in MCP (Model Context Protocol) mode\n\n");
         print ("ARGUMENTS:\n");
         print ("    VAPI_FILE           Path to a VAPI file to analyze\n");
         print ("                        Can be:\n");

+ 29 - 0
src/cli/command-handler.vala

@@ -57,6 +57,11 @@ public class CommandHandler : Object {
                 return 0;
             }
             
+            // Check for MCP mode - this takes precedence over other operations
+            if (args.is_mcp_mode ()) {
+                return handle_mcp_mode ();
+            }
+            
             string vapi_path = args.get_vapi_path ();
             
             // Handle no parameters - list VAPI files
@@ -500,6 +505,30 @@ public class CommandHandler : Object {
         var now = new DateTime.now_utc ();
         return now.format ("%Y-%m-%dT%H:%M:%S.%fZ");
     }
+    
+    /**
+     * Handles MCP mode operation.
+     * Initializes and starts the MCP server for STDIO communication.
+     *
+     * @return Exit code
+     */
+    private int handle_mcp_mode () {
+        try {
+            // Create MCP command handler
+            var mcp_handler = new McpCommandHandler ();
+            
+            // Start MCP server
+            return mcp_handler.handle_mcp_command ();
+            
+        } catch (ValaqError e) {
+            // In MCP mode, we should use stderr for error output to avoid interfering with JSON-RPC
+            stderr.printf ("MCP initialization error: %s\n", e.message);
+            return 1;
+        } catch (Error e) {
+            stderr.printf ("Unexpected MCP error: %s\n", e.message);
+            return 1;
+        }
+    }
 }
 
 /**

+ 77 - 0
src/mcp/command-handler.vala

@@ -0,0 +1,77 @@
+/*
+ * mcp/command-handler.vala
+ *
+ * MCP command handler for valaq.
+ * This class manages MCP server startup and shutdown.
+ */
+
+/**
+ * MCP command handler that manages MCP server operations.
+ */
+public class McpCommandHandler : Object {
+    
+    private McpServer mcp_server;
+    
+    /**
+     * Creates a new McpCommandHandler instance.
+     * @throws Error If initialization fails
+     */
+    public McpCommandHandler () throws Error {
+        mcp_server = new McpServer ();
+    }
+    
+    /**
+     * Handles MCP mode command by starting the MCP server.
+     *
+     * @return Exit code (0 for success, non-zero for error)
+     */
+    public int handle_mcp_command () {
+        try {
+            // Start MCP server in STDIO mode
+            mcp_server.start.begin ((obj, res) => {
+                try {
+                    bool started = mcp_server.start.end (res);
+                    
+                    if (!started) {
+                        stderr.printf ("Failed to start MCP server\n");
+                        return;
+                    }
+                    
+                    // Server started successfully, keep running
+                } catch (Error e) {
+                    stderr.printf ("MCP server error: %s\n", e.message);
+                }
+            });
+            
+            // Keep main loop running for async operations
+            var loop = new MainLoop ();
+            loop.run ();
+            
+            return 0;
+        } catch (ValaqError e) {
+            stderr.printf ("MCP server error: %s\n", e.message);
+            return 1;
+        } catch (Error e) {
+            stderr.printf ("Unexpected MCP server error: %s\n", e.message);
+            return 1;
+        }
+    }
+    
+    /**
+     * Stops the MCP server.
+     */
+    public async void stop_server () {
+        if (mcp_server != null) {
+            yield mcp_server.stop ();
+        }
+    }
+    
+    /**
+     * Gets the MCP server instance.
+     *
+     * @return The MCP server instance
+     */
+    public McpServer get_server () {
+        return mcp_server;
+    }
+}

+ 123 - 0
src/mcp/server.vala

@@ -0,0 +1,123 @@
+/*
+ * mcp/server.vala
+ *
+ * MCP server implementation for valaq.
+ * This class provides the main MCP server functionality using STDIO communication.
+ */
+
+/**
+ * MCP server wrapper that provides valaq-specific MCP functionality.
+ */
+public class McpServer : Object {
+    
+    private Mcp.Core.Server mcp_core_server;
+    private bool server_initialized = false;
+    private VapiResourceProvider vapi_provider;
+    
+    /**
+     * Creates a new McpServer instance.
+     * @throws Error If initialization fails
+     */
+    public McpServer () throws Error {
+        // Create server info
+        var server_info = new Mcp.Types.Protocol.ServerInfo (
+            "valaq",
+            "1.0.0"
+        );
+
+        server_info.description = "Quick reference of installed Vala libraries and their symbols via Vala VAPI headers";
+        
+        // Create capabilities
+        var capabilities = new Mcp.Types.Protocol.ServerCapabilities ();
+        capabilities.resources = new Mcp.Types.Protocol.ResourcesCapabilities ();
+        capabilities.resources.subscribe = false;  // We don't support subscriptions
+        capabilities.resources.list_changed = false;  // We don't support list changes
+        
+        // Create MCP server
+        mcp_core_server = new Mcp.Core.Server (server_info, capabilities);
+        
+        // Create and register VAPI resource provider
+        vapi_provider = new VapiResourceProvider ();
+        mcp_core_server.resource_manager.register_provider ("vapi", vapi_provider);
+        
+        // Register the VAPI symbol path template
+        var vapi_template = new Mcp.Resources.Types.Resource.ResourceTemplate (
+            "vapi://{vapi}/{symbol-path}",
+            "VAPI Symbol Path"
+        );
+        vapi_template.description = "Access specific symbols within VAPI files using a dot ('.') separated symbol path";
+        vapi_template.mime_type = "application/json";
+        
+        try {
+            mcp_core_server.resource_manager.register_template ("vapi-symbol-path", vapi_template);
+        } catch (Error e) {
+            warning ("Failed to register VAPI symbol path template: %s", e.message);
+        }
+        
+        // Connect server signals
+        mcp_core_server.initialized.connect (on_server_initialized);
+        mcp_core_server.shutdown.connect (on_server_shutdown);
+    }
+    
+    /**
+     * Starts the MCP server in STDIO mode.
+     *
+     * @return true if server started successfully, false otherwise
+     * @throws Error If server startup fails
+     */
+    public async bool start () throws Error {
+        if (server_initialized) {
+            throw new ValaqError.INITIALIZATION_ERROR ("MCP server already started");
+        }
+        
+        // Start the server in STDIO mode
+        yield mcp_core_server.start ();
+        
+        return true;
+    }
+    
+    /**
+     * Stops the MCP server.
+     */
+    public async void stop () {
+        if (mcp_core_server != null) {
+            yield mcp_core_server.stop ();
+            server_initialized = false;
+        }
+    }
+    
+    /**
+     * Gets the underlying MCP core server instance.
+     *
+     * @return The MCP core server instance
+     */
+    public Mcp.Core.Server get_core_server () {
+        return mcp_core_server;
+    }
+    
+    /**
+     * Checks if the server is initialized.
+     *
+     * @return true if server is initialized, false otherwise
+     */
+    public bool is_initialized () {
+        return server_initialized;
+    }
+    
+    /**
+     * Callback for server initialization.
+     */
+    private void on_server_initialized () {
+        server_initialized = true;
+        // Server is ready for MCP communication
+        // Note: We don't print to stdout in MCP mode as it interferes with JSON-RPC communication
+    }
+    
+    /**
+     * Callback for server shutdown.
+     */
+    private void on_server_shutdown () {
+        server_initialized = false;
+        // Cleanup resources if needed
+    }
+}

+ 200 - 0
src/mcp/vapi-resource-provider.vala

@@ -0,0 +1,200 @@
+/*
+ * mcp/vapi-resource-provider.vala
+ *
+ * VAPI resource provider for MCP server.
+ * This class provides MCP resource access to VAPI files.
+ */
+
+/**
+ * VAPI resource provider that exposes VAPI files as MCP resources.
+ *
+ * This class implements the Mcp.Resources.BaseProvider interface to provide
+ * access to VAPI files through the MCP protocol. It handles URI schemes
+ * like "vapi://{vapi}" for root symbols and "vapi://{vapi}/{symbol-path}"
+ * for navigating to specific symbols.
+ */
+public class VapiResourceProvider : Mcp.Resources.BaseProvider {
+    
+    /**
+     * VAPI parser for parsing VAPI files.
+     */
+    private VapiParser parser;
+    
+    /**
+     * JSON formatter for formatting output.
+     */
+    private JsonFormatter formatter;
+    
+    /**
+     * File utilities for VAPI file discovery and resolution.
+     */
+    private FileUtils file_utils;
+    
+    /**
+     * Creates a new VapiResourceProvider instance.
+     */
+    public VapiResourceProvider () {
+        // Initialize base provider
+        base ();
+        
+        // Initialize components
+        parser = new VapiParser ();
+        formatter = new JsonFormatter ();
+        file_utils = new FileUtils ();
+        
+    }
+    
+    /**
+     * Lists all available VAPI resources.
+     *
+     * Discovers all VAPI files in the standard search paths and creates
+     * MCP resource entries for them. Each VAPI file becomes a resource
+     * with a "vapi://{vapi_name}" URI.
+     *
+     * @param cursor Optional cursor for pagination (not used in this implementation)
+     * @return List of MCP resources representing available VAPI files
+     * @throws Error If resource discovery fails
+     */
+    public override async Gee.ArrayList<Mcp.Resources.Types.Resource> list_resources (string? cursor = null) throws Error {
+        var resources = new Gee.ArrayList<Mcp.Resources.Types.Resource> ();
+        
+        // Get list of available VAPI files
+        var vapi_files = file_utils.find_all_vapi_files ();
+        
+        foreach (string vapi_file in vapi_files) {
+            string vapi_name = get_vapi_basename (vapi_file);
+            string uri = @"vapi://$(vapi_name)";
+            
+            var resource = new Mcp.Resources.Types.Resource (uri, vapi_name);
+            resource.mime_type = "application/json";
+            resource.description = @"Reference for Vala library \"$(vapi_name)\"";
+            
+            resources.add (resource);
+        }
+        
+        return resources;
+    }
+    
+    /**
+     * Reads a VAPI resource and returns its contents.
+     *
+     * Handles two types of URIs:
+     * 1. "vapi://{vapi}" - Returns JSON list of root symbols
+     * 2. "vapi://{vapi}/{symbol-path}" - Returns JSON list of symbol children
+     *
+     * @param uri The VAPI resource URI to read
+     * @return Resource contents as JSON text
+     * @throws Error If URI is invalid, VAPI file not found, or parsing fails
+     */
+    public override async Gee.ArrayList<Mcp.Types.Common.ResourceContents> read_resource (string uri) throws Error {
+        // Validate and parse URI
+        if (!uri.has_prefix ("vapi://")) {
+            throw new Mcp.Core.McpError.INVALID_PARAMS ("Invalid VAPI URI format: %s".printf (uri));
+        }
+        
+        // Extract VAPI name and symbol path from URI
+        string uri_path = uri.substring (7); // Remove "vapi://"
+        string[] path_parts = uri_path.split ("/");
+        
+        if (path_parts.length == 0 || path_parts[0] == "") {
+            throw new Mcp.Core.McpError.INVALID_PARAMS ("Missing VAPI name in URI: %s".printf (uri));
+        }
+        
+        string vapi_name = path_parts[0];
+        string? symbol_path = path_parts.length > 1 ? string.joinv (".", path_parts[1:]) : null;
+        
+        // Resolve VAPI file path
+        string? vapi_path = file_utils.resolve_vapi_file (vapi_name);
+        if (vapi_path == null) {
+            throw new Mcp.Core.McpError.RESOURCE_NOT_FOUND ("VAPI file not found: %s".printf (vapi_name));
+        }
+        
+        // Validate path for security
+        if (!file_utils.validate_path (vapi_path)) {
+            throw new Mcp.Core.McpError.INVALID_PARAMS ("Access denied to VAPI file: %s".printf (vapi_name));
+        }
+        
+        // Parse VAPI file
+        Gee.ArrayList<Symbol> root_symbols;
+        SymbolNavigator symbol_navigator;
+        
+        // Parse the VAPI file
+        if (!parser.parse_file (vapi_path)) {
+            throw new Mcp.Core.McpError.PARSE_ERROR ("Failed to parse VAPI file: %s".printf (vapi_name));
+        }
+        
+        root_symbols = parser.get_root_symbols ();
+        symbol_navigator = new SymbolNavigator (root_symbols);
+        
+        var contents = new Gee.ArrayList<Mcp.Types.Common.ResourceContents> ();
+        
+        if (symbol_path == null) {
+            // Return root symbols as JSON
+            string json_data = formatter.format_symbol_list (root_symbols);
+            var content = new Mcp.Types.Common.TextResourceContents (uri, json_data, "application/json");
+            contents.add (content);
+        } else {
+            // Navigate to symbol and return children
+            string[] symbol_parts = symbol_path.split (".");
+            Symbol? target_symbol = symbol_navigator.navigate_to_path (symbol_parts);
+            
+            if (target_symbol == null) {
+                throw new Mcp.Core.McpError.RESOURCE_NOT_FOUND ("Symbol not found: %s".printf (symbol_path));
+            }
+            
+            var child_symbols = symbol_navigator.get_child_symbols (target_symbol);
+            string json_data = formatter.format_symbol_list (child_symbols);
+            var content = new Mcp.Types.Common.TextResourceContents (uri, json_data, "application/json");
+            contents.add (content);
+        }
+        
+        return contents;
+    }
+    
+    /**
+     * Subscribes to resource changes (not implemented).
+     *
+     * This method is part of the BaseProvider interface but is not
+     * implemented for VAPI files since they are typically static.
+     *
+     * @param uri The resource URI to subscribe to
+     * @throws Error NOT_IMPLEMENTED
+     */
+    public override async void subscribe (string uri) throws Error {
+        throw new Mcp.Core.McpError.INVALID_REQUEST ("Resource subscription not supported for VAPI files");
+    }
+    
+    /**
+     * Unsubscribes from resource changes (not implemented).
+     *
+     * This method is part of the BaseProvider interface but is not
+     * implemented for VAPI files since they are typically static.
+     *
+     * @param uri The resource URI to unsubscribe from
+     * @throws Error NOT_IMPLEMENTED
+     */
+    public override async void unsubscribe (string uri) throws Error {
+        throw new Mcp.Core.McpError.INVALID_REQUEST ("Resource subscription not supported for VAPI files");
+    }
+    
+    /**
+     * Gets the VAPI basename from a file path.
+     *
+     * Extracts the VAPI name without the .vapi extension.
+     *
+     * @param vapi_file The full path to the VAPI file
+     * @return The VAPI name without extension
+     */
+    private string get_vapi_basename (string vapi_file) {
+        var file = File.new_for_path (vapi_file);
+        string basename = file.get_basename ();
+        
+        // Remove .vapi extension if present
+        if (basename.has_suffix (".vapi")) {
+            return basename.substring (0, basename.length - 5);
+        }
+        
+        return basename;
+    }
+    
+}

+ 154 - 0
test_mcp_client.py

@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+"""
+Simple test script to verify VAPI resource provider functionality.
+This script sends basic MCP requests to test the VAPI resource provider.
+"""
+
+import json
+import subprocess
+import sys
+import time
+
+def send_mcp_request(method, params=None):
+    """Send an MCP request to the server and return the response."""
+    request = {
+        "jsonrpc": "2.0",
+        "id": 1,
+        "method": method
+    }
+    if params:
+        request["params"] = params
+    
+    # Start the MCP server process
+    process = subprocess.Popen(
+        ["./builddir/valaq", "--mcp"],
+        stdin=subprocess.PIPE,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        text=True
+    )
+    
+    try:
+        # Send the request
+        request_json = json.dumps(request) + "\n"
+        stdout, stderr = process.communicate(input=request_json, timeout=5)
+        
+        if stderr:
+            print(f"STDERR: {stderr}")
+        
+        if stdout:
+            try:
+                response = json.loads(stdout.strip())
+                return response
+            except json.JSONDecodeError as e:
+                print(f"Failed to parse JSON response: {e}")
+                print(f"Raw response: {stdout}")
+                return None
+        else:
+            print("No response from server")
+            return None
+            
+    except subprocess.TimeoutExpired:
+        process.kill()
+        print("Server timed out")
+        return None
+    except Exception as e:
+        print(f"Error communicating with server: {e}")
+        return None
+
+def test_list_resources():
+    """Test listing VAPI resources."""
+    print("Testing list_resources...")
+    response = send_mcp_request("resources/list")
+    
+    if response and "result" in response:
+        resources = response["result"].get("resources", [])
+        print(f"Found {len(resources)} resources:")
+        for resource in resources:
+            print(f"  - {resource['uri']}: {resource.get('description', 'No description')}")
+        return True
+    else:
+        print(f"Failed to list resources: {response}")
+        return False
+
+def test_read_resource():
+    """Test reading a VAPI resource."""
+    print("\nTesting read_resource for test_namespace_methods.vapi...")
+    response = send_mcp_request("resources/read", {"uri": "vapi://test_namespace_methods"})
+    
+    if response and "result" in response:
+        contents = response["result"].get("contents", [])
+        if contents:
+            print(f"Resource content (first 200 chars): {contents[0]['text'][:200]}...")
+            return True
+        else:
+            print("No content in response")
+            return False
+    else:
+        print(f"Failed to read resource: {response}")
+        return False
+
+def test_read_symbol_path():
+    """Test reading a VAPI symbol path."""
+    print("\nTesting read_resource for vapi://test_namespace_methods/Invercargill...")
+    response = send_mcp_request("resources/read", {"uri": "vapi://test_namespace_methods/Invercargill"})
+    
+    if response and "result" in response:
+        contents = response["result"].get("contents", [])
+        if contents:
+            print(f"Symbol path content (first 200 chars): {contents[0]['text'][:200]}...")
+            return True
+        else:
+            print("No content in response")
+            return False
+    else:
+        print(f"Failed to read symbol path: {response}")
+        return False
+
+def test_list_templates():
+    """Test listing resource templates."""
+    print("\nTesting resources/templates/list...")
+    response = send_mcp_request("resources/templates/list")
+    
+    if response and "result" in response:
+        templates = response["result"].get("resourceTemplates", [])
+        print(f"Found {len(templates)} templates:")
+        for template in templates:
+            print(f"  - {template['name']}: {template.get('uriTemplate', 'No URI template')}")
+            print(f"    Description: {template.get('description', 'No description')}")
+        return True
+    else:
+        print(f"Failed to list templates: {response}")
+        return False
+
+def main():
+    """Run all tests."""
+    print("Testing VAPI Resource Provider MCP Implementation")
+    print("=" * 50)
+    
+    tests = [
+        test_list_resources,
+        test_read_resource,
+        test_read_symbol_path,
+        test_list_templates
+    ]
+    
+    passed = 0
+    total = len(tests)
+    
+    for test in tests:
+        if test():
+            passed += 1
+        time.sleep(1)  # Small delay between tests
+    
+    print(f"\nTest Results: {passed}/{total} tests passed")
+    
+    if passed == total:
+        print("All tests passed! VAPI resource provider is working correctly.")
+        return 0
+    else:
+        print("Some tests failed. Please check the implementation.")
+        return 1
+
+if __name__ == "__main__":
+    sys.exit(main())