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.
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]
The MCP mode integrates with existing valaq components:
--mcp flagMcp.Core.Server with valaq-specific logicThe MCP mode will use the vapi:// URI scheme with the following patterns:
Root VAPI Listing: vapi://{vapi}
vapi://gtk+-3.0Symbol Navigation: vapi://{vapi}/{symbol-path}
vapi://gtk+-3.0/Gtk.Window.show| 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 |
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);
}
}
The resource content follows the existing valaq JSON output format:
{
"result_type": "symbol_list",
"symbols": [
{
"name": "Gtk",
"type": "namespace",
"access": "public",
"full_path": "Gtk",
"child_count": 15
}
]
}
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;
}
}
}
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 ();
}
}
// 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");
}
// 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
}
# 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,
)
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...]
Mcp.Core.McpError domain with proper error codesValaqError with MCP error responsesprint() for server status messagesstderr.printf() for error messagesCreate MCP Server Classes
McpServer wrapper classMcpModeCommandHandler for MCP modeImplement VAPI Resource Provider
VapiResourceProvider classExtend Command-Line Interface
--mcp flag to ArgumentParserCommandHandler for MCP mode detectionIntegrate Components
Testing
Documentation
Performance Optimization
Advanced Features
// 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
}
}
}
# 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"
}
]
}
}
vapi:// URIs strictlyThis design provides a comprehensive approach to implementing MCP mode in valaq that:
The implementation follows valaq's modular design principles while adding MCP capabilities through well-defined interfaces and standard protocols.