/* * Calculator MCP Server Example * * This example demonstrates how to create an MCP server that provides * mathematical calculation tools. It shows how to implement and register * multiple tools with different capabilities. * * Compile with: * valac --pkg mcp-vala --pkg json-glib-1.0 --pkg gee-0.8 --pkg posix calculator-server.vala -o calculator-server */ using Mcp; using Posix; /** * Calculator server that provides mathematical tools. */ public class CalculatorServer : GLib.Object { private Mcp.Core.Server server; /** * Creates a new CalculatorServer. */ public CalculatorServer () { // Create server information var server_info = new Mcp.Types.Protocol.ServerInfo ( "calculator-server", "1.0.0" ); server_info.description = "MCP server providing mathematical calculation tools"; // Create server capabilities with tools enabled var capabilities = new Mcp.Types.Protocol.ServerCapabilities (); capabilities.logging = true; capabilities.tools = new Mcp.Types.Protocol.ToolsCapabilities (); // Create server server = new Mcp.Core.Server (server_info, capabilities); // Register calculation tools setup_tools (); } /** * Sets up mathematical calculation tools. */ private void setup_tools () { // Register basic calculator tool var basic_calculator = new BasicCalculatorTool (); try { server.tool_manager.register_executor ("basic_calculator", basic_calculator); } catch (Error e) { // Error handling } // Register advanced calculator tool var advanced_calculator = new AdvancedCalculatorTool (); try { server.tool_manager.register_executor ("advanced_calculator", advanced_calculator); } catch (Error e) { // Error handling } // Register unit converter tool var unit_converter = new UnitConverterTool (); try { server.tool_manager.register_executor ("unit_converter", unit_converter); } catch (Error e) { // Error handling } } /** * Runs the server. * * @return Exit code */ public async int run () { try { // Start the server bool started = yield server.start (); if (!started) { return 1; } // Calculator server started successfully // Available tools: basic_calculator, advanced_calculator, unit_converter // Waiting for MCP client connections... // Run the main loop var main_loop = new MainLoop (); // Connect shutdown signal server.shutdown.connect (() => { main_loop.quit (); }); main_loop.run (); return 0; } catch (Error e) { return 1; } } } /** * Basic calculator tool for simple arithmetic operations. */ public class BasicCalculatorTool : Mcp.Tools.BaseExecutor { /** * Creates a new BasicCalculatorTool. */ public BasicCalculatorTool () { // Create tool definition using Variant var builder = new VariantBuilder (new VariantType ("a{sv}")); // Add schema properties builder.add ("{sv}", "type", new Variant.string ("object")); builder.add ("{sv}", "description", new Variant.string ("Perform basic arithmetic operations")); var properties_builder = new VariantBuilder (new VariantType ("a{sv}")); // Operation property var op_prop_builder = new VariantBuilder (new VariantType ("a{sv}")); op_prop_builder.add ("{sv}", "type", new Variant.string ("string")); op_prop_builder.add ("{sv}", "description", new Variant.string ("Arithmetic operation to perform")); var enum_array_builder = new VariantBuilder (new VariantType ("as")); enum_array_builder.add ("s", "add"); enum_array_builder.add ("s", "subtract"); enum_array_builder.add ("s", "multiply"); enum_array_builder.add ("s", "divide"); enum_array_builder.add ("s", "power"); enum_array_builder.add ("s", "modulo"); op_prop_builder.add ("{sv}", "enum", enum_array_builder.end ()); properties_builder.add ("{sv}", "operation", op_prop_builder.end ()); // Operand 1 property var operand1_prop_builder = new VariantBuilder (new VariantType ("a{sv}")); operand1_prop_builder.add ("{sv}", "type", new Variant.string ("number")); operand1_prop_builder.add ("{sv}", "description", new Variant.string ("First operand")); properties_builder.add ("{sv}", "operand1", operand1_prop_builder.end ()); // Operand 2 property var operand2_prop_builder = new VariantBuilder (new VariantType ("a{sv}")); operand2_prop_builder.add ("{sv}", "type", new Variant.string ("number")); operand2_prop_builder.add ("{sv}", "description", new Variant.string ("Second operand")); properties_builder.add ("{sv}", "operand2", operand2_prop_builder.end ()); builder.add ("{sv}", "properties", properties_builder.end ()); var required_array_builder = new VariantBuilder (new VariantType ("as")); required_array_builder.add ("s", "operation"); required_array_builder.add ("s", "operand1"); required_array_builder.add ("s", "operand2"); builder.add ("{sv}", "required", required_array_builder.end ()); var input_schema = builder.end (); var definition = new Mcp.Tools.Types.ToolDefinition ("basic_calculator", input_schema); definition.title = "Basic Calculator"; definition.description = "Perform basic arithmetic operations (add, subtract, multiply, divide, power, modulo)"; base (definition); } /** * {@inheritDoc} */ protected override async Mcp.Tools.Types.CallToolResult do_execute (GLib.Variant arguments) throws Error { string operation = get_string_arg (arguments, "operation"); double operand1 = get_double_arg (arguments, "operand1"); double operand2 = get_double_arg (arguments, "operand2"); double result = 0.0; switch (operation) { case "add": result = operand1 + operand2; break; case "subtract": result = operand1 - operand2; break; case "multiply": result = operand1 * operand2; break; case "divide": if (operand2 == 0) { throw new Mcp.Core.McpError.INVALID_PARAMS ("Division by zero is not allowed"); } result = operand1 / operand2; break; case "power": result = Math.pow (operand1, operand2); break; case "modulo": if (operand2 == 0) { throw new Mcp.Core.McpError.INVALID_PARAMS ("Modulo by zero is not allowed"); } result = (int)operand1 % (int)operand2; break; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown operation: %s".printf (operation)); } // Create structured result using Variant var structured_data_builder = new VariantBuilder (new VariantType ("a{sv}")); structured_data_builder.add ("{sv}", "result", new Variant.double (result)); structured_data_builder.add ("{sv}", "operation", new Variant.string (operation)); structured_data_builder.add ("{sv}", "operand1", new Variant.double (operand1)); structured_data_builder.add ("{sv}", "operand2", new Variant.double (operand2)); structured_data_builder.add ("{sv}", "expression", new Variant.string ("%.2f %s %.2f = %.2f".printf (operand1, operation, operand2, result))); var structured_data = structured_data_builder.end (); return create_structured_result ("Calculation result: %.2f".printf (result), structured_data); } } /** * Advanced calculator tool for complex mathematical operations. */ public class AdvancedCalculatorTool : Mcp.Tools.BaseExecutor { /** * Creates a new AdvancedCalculatorTool. */ public AdvancedCalculatorTool () { // Create tool definition using Variant var builder = new VariantBuilder (new VariantType ("a{sv}")); // Add schema properties builder.add ("{sv}", "type", new Variant.string ("object")); builder.add ("{sv}", "description", new Variant.string ("Perform advanced mathematical operations")); var properties_builder = new VariantBuilder (new VariantType ("a{sv}")); // Function property var func_prop_builder = new VariantBuilder (new VariantType ("a{sv}")); func_prop_builder.add ("{sv}", "type", new Variant.string ("string")); func_prop_builder.add ("{sv}", "description", new Variant.string ("Mathematical function to apply")); var enum_array_builder = new VariantBuilder (new VariantType ("as")); enum_array_builder.add ("s", "sin"); enum_array_builder.add ("s", "cos"); enum_array_builder.add ("s", "tan"); enum_array_builder.add ("s", "log"); enum_array_builder.add ("s", "ln"); enum_array_builder.add ("s", "sqrt"); enum_array_builder.add ("s", "abs"); enum_array_builder.add ("s", "ceil"); enum_array_builder.add ("s", "floor"); enum_array_builder.add ("s", "round"); enum_array_builder.add ("s", "exp"); func_prop_builder.add ("{sv}", "enum", enum_array_builder.end ()); properties_builder.add ("{sv}", "function", func_prop_builder.end ()); // Value property var value_prop_builder = new VariantBuilder (new VariantType ("a{sv}")); value_prop_builder.add ("{sv}", "type", new Variant.string ("number")); value_prop_builder.add ("{sv}", "description", new Variant.string ("Value to apply the function to")); properties_builder.add ("{sv}", "value", value_prop_builder.end ()); // Degrees property (for trigonometric functions) var degrees_prop_builder = new VariantBuilder (new VariantType ("a{sv}")); degrees_prop_builder.add ("{sv}", "type", new Variant.string ("boolean")); degrees_prop_builder.add ("{sv}", "description", new Variant.string ("Whether to use degrees instead of radians (for trigonometric functions)")); degrees_prop_builder.add ("{sv}", "default", new Variant.boolean (false)); properties_builder.add ("{sv}", "degrees", degrees_prop_builder.end ()); builder.add ("{sv}", "properties", properties_builder.end ()); var required_array_builder = new VariantBuilder (new VariantType ("as")); required_array_builder.add ("s", "function"); required_array_builder.add ("s", "value"); builder.add ("{sv}", "required", required_array_builder.end ()); var input_schema = builder.end (); var definition = new Mcp.Tools.Types.ToolDefinition ("advanced_calculator", input_schema); definition.title = "Advanced Calculator"; definition.description = "Perform advanced mathematical operations (trigonometric, logarithmic, etc.)"; base (definition); } /** * {@inheritDoc} */ protected override async Mcp.Tools.Types.CallToolResult do_execute (GLib.Variant arguments) throws Error { string function = get_string_arg (arguments, "function"); double value = get_double_arg (arguments, "value"); bool degrees = arguments.lookup_value ("degrees", null) != null ? arguments.lookup_value ("degrees", new VariantType ("b")).get_boolean () : false; double result = 0.0; // Convert to radians if degrees are specified for trigonometric functions double radians = degrees ? value * Math.PI / 180.0 : value; switch (function) { case "sin": result = Math.sin (radians); break; case "cos": result = Math.cos (radians); break; case "tan": result = Math.tan (radians); break; case "log": if (value <= 0) { throw new Mcp.Core.McpError.INVALID_PARAMS ("Logarithm is only defined for positive numbers"); } result = Math.log10 (value); break; case "ln": if (value <= 0) { throw new Mcp.Core.McpError.INVALID_PARAMS ("Natural logarithm is only defined for positive numbers"); } result = Math.log (value); break; case "sqrt": if (value < 0) { throw new Mcp.Core.McpError.INVALID_PARAMS ("Square root is not defined for negative numbers"); } result = Math.sqrt (value); break; case "abs": result = Math.fabs (value); break; case "ceil": result = Math.ceil (value); break; case "floor": result = Math.floor (value); break; case "round": result = Math.round (value); break; case "exp": result = Math.exp (value); break; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown function: %s".printf (function)); } // Create structured result using Variant var structured_data_builder = new VariantBuilder (new VariantType ("a{sv}")); structured_data_builder.add ("{sv}", "result", new Variant.double (result)); structured_data_builder.add ("{sv}", "function", new Variant.string (function)); structured_data_builder.add ("{sv}", "input", new Variant.double (value)); if (degrees && (function == "sin" || function == "cos" || function == "tan")) { structured_data_builder.add ("{sv}", "degrees", new Variant.boolean (true)); structured_data_builder.add ("{sv}", "expression", new Variant.string ("%s(%.2f°) = %.6f".printf (function, value, result))); } else { structured_data_builder.add ("{sv}", "expression", new Variant.string ("%s(%.2f) = %.6f".printf (function, value, result))); } var structured_data = structured_data_builder.end (); return create_structured_result ("Advanced calculation result: %.6f".printf (result), structured_data); } } /** * Unit converter tool for converting between different units. */ public class UnitConverterTool : Mcp.Tools.BaseExecutor { /** * Creates a new UnitConverterTool. */ public UnitConverterTool () { // Create tool definition using Variant var builder = new VariantBuilder (new VariantType ("a{sv}")); // Add schema properties builder.add ("{sv}", "type", new Variant.string ("object")); builder.add ("{sv}", "description", new Variant.string ("Convert between different units of measurement")); var properties_builder = new VariantBuilder (new VariantType ("a{sv}")); // Conversion type property var type_prop_builder = new VariantBuilder (new VariantType ("a{sv}")); type_prop_builder.add ("{sv}", "type", new Variant.string ("string")); type_prop_builder.add ("{sv}", "description", new Variant.string ("Type of conversion")); var enum_array_builder = new VariantBuilder (new VariantType ("as")); enum_array_builder.add ("s", "temperature"); enum_array_builder.add ("s", "length"); enum_array_builder.add ("s", "weight"); enum_array_builder.add ("s", "volume"); enum_array_builder.add ("s", "data"); type_prop_builder.add ("{sv}", "enum", enum_array_builder.end ()); properties_builder.add ("{sv}", "type", type_prop_builder.end ()); // From unit property var from_prop_builder = new VariantBuilder (new VariantType ("a{sv}")); from_prop_builder.add ("{sv}", "type", new Variant.string ("string")); from_prop_builder.add ("{sv}", "description", new Variant.string ("Source unit")); properties_builder.add ("{sv}", "from", from_prop_builder.end ()); // To unit property var to_prop_builder = new VariantBuilder (new VariantType ("a{sv}")); to_prop_builder.add ("{sv}", "type", new Variant.string ("string")); to_prop_builder.add ("{sv}", "description", new Variant.string ("Target unit")); properties_builder.add ("{sv}", "to", to_prop_builder.end ()); // Value property var value_prop_builder = new VariantBuilder (new VariantType ("a{sv}")); value_prop_builder.add ("{sv}", "type", new Variant.string ("number")); value_prop_builder.add ("{sv}", "description", new Variant.string ("Value to convert")); properties_builder.add ("{sv}", "value", value_prop_builder.end ()); builder.add ("{sv}", "properties", properties_builder.end ()); var required_array_builder = new VariantBuilder (new VariantType ("as")); required_array_builder.add ("s", "type"); required_array_builder.add ("s", "from"); required_array_builder.add ("s", "to"); required_array_builder.add ("s", "value"); builder.add ("{sv}", "required", required_array_builder.end ()); var input_schema = builder.end (); var definition = new Mcp.Tools.Types.ToolDefinition ("unit_converter", input_schema); definition.title = "Unit Converter"; definition.description = "Convert between different units of measurement (temperature, length, weight, volume, data)"; base (definition); } /** * {@inheritDoc} */ protected override async Mcp.Tools.Types.CallToolResult do_execute (GLib.Variant arguments) throws Error { string conversion_type = get_string_arg (arguments, "type"); string from_unit = get_string_arg (arguments, "from"); string to_unit = get_string_arg (arguments, "to"); double value = get_double_arg (arguments, "value"); double result = 0.0; try { switch (conversion_type) { case "temperature": result = convert_temperature (value, from_unit, to_unit); break; case "length": result = convert_length (value, from_unit, to_unit); break; case "weight": result = convert_weight (value, from_unit, to_unit); break; case "volume": result = convert_volume (value, from_unit, to_unit); break; case "data": result = convert_data (value, from_unit, to_unit); break; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown conversion type: %s".printf (conversion_type)); } } catch (Error e) { throw new Mcp.Core.McpError.INVALID_PARAMS ("Conversion error: %s".printf (e.message)); } // Create structured result using Variant var structured_data_builder = new VariantBuilder (new VariantType ("a{sv}")); structured_data_builder.add ("{sv}", "result", new Variant.double (result)); structured_data_builder.add ("{sv}", "conversion_type", new Variant.string (conversion_type)); structured_data_builder.add ("{sv}", "from_unit", new Variant.string (from_unit)); structured_data_builder.add ("{sv}", "to_unit", new Variant.string (to_unit)); structured_data_builder.add ("{sv}", "input_value", new Variant.double (value)); structured_data_builder.add ("{sv}", "expression", new Variant.string ("%.2f %s = %.2f %s".printf (value, from_unit, result, to_unit))); var structured_data = structured_data_builder.end (); return create_structured_result ("Conversion result: %.2f %s".printf (result, to_unit), structured_data); } /** * Converts temperature between different units. */ private double convert_temperature (double value, string from, string to) throws Error { // First convert to Celsius double celsius; switch (from.down ()) { case "c": case "celsius": celsius = value; break; case "f": case "fahrenheit": celsius = (value - 32) * 5 / 9; break; case "k": case "kelvin": celsius = value - 273.15; break; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown temperature unit: %s".printf (from)); } // Then convert from Celsius to target unit switch (to.down ()) { case "c": case "celsius": return celsius; case "f": case "fahrenheit": return celsius * 9 / 5 + 32; case "k": case "kelvin": return celsius + 273.15; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown temperature unit: %s".printf (to)); } } /** * Converts length between different units. */ private double convert_length (double value, string from, string to) throws Error { // Convert to meters first double meters; switch (from.down ()) { case "m": case "meter": case "meters": meters = value; break; case "cm": case "centimeter": case "centimeters": meters = value / 100; break; case "km": case "kilometer": case "kilometers": meters = value * 1000; break; case "ft": case "foot": case "feet": meters = value * 0.3048; break; case "in": case "inch": case "inches": meters = value * 0.0254; break; case "mi": case "mile": case "miles": meters = value * 1609.34; break; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown length unit: %s".printf (from)); } // Convert from meters to target unit switch (to.down ()) { case "m": case "meter": case "meters": return meters; case "cm": case "centimeter": case "centimeters": return meters * 100; case "km": case "kilometer": case "kilometers": return meters / 1000; case "ft": case "foot": case "feet": return meters / 0.3048; case "in": case "inch": case "inches": return meters / 0.0254; case "mi": case "mile": case "miles": return meters / 1609.34; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown length unit: %s".printf (to)); } } /** * Converts weight between different units. */ private double convert_weight (double value, string from, string to) throws Error { // Convert to kilograms first double kilograms; switch (from.down ()) { case "kg": case "kilogram": case "kilograms": kilograms = value; break; case "g": case "gram": case "grams": kilograms = value / 1000; break; case "lb": case "pound": case "pounds": kilograms = value * 0.453592; break; case "oz": case "ounce": case "ounces": kilograms = value * 0.0283495; break; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown weight unit: %s".printf (from)); } // Convert from kilograms to target unit switch (to.down ()) { case "kg": case "kilogram": case "kilograms": return kilograms; case "g": case "gram": case "grams": return kilograms * 1000; case "lb": case "pound": case "pounds": return kilograms / 0.453592; case "oz": case "ounce": case "ounces": return kilograms / 0.0283495; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown weight unit: %s".printf (to)); } } /** * Converts volume between different units. */ private double convert_volume (double value, string from, string to) throws Error { // Convert to liters first double liters; switch (from.down ()) { case "l": case "liter": case "liters": liters = value; break; case "ml": case "milliliter": case "milliliters": liters = value / 1000; break; case "gal": case "gallon": case "gallons": liters = value * 3.78541; break; case "qt": case "quart": case "quarts": liters = value * 0.946353; break; case "pt": case "pint": case "pints": liters = value * 0.473176; break; case "cup": case "cups": liters = value * 0.236588; break; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown volume unit: %s".printf (from)); } // Convert from liters to target unit switch (to.down ()) { case "l": case "liter": case "liters": return liters; case "ml": case "milliliter": case "milliliters": return liters * 1000; case "gal": case "gallon": case "gallons": return liters / 3.78541; case "qt": case "quart": case "quarts": return liters / 0.946353; case "pt": case "pint": case "pints": return liters / 0.473176; case "cup": case "cups": return liters / 0.236588; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown volume unit: %s".printf (to)); } } /** * Converts data storage between different units. */ private double convert_data (double value, string from, string to) throws Error { // Convert to bytes first double bytes; switch (from.down ()) { case "b": case "byte": case "bytes": bytes = value; break; case "kb": case "kilobyte": case "kilobytes": bytes = value * 1024; break; case "mb": case "megabyte": case "megabytes": bytes = value * 1024 * 1024; break; case "gb": case "gigabyte": case "gigabytes": bytes = value * 1024 * 1024 * 1024; break; case "tb": case "terabyte": case "terabytes": bytes = value * 1024.0 * 1024.0 * 1024.0 * 1024.0; break; default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown data unit: %s".printf (from)); } // Convert from bytes to target unit switch (to.down ()) { case "b": case "byte": case "bytes": return bytes; case "kb": case "kilobyte": case "kilobytes": return bytes / 1024; case "mb": case "megabyte": case "megabytes": return bytes / (1024 * 1024); case "gb": case "gigabyte": case "gigabytes": return bytes / (1024 * 1024 * 1024); case "tb": case "terabyte": case "terabytes": return bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0); default: throw new Mcp.Core.McpError.INVALID_PARAMS ("Unknown data unit: %s".printf (to)); } } } /** * Main function. * * @param args Command line arguments * @return Exit code */ public static int main (string[] args) { // Create and run the server var server = new CalculatorServer (); // Handle Ctrl+C to gracefully shutdown // For now, we'll skip signal handling to get the build working var loop = new MainLoop (); // Run the server server.run.begin ((obj, res) => { loop.quit (); }); // Keep the main loop running loop.run (); return 0; }