# MCP Vala Library Developer Guide This guide provides comprehensive information for developers who want to use the MCP Vala library to create Model Context Protocol (MCP) servers. ## Table of Contents 1. [Architecture Overview](#architecture-overview) 2. [Getting Started](#getting-started) 3. [Core Concepts](#core-concepts) 4. [Implementing Resources](#implementing-resources) 5. [Implementing Tools](#implementing-tools) 6. [Implementing Prompts](#implementing-prompts) 7. [Best Practices](#best-practices) 8. [Error Handling](#error-handling) 9. [Testing and Debugging](#testing-and-debugging) 10. [Extending the Library](#extending-the-library) 11. [Troubleshooting](#troubleshooting) ## Architecture Overview The MCP Vala library follows a layered architecture that separates concerns and provides clean interfaces for extensibility: ``` ┌─────────────────────────────────────────────────────────────┐ │ Application Layer │ │ ┌─────────────────────────────────────────────┐ │ │ │ Your Server Code │ │ │ └─────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────┐ │ │ │ MCP Vala Library │ │ │ │ ┌─────────────────────────────┐ │ │ │ │ │ Core Components │ │ │ │ │ │ • Server │ │ │ │ │ │ • Jsonrpc.Server │ │ │ │ │ └─────────────────────────────┘ │ │ │ │ ┌─────────────────────────────┐ │ │ │ │ │ Managers │ │ │ │ │ │ • ResourceManager │ │ │ │ │ │ • ToolManager │ │ │ │ │ │ • PromptManager │ │ │ │ │ └─────────────────────────────┘ │ │ │ │ ┌─────────────────────────────┐ │ │ │ │ │ Base Classes │ │ │ │ │ │ • BaseExecutor │ │ │ │ │ │ • BaseProvider │ │ │ │ │ │ • BaseTemplate │ │ │ │ │ └─────────────────────────────┘ │ │ │ └─────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────┐ │ │ │ jsonrpc-glib-1.0 Library │ │ │ │ • Jsonrpc.Server │ │ │ │ • Jsonrpc.Client │ │ │ │ • JSON-RPC 2.0 Protocol │ │ │ │ • STDIO Transport │ │ │ └─────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────┘ ``` ### Key Components - **Core.Server**: Main server class that orchestrates all MCP functionality using Jsonrpc.Server - **Jsonrpc.Server**: Standard JSON-RPC 2.0 server implementation from jsonrpc-glib-1.0 - **Managers**: Coordinate resources, tools, and prompts - **Base Classes**: Provide common functionality for custom implementations ## Getting Started ### Prerequisites - Vala compiler (valac) >= 0.56 - Required dependencies: - glib-2.0 >= 2.70 - gobject-2.0 >= 2.70 - gio-2.0 >= 2.70 - json-glib-1.0 >= 1.6 - jsonrpc-glib-1.0 >= 3.34 - gio-unix-2.0 (for STDIO transport) - gee-0.8 (for collections) ### Basic Server Setup ```vala using Mcp; public class MyServer : GLib.Object { private Mcp.Core.Server server; public MyServer () { // Create server information var server_info = new Mcp.Types.Protocol.ServerInfo ( "my-server", "1.0.0", "My custom MCP server" ); // Create capabilities var capabilities = new Mcp.Types.Protocol.ServerCapabilities (); capabilities.logging = true; // Create server server = new Mcp.Core.Server (server_info, capabilities); } public async int run () { // Start the server bool started = yield server.start (); if (!started) { stderr.printf ("Failed to start server\n"); return 1; } // Run main loop var main_loop = new MainLoop (); main_loop.run (); return 0; } } int main (string[] args) { var server = new MyServer (); return server.run (); } ``` ## Core Concepts ### Resources Resources represent data that can be read by clients: ```vala // Resource definition var resource = new Mcp.Resources.Types.Resource ( "file:///path/to/file.txt", "My File" ); resource.description = "A sample text file"; resource.mime_type = "text/plain"; // Resource contents var contents = new Mcp.Types.Common.TextResourceContents ( resource.uri, "Hello, World!" ); ``` ### Tools Tools are functions that clients can execute: ```vala // Tool definition var input_schema = new Json.Object (); input_schema.set_string_member ("type", "object"); var definition = new Mcp.Tools.Types.ToolDefinition ( "my_tool", input_schema ); // Tool execution result var result = new Mcp.Tools.Types.CallToolResult (); var content = new Mcp.Types.Common.TextContent ("Operation completed"); result.content.add (content); ``` ### Prompts Prompts are templates for generating consistent messages: ```vala // Prompt definition var prompt = new Mcp.Prompts.Types.PromptDefinition ("my_prompt"); prompt.description = "A custom prompt template"; // Prompt result var result = new Mcp.Prompts.Types.GetPromptResult (); var message = new Mcp.Prompts.Types.PromptMessage ( "user", new Mcp.Types.Common.TextContent ("Generated prompt text") ); result.messages.add (message); ``` ## Implementing Resources ### Creating a Resource Provider Implement the `Mcp.Resources.Provider` interface or extend `Mcp.Resources.BaseProvider`: ```vala public class MyResourceProvider : Mcp.Resources.BaseProvider { private HashTable data_store; public MyResourceProvider () { data_store = new HashTable (str_hash, str_equal); // Initialize with some data data_store.insert ("my://item1", "Content of item 1"); data_store.insert ("my://item2", "Content of item 2"); } public override async Gee.ArrayList list_resources (string? cursor) throws Error { var resources = new Gee.ArrayList (); foreach (var uri in data_store.get_keys ()) { var resource = new Mcp.Resources.Types.Resource (uri, uri); resource.mime_type = "text/plain"; resource.size = data_store[uri].length; resources.add (resource); } return resources; } public override async Mcp.Types.Common.ResourceContents read_resource (string uri) throws Error { if (!data_store.contains (uri)) { throw new Mcp.Core.McpError.RESOURCE_NOT_FOUND ("Resource not found: %s".printf (uri)); } return new Mcp.Types.Common.TextResourceContents (uri, data_store[uri]); } } // Register with server server.resource_manager.register_provider ("my_provider", new MyResourceProvider ()); ``` ### Resource Subscription Support Enable clients to receive updates when resources change: ```vala public override async void subscribe (string uri) throws Error { // Track subscription yield base.subscribe (uri); // Emit update when data changes notify_resource_updated (uri); } ``` ## Implementing Tools ### Creating a Tool Executor Implement the `Mcp.Tools.Executor` interface or extend `Mcp.Tools.BaseExecutor`: ```vala public class MyTool : Mcp.Tools.BaseExecutor { public MyTool () { // Define input schema var input_schema = new Json.Object (); input_schema.set_string_member ("type", "object"); var properties = new Json.Object (); var text_prop = new Json.Object (); text_prop.set_string_member ("type", "string"); text_prop.set_string_member ("description", "Text to process"); properties.set_object_member ("text", text_prop); input_schema.set_object_member ("properties", properties); var required = new Json.Array (); required.add_string_element ("text"); input_schema.set_array_member ("required", required); var definition = new Mcp.Tools.Types.ToolDefinition ("my_tool", input_schema); definition.description = "Processes text input"; base (definition); } protected override async Mcp.Tools.Types.CallToolResult do_execute (Json.Object arguments) throws Error { string text = get_string_arg (arguments, "text"); // Process the text string processed = text.up (); return create_text_result ("Processed: %s".printf (processed)); } } // Register with server server.tool_manager.register_executor ("my_tool", new MyTool ()); ``` ### Advanced Tool Features #### Structured Results Return structured data with your results: ```vala var structured_data = new Json.Object (); structured_data.set_string_member ("original", text); structured_data.set_string_member ("processed", processed); structured_data.set_int_member ("length", processed.length); return create_structured_result ("Text processed successfully", structured_data); ``` #### Error Handling Handle errors gracefully: ```vala protected override async Mcp.Tools.Types.CallToolResult do_execute (Json.Object arguments) throws Error { try { // Validate input string text = get_string_arg (arguments, "text"); if (text.length == 0) { throw new Mcp.Core.McpError.INVALID_PARAMS ("Text cannot be empty"); } // Process string result = process_text (text); return create_text_result (result); } catch (Error e) { // Return error result return create_error_result (e); } } ``` ## Implementing Prompts ### Creating a Prompt Template Implement the `Mcp.Prompts.Template` interface or extend `Mcp.Prompts.BaseTemplate`: ```vala public class MyPrompt : Mcp.Prompts.BaseTemplate { public MyPrompt () { base ("my_prompt", "My Prompt", "A custom prompt template"); // Add arguments add_argument ("topic", "Topic", "The topic to generate content for", true); add_argument ("style", "Style", "Writing style (formal, casual, creative)", false); add_argument ("length", "Length", "Desired length (short, medium, long)", false); } protected override Gee.ArrayList generate_messages (Json.Object arguments) { var messages = new Gee.ArrayList (); string topic = get_string_arg (arguments, "topic"); string? style = get_string_arg (arguments, "style", "neutral"); string? length = get_string_arg (arguments, "length", "medium"); string template = """Generate {{style}} content about {{topic}}. Target length: {{length}}. Please provide: 1. Introduction to the topic 2. Key points about {{topic}} 3. Analysis or insights 4. Conclusion or summary"""; messages.add (create_user_message (template, arguments)); return messages; } private string? get_string_arg (Json.Object arguments, string name, string? default_value = null) { if (arguments.has_member (name)) { return arguments.get_string_member (name); } return default_value; } } // Register with server server.prompt_manager.register_template ("my_prompt", new MyPrompt ()); ``` ### Advanced Prompt Features #### Conditional Content Use conditional blocks in templates: ```vala string template = """Generate content about {{topic}}. {{#formal}} Write in a formal, professional tone suitable for business communication. {{/formal}} {{#casual}} Write in a relaxed, conversational tone suitable for informal settings. {{/casual}} {{#creative}} Use creative language and imaginative expressions. {{/creative}}"""; ``` #### Multi-Message Prompts Create conversations with multiple messages: ```vala protected override Gee.ArrayList generate_messages (Json.Object arguments) { var messages = new Gee.ArrayList (); // System message var system_content = new Mcp.Types.Common.TextContent ( "You are a helpful assistant specializing in {{topic}}." ); var system_msg = new Mcp.Prompts.Types.PromptMessage ("system", system_content); messages.add (system_msg); // User message var user_content = new Mcp.Types.Common.TextContent ( "Tell me about {{topic}}." ); var user_msg = new Mcp.Prompts.Types.PromptMessage ("user", user_content); messages.add (user_msg); return messages; } ``` ## Best Practices ### Server Initialization 1. **Set up capabilities properly**: ```vala var capabilities = new Mcp.Types.Protocol.ServerCapabilities (); capabilities.logging = true; // Always enable logging ``` 2. **Handle initialization errors**: ```vala try { server = new Mcp.Core.Server (server_info, capabilities); } catch (Error e) { stderr.printf ("Failed to create server: %s\n", e.message); return; } ``` 3. **Register components before starting**: ```vala // Register all providers, executors, and templates setup_resources (); setup_tools (); setup_prompts (); // Then start the server yield server.start (); ``` ### Resource Management 1. **Use appropriate MIME types**: ```vala resource.mime_type = "text/plain"; // Text files resource.mime_type = "application/json"; // JSON data resource.mime_type = "image/png"; // Images ``` 2. **Handle large resources efficiently**: ```vala public override async Mcp.Types.Common.ResourceContents read_resource (string uri) throws Error { // Check size before loading var metadata = get_resource_metadata (uri); if (metadata != null && metadata.size > 1024 * 1024) { // 1MB // Consider streaming for large files return yield read_large_resource_streaming (uri); } // Normal loading for smaller files return yield read_resource_normal (uri); } ``` 3. **Implement subscription notifications**: ```vala // When data changes data_store.insert (uri, new_content); // Notify all subscribers notify_resource_updated (uri); // Trigger list change notification list_changed (); ``` ### Tool Implementation 1. **Validate all inputs**: ```vala protected override void validate_arguments (Json.Object arguments) throws Error { // Call base validation base.validate_arguments (arguments); // Add custom validation if (arguments.has_member ("custom_param")) { string value = arguments.get_string_member ("custom_param"); if (!is_valid_custom_param (value)) { throw new Mcp.Core.McpError.INVALID_PARAMS ("Invalid custom_param"); } } } ``` 2. **Use structured results**: ```vala var structured_data = new Json.Object (); structured_data.set_string_member ("status", "success"); structured_data.set_object_member ("result", result_object); structured_data.set_double_member ("duration_ms", elapsed_ms); return create_structured_result ("Operation completed", structured_data); ``` 3. **Handle long-running operations**: ```vala protected override async Mcp.Tools.Types.CallToolResult do_execute (Json.Object arguments) throws Error { // Start progress notification var progress = new Json.Object (); progress.set_string_member ("status", "processing"); send_progress_notification (progress); // Perform long operation var result = yield perform_long_operation (arguments); // Final progress progress.set_string_member ("status", "completed"); send_progress_notification (progress); return result; } ``` ### Prompt Design 1. **Keep templates focused**: ```vala // Good: Specific and clear add_argument ("topic", "Topic", "The specific topic to address", true); // Avoid: Vague arguments // add_argument ("info", "Information", "Some information", true); // Too generic ``` 2. **Use descriptive names**: ```vala // Good: Clear purpose add_argument ("target_audience", "Target Audience", "Who the content is for", false); // Avoid: Generic names // add_argument ("param1", "Parameter 1", "First parameter", true); ``` 3. **Provide reasonable defaults**: ```vala add_argument ("style", "Style", "Writing style", false, "neutral"); add_argument ("length", "Length", "Content length", false, "medium"); ``` ## Error Handling ### Error Types The library provides these error types: ```vala try { // Your code } catch (Mcp.Core.McpError.INVALID_PARAMS e) { // Invalid method parameters } catch (Mcp.Core.McpError.METHOD_NOT_FOUND e) { // Method doesn't exist } catch (Mcp.Core.McpError.RESOURCE_NOT_FOUND e) { // Resource doesn't exist } catch (Mcp.Core.McpError.INTERNAL_ERROR e) { // Internal server error } catch (Mcp.Core.McpError.TRANSPORT_ERROR e) { // Communication error } catch (Mcp.Core.McpError.PARSE_ERROR e) { // JSON parsing error } ``` ### Best Practices for Error Handling 1. **Be specific in error messages**: ```vala // Good throw new Mcp.Core.McpError.INVALID_PARAMS ( "Temperature must be between -273.15 and 1000 Kelvin" ); // Bad throw new Mcp.Core.McpError.INVALID_PARAMS ("Invalid temperature"); ``` 2. **Include context in errors**: ```vala throw new Mcp.Core.McpError.RESOURCE_NOT_FOUND ( "File not found: %s (working directory: %s)".printf (path, working_dir) ); ``` 3. **Use appropriate error codes**: ```vala // Client error (bad request) throw new Mcp.Core.McpError.INVALID_PARAMS ("..."); // Server error (something went wrong) throw new Mcp.Core.McpError.INTERNAL_ERROR ("..."); ``` ## Testing and Debugging ### Unit Testing Test your components individually: ```vala void test_tool_execution () { var tool = new MyTool (); var arguments = new Json.Object (); arguments.set_string_member ("text", "test"); // Test synchronous execution tool.execute.begin (arguments, (obj, res) => { assert (res.is_error == false); var content = res.content[0] as Mcp.Types.Common.TextContent; assert (content.text.contains ("Processed:")); }); } ``` ### Integration Testing Test the full server: ```vala // Test with mock client void test_server_integration () { var server = new MyServer (); server.start.begin (); // Send JSON-RPC messages var request = create_json_rpc_request ("tools/list", {}); var response = server.handle_request (request); // Verify response assert (response != null); } ``` ### Debugging Tips 1. **Enable logging**: ```vala var capabilities = new Mcp.Types.Protocol.ServerCapabilities (); capabilities.logging = true; // Always enable for debugging ``` 2. **Use print statements**: ```vala public override async Mcp.Tools.Types.CallToolResult do_execute (Json.Object arguments) throws Error { print ("Executing tool with arguments: %s\n", arguments.to_string (null)); var result = yield perform_operation (arguments); print ("Tool execution completed: %s\n", result.is_error ? "ERROR" : "SUCCESS"); return result; } ``` 3. **Test with real MCP clients**: - Use Claude Desktop or other MCP-compatible clients - Test all your tools, resources, and prompts - Verify JSON-RPC message flow ## Extending the Library ### Jsonrpc Integration The library now uses Jsonrpc.Server from jsonrpc-glib-1.0 for JSON-RPC communication. Custom transport implementations are typically not needed as the standard implementation handles: - STDIO communication - JSON-RPC 2.0 protocol compliance - Error handling and response formatting - Async method dispatch ### Custom Managers Create specialized managers for your domain: ### Custom Managers Create specialized managers for your domain: ```vala public class DatabaseResourceManager : Mcp.Resources.Manager { private DatabaseConnection db; public DatabaseResourceManager (DatabaseConnection db) { this.db = db; } public override async Gee.ArrayList list_resources (string? cursor) throws Error { var resources = new Gee.ArrayList (); // Query database var results = db.query_resources (cursor); foreach (var row in results) { var resource = new Mcp.Resources.Types.Resource (row["uri"], row["name"]); resources.add (resource); } return resources; } } ``` ## Troubleshooting ### Common Issues #### Server Won't Start **Problem**: Server fails to start **Causes**: - Missing dependencies - Port already in use - Invalid server configuration **Solutions**: ```bash # Check dependencies valac --pkg mcp-vala --pkg json-glib-1.0 --pkg jsonrpc-glib-1.0 --pkg gio-unix-2.0 --pkg gee-0.8 your-server.vala # Check with strace strace -e trace=network your-server # Use debug build meson configure -Ddebug=true ``` #### Resources Not Appearing **Problem**: Registered resources don't show up in client **Causes**: - Provider not registered correctly - list_resources() method throwing errors - URI scheme issues **Solutions**: ```vala // Check registration try { server.resource_manager.register_provider ("my_provider", provider); print ("Provider registered successfully\n"); } catch (Error e) { stderr.printf ("Failed to register provider: %s\n", e.message); } // Check URIs var resource = new Mcp.Resources.Types.Resource ("file:///valid/path", "Name"); // NOT: "invalid-uri" (missing scheme) // NOT: "file://invalid path" (missing encoding) ``` #### Tool Execution Fails **Problem**: Tools return errors or don't execute **Causes**: - Invalid JSON schemas - Missing required arguments - Async method not yielding **Solutions**: ```vala // Validate schema public Mcp.Tools.Types.ToolDefinition get_definition () throws Error { if (definition.input_schema == null) { throw new Mcp.Core.McpError.INTERNAL_ERROR ("Input schema not initialized"); } return definition; } // Check async execution protected override async Mcp.Tools.Types.CallToolResult do_execute (Json.Object arguments) throws Error { // Always yield, even for sync operations var result = process_sync (arguments); return result; // Missing yield! } ``` #### Prompt Generation Issues **Problem**: Prompts don't generate correctly **Causes**: - Template syntax errors - Missing argument substitutions - Invalid message structures **Solutions**: ```vala // Debug template protected override Gee.ArrayList generate_messages (Json.Object arguments) { print ("Generating prompt with arguments: %s\n", arguments.to_string (null)); var messages = new Gee.ArrayList (); // Debug template substitution string template = get_template (); string result = substitute_variables (template, arguments); print ("Template result: %s\n", result); var content = new Mcp.Types.Common.TextContent (result); var message = new Mcp.Prompts.Types.PromptMessage ("user", content); messages.add (message); return messages; } ``` ### Performance Issues #### Memory Usage **Problem**: High memory consumption **Solutions**: - Stream large resources instead of loading entirely - Use weak references for cached data - Clear caches when not needed ```vala // Good: Streaming public override async Mcp.Types.Common.BlobResourceContents read_resource (string uri) throws Error { var stream = yield open_resource_stream (uri); var buffer = new uint8[4096]; // Small, fixed buffer // Process in chunks do { size_t bytes_read = yield stream.read_async (buffer); if (bytes_read == 0) break; // Process chunk yield process_chunk (buffer[0:bytes_read]); } while (true); } // Bad: Loading everything public override async Mcp.Types.Common.BlobResourceContents read_resource (string uri) throws Error { var file = File.new_for_uri (uri); uint8[] contents; yield file.load_contents_async (null, out contents); // Loads entire file into memory return new Mcp.Types.Common.BlobResourceContents (uri, contents); } ``` #### Concurrency **Problem**: Blocking operations affect performance **Solutions**: - Use async methods properly - Implement cancellation support - Use thread pools for CPU-intensive work ```vala // Good: Non-blocking public override async Mcp.Tools.Types.CallToolResult do_execute (Json.Object arguments) throws Error { // Yield to allow other operations var result = yield perform_long_operation (arguments); return result; } // Bad: Blocking public override async Mcp.Tools.Types.CallToolResult do_execute (Json.Object arguments) throws Error { // This blocks the entire server! var result = perform_blocking_operation (arguments); return result; } ``` ### Getting Help 1. **Check the examples**: All examples demonstrate best practices 2. **Review the source code**: The library is fully documented 3. **Enable debug logging**: Use the logging capabilities 4. **Test with real clients**: Verify with MCP-compatible tools ## Conclusion The MCP Vala library provides a solid foundation for building MCP servers. By following the patterns and best practices in this guide, you can create robust, efficient, and maintainable MCP servers that integrate seamlessly with the Model Context Protocol ecosystem. The library now uses jsonrpc-glib-1.0 for standard-compliant JSON-RPC 2.0 communication, providing: - Better error handling - Improved IO reliability - Standard protocol compliance - Performance optimizations Remember: - Start simple and add complexity incrementally - Test each component independently - Handle errors gracefully - Use the provided base classes - Follow Vala naming conventions - Document your custom extensions Happy coding!