From f7dd227cd0c487710854991a5daa22dc0b3f9ee8 Mon Sep 17 00:00:00 2001 From: Timur Gordon <31495328+timurgordon@users.noreply.github.com> Date: Sun, 13 Apr 2025 13:28:55 +0200 Subject: [PATCH] mcp refactor wip --- lib/{mcp => ai}/escalayer/README.md | 0 lib/{mcp => ai}/escalayer/escalayer.v | 0 .../escalayer/escalayer_architecture.md | 0 lib/{mcp => ai}/escalayer/models.v | 0 lib/{mcp => ai}/escalayer/openrouter.v | 0 lib/{mcp => ai}/escalayer/task.v | 4 +- lib/{mcp => ai}/escalayer/unit_task.v | 9 +- lib/ai/utils/utils.v | 34 +++ lib/lang/rust/rust.v | 103 +++++++ lib/mcp/{core => }/backend_interface.v | 1 + lib/mcp/{core => }/backend_memory.v | 14 + lib/mcp/{servers => }/baobab/README.md | 0 lib/mcp/{servers => }/baobab/baobab_tools.v | 0 .../{servers => }/baobab/baobab_tools_test.v | 0 lib/mcp/{servers => }/baobab/command.v | 0 lib/mcp/{servers => }/baobab/mcp_test.v | 0 lib/mcp/{servers => }/baobab/server.v | 0 lib/mcp/cmd/compile.vsh | 68 +++++ lib/mcp/cmd/mcp.v | 92 ++++++ lib/mcp/{core => }/factory.v | 2 +- lib/mcp/{core => }/generics.v | 0 lib/mcp/{core => }/handler_initialize.v | 1 - lib/mcp/{core => }/handler_initialize_test.v | 0 lib/mcp/{core => }/handler_prompts.v | 22 +- lib/mcp/{core => }/handler_resources.v | 1 - lib/mcp/{core => }/handler_tools.v | 1 - lib/mcp/{servers => }/mcpgen/README.md | 0 lib/mcp/{servers => }/mcpgen/command.v | 0 lib/mcp/{servers => }/mcpgen/mcpgen.v | 0 lib/mcp/{servers => }/mcpgen/mcpgen_helpers.v | 0 lib/mcp/{servers => }/mcpgen/mcpgen_tools.v | 0 .../create_mcp_tools_code_tool_input.json | 0 lib/mcp/{servers => }/mcpgen/server.v | 0 .../mcpgen/templates/tool_code.v.template | 0 .../mcpgen/templates/tool_handler.v.template | 0 .../mcpgen/templates/tools_file.v.template | 0 lib/mcp/{core => }/model_configuration.v | 1 - lib/mcp/{core => }/model_configuration_test.v | 0 lib/mcp/{core => }/model_error.v | 0 .../{servers => }/pugconvert/cmd/.gitignore | 0 .../{servers => }/pugconvert/cmd/compile.sh | 0 lib/mcp/{servers => }/pugconvert/cmd/main.v | 0 .../pugconvert/logic/convertpug.v | 0 .../pugconvert/logic/jetvalidation.v | 0 .../{servers => }/pugconvert/logic/loader.v | 0 .../logic/templates/jet_instructions.md | 0 .../{servers => }/pugconvert/mcp/handlers.v | 0 lib/mcp/{servers => }/pugconvert/mcp/mcp.v | 0 .../pugconvert/mcp/specifications.v | 0 lib/mcp/{servers => }/rhai/cmd/.gitignore | 0 lib/mcp/{servers => }/rhai/cmd/compile.sh | 0 lib/mcp/rhai/cmd/main.v | 18 ++ .../rhai/example/example copy.vsh | 0 .../{servers => }/rhai/example/example.vsh | 0 lib/mcp/rhai/logic/logic.v | 279 ++++++++++++++++++ .../rhai/logic/prompts/engine.md | 0 .../rhai/logic/prompts/errors.md | 0 .../rhai/logic/prompts/example_script.md | 0 lib/mcp/rhai/logic/prompts/main.md | 101 +++++++ .../rhai/logic/prompts/wrapper.md | 0 .../rhai/logic/templates/cargo.toml | 0 .../rhai/logic/templates/engine.rs | 0 .../rhai/logic/templates/example.rs | 0 .../rhai/logic/templates/functions.rs | 0 .../rhai/logic/templates/generic_wrapper.rs | 0 .../{servers => }/rhai/logic/templates/lib.rs | 0 lib/mcp/rhai/mcp/command.v | 23 ++ lib/mcp/{servers => }/rhai/mcp/mcp.v | 20 +- lib/mcp/rhai/mcp/prompts.v | 42 +++ .../{servers => }/rhai/mcp/specifications.v | 4 +- lib/mcp/rhai/mcp/tools.v | 39 +++ lib/mcp/rhai/rhai.v | 1 + lib/mcp/{core => }/server.v | 5 +- lib/mcp/servers/rhai/cmd/main.v | 17 -- lib/mcp/servers/rhai/mcp/handlers.v | 58 ---- lib/mcp/{servers => }/vcode/README.md | 0 lib/mcp/{servers => }/vcode/cmd/.gitignore | 0 lib/mcp/{servers => }/vcode/cmd/compile.sh | 0 lib/mcp/{servers => }/vcode/cmd/main.v | 2 +- lib/mcp/{servers => }/vcode/logic/server.v | 0 .../{servers => }/vcode/logic/test_client.vsh | 0 lib/mcp/{servers => }/vcode/logic/vlang.v | 0 .../{servers => }/vcode/logic/vlang_test.v | 0 .../{servers => }/vcode/logic/vlang_tools.v | 0 .../vcode/logic/write_vfile_tool.v | 0 lib/mcp/{servers => }/vcode/mcp/handlers.v | 0 lib/mcp/{servers => }/vcode/mcp/mcp.v | 0 .../{servers => }/vcode/mcp/specifications.v | 0 88 files changed, 855 insertions(+), 107 deletions(-) rename lib/{mcp => ai}/escalayer/README.md (100%) rename lib/{mcp => ai}/escalayer/escalayer.v (100%) rename lib/{mcp => ai}/escalayer/escalayer_architecture.md (100%) rename lib/{mcp => ai}/escalayer/models.v (100%) rename lib/{mcp => ai}/escalayer/openrouter.v (100%) rename lib/{mcp => ai}/escalayer/task.v (94%) rename lib/{mcp => ai}/escalayer/unit_task.v (88%) create mode 100644 lib/ai/utils/utils.v create mode 100644 lib/lang/rust/rust.v rename lib/mcp/{core => }/backend_interface.v (92%) rename lib/mcp/{core => }/backend_memory.v (89%) rename lib/mcp/{servers => }/baobab/README.md (100%) rename lib/mcp/{servers => }/baobab/baobab_tools.v (100%) rename lib/mcp/{servers => }/baobab/baobab_tools_test.v (100%) rename lib/mcp/{servers => }/baobab/command.v (100%) rename lib/mcp/{servers => }/baobab/mcp_test.v (100%) rename lib/mcp/{servers => }/baobab/server.v (100%) create mode 100755 lib/mcp/cmd/compile.vsh create mode 100644 lib/mcp/cmd/mcp.v rename lib/mcp/{core => }/factory.v (95%) rename lib/mcp/{core => }/generics.v (100%) rename lib/mcp/{core => }/handler_initialize.v (95%) rename lib/mcp/{core => }/handler_initialize_test.v (100%) rename lib/mcp/{core => }/handler_prompts.v (77%) rename lib/mcp/{core => }/handler_resources.v (99%) rename lib/mcp/{core => }/handler_tools.v (98%) rename lib/mcp/{servers => }/mcpgen/README.md (100%) rename lib/mcp/{servers => }/mcpgen/command.v (100%) rename lib/mcp/{servers => }/mcpgen/mcpgen.v (100%) rename lib/mcp/{servers => }/mcpgen/mcpgen_helpers.v (100%) rename lib/mcp/{servers => }/mcpgen/mcpgen_tools.v (100%) rename lib/mcp/{servers => }/mcpgen/schemas/create_mcp_tools_code_tool_input.json (100%) rename lib/mcp/{servers => }/mcpgen/server.v (100%) rename lib/mcp/{servers => }/mcpgen/templates/tool_code.v.template (100%) rename lib/mcp/{servers => }/mcpgen/templates/tool_handler.v.template (100%) rename lib/mcp/{servers => }/mcpgen/templates/tools_file.v.template (100%) rename lib/mcp/{core => }/model_configuration.v (98%) rename lib/mcp/{core => }/model_configuration_test.v (100%) rename lib/mcp/{core => }/model_error.v (100%) rename lib/mcp/{servers => }/pugconvert/cmd/.gitignore (100%) rename lib/mcp/{servers => }/pugconvert/cmd/compile.sh (100%) rename lib/mcp/{servers => }/pugconvert/cmd/main.v (100%) rename lib/mcp/{servers => }/pugconvert/logic/convertpug.v (100%) rename lib/mcp/{servers => }/pugconvert/logic/jetvalidation.v (100%) rename lib/mcp/{servers => }/pugconvert/logic/loader.v (100%) rename lib/mcp/{servers => }/pugconvert/logic/templates/jet_instructions.md (100%) rename lib/mcp/{servers => }/pugconvert/mcp/handlers.v (100%) rename lib/mcp/{servers => }/pugconvert/mcp/mcp.v (100%) rename lib/mcp/{servers => }/pugconvert/mcp/specifications.v (100%) rename lib/mcp/{servers => }/rhai/cmd/.gitignore (100%) rename lib/mcp/{servers => }/rhai/cmd/compile.sh (100%) create mode 100644 lib/mcp/rhai/cmd/main.v rename lib/mcp/{servers => }/rhai/example/example copy.vsh (100%) mode change 100755 => 100644 rename lib/mcp/{servers => }/rhai/example/example.vsh (100%) create mode 100644 lib/mcp/rhai/logic/logic.v rename lib/mcp/{servers => }/rhai/logic/prompts/engine.md (100%) rename lib/mcp/{servers => }/rhai/logic/prompts/errors.md (100%) rename lib/mcp/{servers => }/rhai/logic/prompts/example_script.md (100%) create mode 100644 lib/mcp/rhai/logic/prompts/main.md rename lib/mcp/{servers => }/rhai/logic/prompts/wrapper.md (100%) rename lib/mcp/{servers => }/rhai/logic/templates/cargo.toml (100%) rename lib/mcp/{servers => }/rhai/logic/templates/engine.rs (100%) rename lib/mcp/{servers => }/rhai/logic/templates/example.rs (100%) rename lib/mcp/{servers => }/rhai/logic/templates/functions.rs (100%) rename lib/mcp/{servers => }/rhai/logic/templates/generic_wrapper.rs (100%) rename lib/mcp/{servers => }/rhai/logic/templates/lib.rs (100%) create mode 100644 lib/mcp/rhai/mcp/command.v rename lib/mcp/{servers => }/rhai/mcp/mcp.v (54%) create mode 100644 lib/mcp/rhai/mcp/prompts.v rename lib/mcp/{servers => }/rhai/mcp/specifications.v (89%) create mode 100644 lib/mcp/rhai/mcp/tools.v create mode 100644 lib/mcp/rhai/rhai.v rename lib/mcp/{core => }/server.v (90%) delete mode 100644 lib/mcp/servers/rhai/cmd/main.v delete mode 100644 lib/mcp/servers/rhai/mcp/handlers.v rename lib/mcp/{servers => }/vcode/README.md (100%) rename lib/mcp/{servers => }/vcode/cmd/.gitignore (100%) rename lib/mcp/{servers => }/vcode/cmd/compile.sh (100%) rename lib/mcp/{servers => }/vcode/cmd/main.v (84%) rename lib/mcp/{servers => }/vcode/logic/server.v (100%) rename lib/mcp/{servers => }/vcode/logic/test_client.vsh (100%) rename lib/mcp/{servers => }/vcode/logic/vlang.v (100%) rename lib/mcp/{servers => }/vcode/logic/vlang_test.v (100%) rename lib/mcp/{servers => }/vcode/logic/vlang_tools.v (100%) rename lib/mcp/{servers => }/vcode/logic/write_vfile_tool.v (100%) rename lib/mcp/{servers => }/vcode/mcp/handlers.v (100%) rename lib/mcp/{servers => }/vcode/mcp/mcp.v (100%) rename lib/mcp/{servers => }/vcode/mcp/specifications.v (100%) diff --git a/lib/mcp/escalayer/README.md b/lib/ai/escalayer/README.md similarity index 100% rename from lib/mcp/escalayer/README.md rename to lib/ai/escalayer/README.md diff --git a/lib/mcp/escalayer/escalayer.v b/lib/ai/escalayer/escalayer.v similarity index 100% rename from lib/mcp/escalayer/escalayer.v rename to lib/ai/escalayer/escalayer.v diff --git a/lib/mcp/escalayer/escalayer_architecture.md b/lib/ai/escalayer/escalayer_architecture.md similarity index 100% rename from lib/mcp/escalayer/escalayer_architecture.md rename to lib/ai/escalayer/escalayer_architecture.md diff --git a/lib/mcp/escalayer/models.v b/lib/ai/escalayer/models.v similarity index 100% rename from lib/mcp/escalayer/models.v rename to lib/ai/escalayer/models.v diff --git a/lib/mcp/escalayer/openrouter.v b/lib/ai/escalayer/openrouter.v similarity index 100% rename from lib/mcp/escalayer/openrouter.v rename to lib/ai/escalayer/openrouter.v diff --git a/lib/mcp/escalayer/task.v b/lib/ai/escalayer/task.v similarity index 94% rename from lib/mcp/escalayer/task.v rename to lib/ai/escalayer/task.v index 2aec1173..190b65fa 100644 --- a/lib/mcp/escalayer/task.v +++ b/lib/ai/escalayer/task.v @@ -1,5 +1,7 @@ module escalayer +import log + // Task represents a complete AI task composed of multiple sequential unit tasks pub struct Task { pub mut: @@ -41,7 +43,7 @@ pub fn (mut t Task) initiate(input string)! string { mut current_input := input for i, mut unit_task in t.unit_tasks { - println('Executing unit task ${i+1}/${t.unit_tasks.len}: ${unit_task.name}') + log.error('Executing unit task ${i+1}/${t.unit_tasks.len}: ${unit_task.name}') // Execute the unit task with the current input result := unit_task.execute(current_input)! diff --git a/lib/mcp/escalayer/unit_task.v b/lib/ai/escalayer/unit_task.v similarity index 88% rename from lib/mcp/escalayer/unit_task.v rename to lib/ai/escalayer/unit_task.v index ac6d1057..58268555 100644 --- a/lib/mcp/escalayer/unit_task.v +++ b/lib/ai/escalayer/unit_task.v @@ -1,5 +1,6 @@ module escalayer +import log import freeflowuniverse.herolib.clients.openai // UnitTask represents a single step in the task @@ -30,7 +31,7 @@ pub fn (mut ut UnitTask) execute(input string)! string { // If we've exhausted retries with the base model, switch to the retry model if attempts > ut.retry_count { - println('Escalating to more powerful model: ${ut.retry_model.name}') + log.error('Escalating to more powerful model: ${ut.retry_model.name}') current_model = ut.retry_model // Calculate remaining attempts but don't exceed absolute max max_attempts = attempts + ut.retry_count @@ -39,7 +40,7 @@ pub fn (mut ut UnitTask) execute(input string)! string { } } - println('Attempt ${attempts} with model ${current_model.name}') + log.error('Attempt ${attempts} with model ${current_model.name}') // Prepare the prompt with error feedback if this is a retry mut current_prompt := prompt @@ -49,7 +50,7 @@ pub fn (mut ut UnitTask) execute(input string)! string { // Call the AI model response := call_ai_model(current_prompt, current_model) or { - println('AI call failed: ${err}') + log.error('AI call failed: ${err}') last_error = err.str() continue // Try again } @@ -57,7 +58,7 @@ pub fn (mut ut UnitTask) execute(input string)! string { // Process the response with the callback function result := ut.callback_function(response) or { // If callback returns an error, retry with the error message - println('Callback returned error: ${err}') + log.error('Callback returned error: ${err}') last_error = err.str() continue // Try again } diff --git a/lib/ai/utils/utils.v b/lib/ai/utils/utils.v new file mode 100644 index 00000000..12a7f893 --- /dev/null +++ b/lib/ai/utils/utils.v @@ -0,0 +1,34 @@ +module utils + +// Helper function to extract code blocks from the response +pub fn extract_code_block(response string, identifier string, language string) string { + // Find the start marker for the code block + mut start_marker := '```${language}\n// ${identifier}' + if language == '' { + start_marker = '```\n// ${identifier}' + } + + start_index := response.index(start_marker) or { + // Try alternative format + mut alt_marker := '```${language}\n${identifier}' + if language == '' { + alt_marker = '```\n${identifier}' + } + + response.index(alt_marker) or { + return '' + } + } + + // Find the end marker + end_marker := '```' + end_index := response.index_after(end_marker, start_index + start_marker.len) or { + return '' + } + + // Extract the content between the markers + content_start := start_index + start_marker.len + content := response[content_start..end_index].trim_space() + + return content +} \ No newline at end of file diff --git a/lib/lang/rust/rust.v b/lib/lang/rust/rust.v new file mode 100644 index 00000000..7511bfdb --- /dev/null +++ b/lib/lang/rust/rust.v @@ -0,0 +1,103 @@ +module rust + +import os + +// Reads and combines all Rust files in the given directory +pub fn read_source_code(source_code_path string) !string { + // Get all files in the directory + files := os.ls(source_code_path) or { + return error('Failed to list files in directory: ${err}') + } + + // Combine all Rust files into a single source code string + mut source_code := '' + for file in files { + file_path := os.join_path(source_code_path, file) + + // Skip directories and non-Rust files + if os.is_dir(file_path) || !file.ends_with('.rs') { + continue + } + + // Read the file content + file_content := os.read_file(file_path) or { + println('Failed to read file ${file_path}: ${err}') + continue + } + + // Add file content to the combined source code + source_code += '// File: ${file}\n${file_content}\n\n' + } + + if source_code == '' { + return error('No Rust files found in directory: ${source_code_path}') + } + + return source_code +} + +// Determines the crate path from the source code path +pub fn determine_crate_path(source_code_path string) !string { + // Extract the path relative to the src directory + src_index := source_code_path.index('src/') or { + return error('Could not determine crate path: src/ not found in path') + } + + mut path_parts := source_code_path[src_index+4..].split('/') + // Remove the last part (the file name) + if path_parts.len > 0 { + path_parts.delete_last() + } + rel_path := path_parts.join('::') + return 'sal::${rel_path}' +} + +// Extracts the module name from a directory path +pub fn extract_module_name_from_path(path string) string { + dir_parts := path.split('/') + return dir_parts[dir_parts.len - 1] +} + +// Build and run a Rust project with an example +pub fn run_example(project_dir string, example_name string) !(string, string) { + // Change to the project directory + os.chdir(project_dir) or { + return error('Failed to change directory to project: ${err}') + } + + // Run cargo build first + build_result := os.execute('cargo build') + if build_result.exit_code != 0 { + return error('Compilation failed. Please fix the following errors and ensure your code is compatible with the existing codebase:\n\n${build_result.output}') + } + + // Run the example + run_result := os.execute('cargo run --example ${example_name}') + + return build_result.output, run_result.output +} + + +// Extract function names from wrapper code +fn extract_functions_from_code(code string) []string { + mut functions := []string{} + lines := code.split('\n') + + for line in lines { + if line.contains('pub fn ') && !line.contains('//') { + // Extract function name + parts := line.split('pub fn ') + if parts.len > 1 { + name_parts := parts[1].split('(') + if name_parts.len > 0 { + fn_name := name_parts[0].trim_space() + if fn_name != '' { + functions << fn_name + } + } + } + } + } + + return functions +} \ No newline at end of file diff --git a/lib/mcp/core/backend_interface.v b/lib/mcp/backend_interface.v similarity index 92% rename from lib/mcp/core/backend_interface.v rename to lib/mcp/backend_interface.v index 1cd79e29..4ba0fe24 100644 --- a/lib/mcp/core/backend_interface.v +++ b/lib/mcp/backend_interface.v @@ -14,6 +14,7 @@ interface Backend { // Prompt methods prompt_exists(name string) !bool prompt_get(name string) !Prompt + prompt_call(name string, arguments []string) ![]PromptMessage prompt_list() ![]Prompt prompt_messages_get(name string, arguments map[string]string) ![]PromptMessage diff --git a/lib/mcp/core/backend_memory.v b/lib/mcp/backend_memory.v similarity index 89% rename from lib/mcp/core/backend_memory.v rename to lib/mcp/backend_memory.v index 3a83d59f..349caad6 100644 --- a/lib/mcp/core/backend_memory.v +++ b/lib/mcp/backend_memory.v @@ -13,6 +13,7 @@ pub mut: // Prompt related fields prompts map[string]Prompt prompt_messages map[string][]PromptMessage + prompt_handlers map[string]PromptHandler // Tool related fields tools map[string]Tool @@ -20,6 +21,9 @@ pub mut: } pub type ToolHandler = fn (arguments map[string]json2.Any) !ToolCallResult + +pub type PromptHandler = fn (arguments []string) ![]PromptMessage + fn (b &MemoryBackend) resource_exists(uri string) !bool { return uri in b.resources } @@ -105,6 +109,16 @@ fn (b &MemoryBackend) prompt_messages_get(name string, arguments map[string]stri return messages } + +fn (b &MemoryBackend) prompt_call(name string, arguments []string) ![]PromptMessage { + // Get the tool handler + handler := b.prompt_handlers[name] or { return error('tool handler not found') } + + // Call the handler with the provided arguments + return handler(arguments) or {panic(err)} +} + + // Tool related methods fn (b &MemoryBackend) tool_exists(name string) !bool { diff --git a/lib/mcp/servers/baobab/README.md b/lib/mcp/baobab/README.md similarity index 100% rename from lib/mcp/servers/baobab/README.md rename to lib/mcp/baobab/README.md diff --git a/lib/mcp/servers/baobab/baobab_tools.v b/lib/mcp/baobab/baobab_tools.v similarity index 100% rename from lib/mcp/servers/baobab/baobab_tools.v rename to lib/mcp/baobab/baobab_tools.v diff --git a/lib/mcp/servers/baobab/baobab_tools_test.v b/lib/mcp/baobab/baobab_tools_test.v similarity index 100% rename from lib/mcp/servers/baobab/baobab_tools_test.v rename to lib/mcp/baobab/baobab_tools_test.v diff --git a/lib/mcp/servers/baobab/command.v b/lib/mcp/baobab/command.v similarity index 100% rename from lib/mcp/servers/baobab/command.v rename to lib/mcp/baobab/command.v diff --git a/lib/mcp/servers/baobab/mcp_test.v b/lib/mcp/baobab/mcp_test.v similarity index 100% rename from lib/mcp/servers/baobab/mcp_test.v rename to lib/mcp/baobab/mcp_test.v diff --git a/lib/mcp/servers/baobab/server.v b/lib/mcp/baobab/server.v similarity index 100% rename from lib/mcp/servers/baobab/server.v rename to lib/mcp/baobab/server.v diff --git a/lib/mcp/cmd/compile.vsh b/lib/mcp/cmd/compile.vsh new file mode 100755 index 00000000..0f994400 --- /dev/null +++ b/lib/mcp/cmd/compile.vsh @@ -0,0 +1,68 @@ +#!/usr/bin/env -S v -n -cg -w -parallel-cc -enable-globals run + +import os +import flag + +mut fp := flag.new_flag_parser(os.args) +fp.application('compile.vsh') +fp.version('v0.1.0') +fp.description('Compile MCP binary in debug or production mode') +fp.skip_executable() + +prod_mode := fp.bool('prod', `p`, false, 'Build production version (optimized)') +help_requested := fp.bool('help', `h`, false, 'Show help message') + +if help_requested { + println(fp.usage()) + exit(0) +} + +additional_args := fp.finalize() or { + eprintln(err) + println(fp.usage()) + exit(1) +} + +if additional_args.len > 0 { + eprintln('Unexpected arguments: ${additional_args.join(' ')}') + println(fp.usage()) + exit(1) +} + +// Change to the mcp directory +mcp_dir := os.dir(os.real_path(os.executable())) +os.chdir(mcp_dir) or { panic('Failed to change directory to ${mcp_dir}: ${err}') } + +// Set MCPPATH based on OS +mut mcppath := '/usr/local/bin/mcp' +if os.user_os() == 'macos' { + mcppath = os.join_path(os.home_dir(), 'hero/bin/mcp') +} + +// Set compilation command based on OS and mode +compile_cmd := if prod_mode { + 'v -enable-globals -w -n -prod mcp.v' +} else { + 'v -w -cg -gc none -cc tcc -d use_openssl -enable-globals mcp.v' +} + +println('Building MCP in ${if prod_mode { 'production' } else { 'debug' }} mode...') + +if os.system(compile_cmd) != 0 { + panic('Failed to compile mcp.v with command: ${compile_cmd}') +} + +// Make executable +os.chmod('mcp', 0o755) or { panic('Failed to make mcp binary executable: ${err}') } + +// Ensure destination directory exists +os.mkdir_all(os.dir(mcppath)) or { panic('Failed to create directory ${os.dir(mcppath)}: ${err}') } + +// Copy to destination paths +os.cp('mcp', mcppath) or { panic('Failed to copy mcp binary to ${mcppath}: ${err}') } +os.cp('mcp', '/tmp/mcp') or { panic('Failed to copy mcp binary to /tmp/mcp: ${err}') } + +// Clean up +os.rm('mcp') or { panic('Failed to remove temporary mcp binary: ${err}') } + +println('**MCP COMPILE OK**') diff --git a/lib/mcp/cmd/mcp.v b/lib/mcp/cmd/mcp.v new file mode 100644 index 00000000..ca64f92f --- /dev/null +++ b/lib/mcp/cmd/mcp.v @@ -0,0 +1,92 @@ +module main + +import os +import cli { Command, Flag } +import freeflowuniverse.herolib.osal +// import freeflowuniverse.herolib.mcp.vcode +// import freeflowuniverse.herolib.mcp.mcpgen +// import freeflowuniverse.herolib.mcp.baobab +import freeflowuniverse.herolib.mcp.rhai.mcp as rhai_mcp + +fn main() { + do() or { panic(err) } +} + +pub fn do() ! { + mut cmd_mcp := Command{ + name: 'mcp' + usage: ' +## Manage your MCPs + +example: + +mcp + ' + description: 'create, edit, show mdbooks' + required_args: 0 + } + + // cmd_run_add_flags(mut cmd_publisher) + + cmd_mcp.add_flag(Flag{ + flag: .bool + required: false + name: 'debug' + abbrev: 'd' + description: 'show debug output' + }) + + cmd_mcp.add_flag(Flag{ + flag: .bool + required: false + name: 'verbose' + abbrev: 'v' + description: 'show verbose output' + }) + + mut cmd_inspector := cli.Command{ + sort_flags: true + name: 'inspector' + execute: cmd_inspector_execute + description: 'will list existing mdbooks' + } + + cmd_inspector.add_flag(Flag{ + flag: .string + required: false + name: 'name' + abbrev: 'n' + description: 'name of the MCP' + }) + + cmd_inspector.add_flag(Flag{ + flag: .bool + required: false + name: 'open' + abbrev: 'o' + description: 'open inspector' + }) + + + cmd_mcp.add_command(rhai_mcp.command) + // cmd_mcp.add_command(baobab.command) + // cmd_mcp.add_command(vcode.command) + cmd_mcp.add_command(cmd_inspector) + // cmd_mcp.add_command(vcode.command) + cmd_mcp.setup() + cmd_mcp.parse(os.args) +} + +fn cmd_inspector_execute(cmd cli.Command) ! { + open := cmd.flags.get_bool('open') or { false } + if open { + osal.exec(cmd: 'open http://localhost:5173')! + } + name := cmd.flags.get_string('name') or { '' } + if name.len > 0 { + println('starting inspector for MCP ${name}') + osal.exec(cmd: 'npx @modelcontextprotocol/inspector mcp ${name} start')! + } else { + osal.exec(cmd: 'npx @modelcontextprotocol/inspector')! + } +} diff --git a/lib/mcp/core/factory.v b/lib/mcp/factory.v similarity index 95% rename from lib/mcp/core/factory.v rename to lib/mcp/factory.v index fe4cbd9a..ae538a1d 100644 --- a/lib/mcp/core/factory.v +++ b/lib/mcp/factory.v @@ -5,7 +5,6 @@ import os import log import x.json2 import freeflowuniverse.herolib.schemas.jsonrpc -import freeflowuniverse.herolib.mcp.logger @[params] pub struct ServerParams { @@ -38,6 +37,7 @@ pub fn new_server(backend Backend, params ServerParams) !&Server { // Prompt handlers 'prompts/list': server.prompts_list_handler, 'prompts/get': server.prompts_get_handler, + 'completion/complete': server.prompts_get_handler, // Tool handlers 'tools/list': server.tools_list_handler, diff --git a/lib/mcp/core/generics.v b/lib/mcp/generics.v similarity index 100% rename from lib/mcp/core/generics.v rename to lib/mcp/generics.v diff --git a/lib/mcp/core/handler_initialize.v b/lib/mcp/handler_initialize.v similarity index 95% rename from lib/mcp/core/handler_initialize.v rename to lib/mcp/handler_initialize.v index 0622c246..01b52e7f 100644 --- a/lib/mcp/core/handler_initialize.v +++ b/lib/mcp/handler_initialize.v @@ -5,7 +5,6 @@ import os import log import x.json2 import freeflowuniverse.herolib.schemas.jsonrpc -import freeflowuniverse.herolib.mcp.logger // initialize_handler handles the initialize request according to the MCP specification fn (mut s Server) initialize_handler(data string) !string { diff --git a/lib/mcp/core/handler_initialize_test.v b/lib/mcp/handler_initialize_test.v similarity index 100% rename from lib/mcp/core/handler_initialize_test.v rename to lib/mcp/handler_initialize_test.v diff --git a/lib/mcp/core/handler_prompts.v b/lib/mcp/handler_prompts.v similarity index 77% rename from lib/mcp/core/handler_prompts.v rename to lib/mcp/handler_prompts.v index 66eb5be8..d35ba6aa 100644 --- a/lib/mcp/core/handler_prompts.v +++ b/lib/mcp/handler_prompts.v @@ -6,7 +6,6 @@ import log import x.json2 import json import freeflowuniverse.herolib.schemas.jsonrpc -import freeflowuniverse.herolib.mcp.logger // Prompt related structs @@ -88,27 +87,30 @@ pub: // This request is used to retrieve a specific prompt with arguments fn (mut s Server) prompts_get_handler(data string) !string { // Decode the request with name and arguments parameters - request := jsonrpc.decode_request_generic[PromptGetParams](data)! + request_map := json2.raw_decode(data)!.as_map() + params_map := request_map['params'].as_map() - if !s.backend.prompt_exists(request.params.name)! { - return jsonrpc.new_error_response(request.id, prompt_not_found(request.params.name)).encode() + if !s.backend.prompt_exists(params_map['name'].str())! { + return jsonrpc.new_error_response(request_map['id'].int(), prompt_not_found(params_map['name'].str())).encode() } // Get the prompt by name - prompt := s.backend.prompt_get(request.params.name)! + prompt := s.backend.prompt_get(params_map['name'].str())! // Validate required arguments for arg in prompt.arguments { - if arg.required && request.params.arguments[arg.name] == '' { - return jsonrpc.new_error_response(request.id, missing_required_argument(arg.name)).encode() + if arg.required && params_map['arguments'].as_map()[arg.name].str() == '' { + return jsonrpc.new_error_response(request_map['id'].int(), missing_required_argument(arg.name)).encode() } } - // Get the prompt messages with arguments applied - messages := s.backend.prompt_messages_get(request.params.name, request.params.arguments)! + messages := s.backend.prompt_call(params_map['name'].str(), params_map['arguments'].as_map().values().map(it.str()))! + + // // Get the prompt messages with arguments applied + // messages := s.backend.prompt_messages_get(request.params.name, request.params.arguments)! // Create a success response with the result - response := jsonrpc.new_response_generic[PromptGetResult](request.id, PromptGetResult{ + response := jsonrpc.new_response_generic[PromptGetResult](request_map['id'].int(), PromptGetResult{ description: prompt.description messages: messages }) diff --git a/lib/mcp/core/handler_resources.v b/lib/mcp/handler_resources.v similarity index 99% rename from lib/mcp/core/handler_resources.v rename to lib/mcp/handler_resources.v index 21fb7f28..c00c6a33 100644 --- a/lib/mcp/core/handler_resources.v +++ b/lib/mcp/handler_resources.v @@ -6,7 +6,6 @@ import log import x.json2 import json import freeflowuniverse.herolib.schemas.jsonrpc -import freeflowuniverse.herolib.mcp.logger pub struct Resource { pub: diff --git a/lib/mcp/core/handler_tools.v b/lib/mcp/handler_tools.v similarity index 98% rename from lib/mcp/core/handler_tools.v rename to lib/mcp/handler_tools.v index f597f069..fdeefbc8 100644 --- a/lib/mcp/core/handler_tools.v +++ b/lib/mcp/handler_tools.v @@ -7,7 +7,6 @@ import x.json2 import json import freeflowuniverse.herolib.schemas.jsonrpc import freeflowuniverse.herolib.schemas.jsonschema -import freeflowuniverse.herolib.mcp.logger // Tool related structs diff --git a/lib/mcp/servers/mcpgen/README.md b/lib/mcp/mcpgen/README.md similarity index 100% rename from lib/mcp/servers/mcpgen/README.md rename to lib/mcp/mcpgen/README.md diff --git a/lib/mcp/servers/mcpgen/command.v b/lib/mcp/mcpgen/command.v similarity index 100% rename from lib/mcp/servers/mcpgen/command.v rename to lib/mcp/mcpgen/command.v diff --git a/lib/mcp/servers/mcpgen/mcpgen.v b/lib/mcp/mcpgen/mcpgen.v similarity index 100% rename from lib/mcp/servers/mcpgen/mcpgen.v rename to lib/mcp/mcpgen/mcpgen.v diff --git a/lib/mcp/servers/mcpgen/mcpgen_helpers.v b/lib/mcp/mcpgen/mcpgen_helpers.v similarity index 100% rename from lib/mcp/servers/mcpgen/mcpgen_helpers.v rename to lib/mcp/mcpgen/mcpgen_helpers.v diff --git a/lib/mcp/servers/mcpgen/mcpgen_tools.v b/lib/mcp/mcpgen/mcpgen_tools.v similarity index 100% rename from lib/mcp/servers/mcpgen/mcpgen_tools.v rename to lib/mcp/mcpgen/mcpgen_tools.v diff --git a/lib/mcp/servers/mcpgen/schemas/create_mcp_tools_code_tool_input.json b/lib/mcp/mcpgen/schemas/create_mcp_tools_code_tool_input.json similarity index 100% rename from lib/mcp/servers/mcpgen/schemas/create_mcp_tools_code_tool_input.json rename to lib/mcp/mcpgen/schemas/create_mcp_tools_code_tool_input.json diff --git a/lib/mcp/servers/mcpgen/server.v b/lib/mcp/mcpgen/server.v similarity index 100% rename from lib/mcp/servers/mcpgen/server.v rename to lib/mcp/mcpgen/server.v diff --git a/lib/mcp/servers/mcpgen/templates/tool_code.v.template b/lib/mcp/mcpgen/templates/tool_code.v.template similarity index 100% rename from lib/mcp/servers/mcpgen/templates/tool_code.v.template rename to lib/mcp/mcpgen/templates/tool_code.v.template diff --git a/lib/mcp/servers/mcpgen/templates/tool_handler.v.template b/lib/mcp/mcpgen/templates/tool_handler.v.template similarity index 100% rename from lib/mcp/servers/mcpgen/templates/tool_handler.v.template rename to lib/mcp/mcpgen/templates/tool_handler.v.template diff --git a/lib/mcp/servers/mcpgen/templates/tools_file.v.template b/lib/mcp/mcpgen/templates/tools_file.v.template similarity index 100% rename from lib/mcp/servers/mcpgen/templates/tools_file.v.template rename to lib/mcp/mcpgen/templates/tools_file.v.template diff --git a/lib/mcp/core/model_configuration.v b/lib/mcp/model_configuration.v similarity index 98% rename from lib/mcp/core/model_configuration.v rename to lib/mcp/model_configuration.v index 2f0c1610..ec2ec77a 100644 --- a/lib/mcp/core/model_configuration.v +++ b/lib/mcp/model_configuration.v @@ -5,7 +5,6 @@ import os import log import x.json2 import freeflowuniverse.herolib.schemas.jsonrpc -import freeflowuniverse.herolib.mcp.logger const protocol_version = '2024-11-05' // MCP server implementation using stdio transport diff --git a/lib/mcp/core/model_configuration_test.v b/lib/mcp/model_configuration_test.v similarity index 100% rename from lib/mcp/core/model_configuration_test.v rename to lib/mcp/model_configuration_test.v diff --git a/lib/mcp/core/model_error.v b/lib/mcp/model_error.v similarity index 100% rename from lib/mcp/core/model_error.v rename to lib/mcp/model_error.v diff --git a/lib/mcp/servers/pugconvert/cmd/.gitignore b/lib/mcp/pugconvert/cmd/.gitignore similarity index 100% rename from lib/mcp/servers/pugconvert/cmd/.gitignore rename to lib/mcp/pugconvert/cmd/.gitignore diff --git a/lib/mcp/servers/pugconvert/cmd/compile.sh b/lib/mcp/pugconvert/cmd/compile.sh similarity index 100% rename from lib/mcp/servers/pugconvert/cmd/compile.sh rename to lib/mcp/pugconvert/cmd/compile.sh diff --git a/lib/mcp/servers/pugconvert/cmd/main.v b/lib/mcp/pugconvert/cmd/main.v similarity index 100% rename from lib/mcp/servers/pugconvert/cmd/main.v rename to lib/mcp/pugconvert/cmd/main.v diff --git a/lib/mcp/servers/pugconvert/logic/convertpug.v b/lib/mcp/pugconvert/logic/convertpug.v similarity index 100% rename from lib/mcp/servers/pugconvert/logic/convertpug.v rename to lib/mcp/pugconvert/logic/convertpug.v diff --git a/lib/mcp/servers/pugconvert/logic/jetvalidation.v b/lib/mcp/pugconvert/logic/jetvalidation.v similarity index 100% rename from lib/mcp/servers/pugconvert/logic/jetvalidation.v rename to lib/mcp/pugconvert/logic/jetvalidation.v diff --git a/lib/mcp/servers/pugconvert/logic/loader.v b/lib/mcp/pugconvert/logic/loader.v similarity index 100% rename from lib/mcp/servers/pugconvert/logic/loader.v rename to lib/mcp/pugconvert/logic/loader.v diff --git a/lib/mcp/servers/pugconvert/logic/templates/jet_instructions.md b/lib/mcp/pugconvert/logic/templates/jet_instructions.md similarity index 100% rename from lib/mcp/servers/pugconvert/logic/templates/jet_instructions.md rename to lib/mcp/pugconvert/logic/templates/jet_instructions.md diff --git a/lib/mcp/servers/pugconvert/mcp/handlers.v b/lib/mcp/pugconvert/mcp/handlers.v similarity index 100% rename from lib/mcp/servers/pugconvert/mcp/handlers.v rename to lib/mcp/pugconvert/mcp/handlers.v diff --git a/lib/mcp/servers/pugconvert/mcp/mcp.v b/lib/mcp/pugconvert/mcp/mcp.v similarity index 100% rename from lib/mcp/servers/pugconvert/mcp/mcp.v rename to lib/mcp/pugconvert/mcp/mcp.v diff --git a/lib/mcp/servers/pugconvert/mcp/specifications.v b/lib/mcp/pugconvert/mcp/specifications.v similarity index 100% rename from lib/mcp/servers/pugconvert/mcp/specifications.v rename to lib/mcp/pugconvert/mcp/specifications.v diff --git a/lib/mcp/servers/rhai/cmd/.gitignore b/lib/mcp/rhai/cmd/.gitignore similarity index 100% rename from lib/mcp/servers/rhai/cmd/.gitignore rename to lib/mcp/rhai/cmd/.gitignore diff --git a/lib/mcp/servers/rhai/cmd/compile.sh b/lib/mcp/rhai/cmd/compile.sh similarity index 100% rename from lib/mcp/servers/rhai/cmd/compile.sh rename to lib/mcp/rhai/cmd/compile.sh diff --git a/lib/mcp/rhai/cmd/main.v b/lib/mcp/rhai/cmd/main.v new file mode 100644 index 00000000..827a3f90 --- /dev/null +++ b/lib/mcp/rhai/cmd/main.v @@ -0,0 +1,18 @@ +module main + +import freeflowuniverse.herolib.mcp.rhai.mcp +import log + +fn main() { + // Create a new MCP server + mut server := mcp.new_mcp_server() or { + log.error('Failed to create MCP server: ${err}') + return + } + + // Start the server + server.start() or { + log.error('Failed to start MCP server: ${err}') + return + } +} diff --git a/lib/mcp/servers/rhai/example/example copy.vsh b/lib/mcp/rhai/example/example copy.vsh old mode 100755 new mode 100644 similarity index 100% rename from lib/mcp/servers/rhai/example/example copy.vsh rename to lib/mcp/rhai/example/example copy.vsh diff --git a/lib/mcp/servers/rhai/example/example.vsh b/lib/mcp/rhai/example/example.vsh similarity index 100% rename from lib/mcp/servers/rhai/example/example.vsh rename to lib/mcp/rhai/example/example.vsh diff --git a/lib/mcp/rhai/logic/logic.v b/lib/mcp/rhai/logic/logic.v new file mode 100644 index 00000000..32ee4db7 --- /dev/null +++ b/lib/mcp/rhai/logic/logic.v @@ -0,0 +1,279 @@ +module logic + +import freeflowuniverse.herolib.ai.escalayer +import freeflowuniverse.herolib.lang.rust +import freeflowuniverse.herolib.ai.utils +import os + +pub fn generate_rhai_wrapper(name string, source_path string) !string { + prompt := rhai_wrapper_generation_prompt(name, source_path) or {panic(err)} + return run_wrapper_generation_task(prompt, RhaiGen{ + name: name + dir: source_path + }) or {panic(err)} +} + +// Runs the task to generate Rhai wrappers +pub fn run_wrapper_generation_task(prompt_content string, gen RhaiGen) !string { + // Create a new task + mut task := escalayer.new_task( + name: 'rhai_wrapper_creator.escalayer' + description: 'Create Rhai wrappers for Rust functions that follow builder pattern and create examples corresponding to the provided example file' + ) + + // Create model configs + sonnet_model := escalayer.ModelConfig{ + name: 'anthropic/claude-3.7-sonnet' + provider: 'anthropic' + temperature: 0.7 + max_tokens: 25000 + } + + gpt4_model := escalayer.ModelConfig{ + name: 'gpt-4' + provider: 'openai' + temperature: 0.7 + max_tokens: 25000 + } + + // Create a prompt function that returns the prepared content + prompt_function := fn [prompt_content] (input string) string { + return prompt_content + } + + // Define a single unit task that handles everything + task.new_unit_task( + name: 'create_rhai_wrappers' + prompt_function: prompt_function + callback_function: gen.process_rhai_wrappers + base_model: sonnet_model + retry_model: gpt4_model + retry_count: 1 + ) + + // Initiate the task + return task.initiate('') +} + +// Define a Rhai wrapper generator function for Container functions +pub fn rhai_wrapper_generation_prompt(name string, source_code string) !string { + current_dir := os.dir(@FILE) + example_rhai := os.read_file('${current_dir}/prompts/example_script.md') or {panic(err)} + wrapper_md := os.read_file('${current_dir}/prompts/wrapper.md') or {panic(err)} + errors_md := os.read_file('${current_dir}/prompts/errors.md') or {panic(err)} + + // Load all required template and guide files + guides := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhaiwrapping_classicai.md')! + engine := $tmpl('./prompts/engine.md') + vector_vs_array := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_array_vs_vector.md')! + rhai_integration_fixes := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_integration_fixes.md')! + rhai_syntax_guide := os.read_file('/Users/timurgordon/code/git.ourworld.tf/herocode/sal/aiprompts/rhai_syntax_guide.md')! + generic_wrapper_rs := $tmpl('./templates/generic_wrapper.rs') + + prompt := $tmpl('./prompts/main.md') + return prompt +} + +@[params] +pub struct WrapperModule { +pub: + lib_rs string + example_rs string + engine_rs string + cargo_toml string + example_rhai string + generic_wrapper_rs string + wrapper_rs string +} + +// functions is a list of function names that AI should extract and pass in +pub fn write_rhai_wrapper_module(wrapper WrapperModule, name string, path string)! string { + + // Define project directory paths + project_dir := '${path}/rhai' + + // Create the project using cargo new --lib + if os.exists(project_dir) { + os.rmdir_all(project_dir) or { + return error('Failed to clean existing project directory: ${err}') + } + } + + // Run cargo new --lib to create the project + os.chdir(path) or { + return error('Failed to change directory to base directory: ${err}') + } + + cargo_new_result := os.execute('cargo new --lib rhai') + if cargo_new_result.exit_code != 0 { + return error('Failed to create new library project: ${cargo_new_result.output}') + } + + // Create examples directory + examples_dir := '${project_dir}/examples' + os.mkdir_all(examples_dir) or { + return error('Failed to create examples directory: ${err}') + } + + // Write the lib.rs file + if wrapper.lib_rs != '' { + os.write_file('${project_dir}/src/lib.rs', wrapper.lib_rs) or { + return error('Failed to write lib.rs: ${err}') + } + } + + // Write the wrapper.rs file + if wrapper.wrapper_rs != '' { + os.write_file('${project_dir}/src/wrapper.rs', wrapper.wrapper_rs) or { + return error('Failed to write wrapper.rs: ${err}') + } + } + + // Write the generic wrapper.rs file + if wrapper.generic_wrapper_rs != '' { + os.write_file('${project_dir}/src/generic_wrapper.rs', wrapper.generic_wrapper_rs) or { + return error('Failed to write generic wrapper.rs: ${err}') + } + } + + // Write the example.rs file + if wrapper.example_rs != '' { + os.write_file('${examples_dir}/example.rs', wrapper.example_rs) or { + return error('Failed to write example.rs: ${err}') + } + } + + // Write the engine.rs file if provided + if wrapper.engine_rs != '' { + os.write_file('${project_dir}/src/engine.rs', wrapper.engine_rs) or { + return error('Failed to write engine.rs: ${err}') + } + } + + // Write the Cargo.toml file + os.write_file('${project_dir}/Cargo.toml', wrapper.cargo_toml) or { + return error('Failed to write Cargo.toml: ${err}') + } + + // Write the example.rhai file + os.write_file('${examples_dir}/example.rhai', wrapper.example_rhai) or { + return error('Failed to write example.rhai: ${err}') + } + + return project_dir +} + + + +// Extract module name from wrapper code +fn extract_module_name(code string) string { + lines := code.split('\n') + + for line in lines { + // Look for pub mod or mod declarations + if line.contains('pub mod ') || line.contains('mod ') { + // Extract module name + mut parts := []string{} + if line.contains('pub mod ') { + parts = line.split('pub mod ') + } else { + parts = line.split('mod ') + } + + if parts.len > 1 { + // Extract the module name and remove any trailing characters + mut name := parts[1].trim_space() + // Remove any trailing { or ; or whitespace + name = name.trim_right('{').trim_right(';').trim_space() + if name != '' { + return name + } + } + } + } + + return '' +} + +// RhaiGen struct for generating Rhai wrappers +struct RhaiGen { + name string + dir string +} + +// Process the AI response and compile the generated code +fn (gen RhaiGen) process_rhai_wrappers(response string)! string { + // Extract code blocks from the response + code_blocks := extract_code_blocks(response) or { + return err + } + + name := gen.name + + // Create a WrapperModule struct with the extracted content + wrapper := WrapperModule{ + lib_rs: $tmpl('./templates/lib.rs') + wrapper_rs: code_blocks.wrapper_rs + example_rs: $tmpl('./templates/example.rs') + engine_rs: code_blocks.engine_rs + generic_wrapper_rs: $tmpl('./templates/generic_wrapper.rs') + cargo_toml: $tmpl('./templates/cargo.toml') + example_rhai: code_blocks.example_rhai + } + + // Create the wrapper module + project_dir := write_rhai_wrapper_module(wrapper, gen.name, gen.dir) or { + return error('Failed to create wrapper module: ${err}') + } + + // Build and run the project + build_output, run_output := rust.run_example(project_dir, 'example') or { + return err + } + + return format_success_message(project_dir, build_output, run_output) +} + +// CodeBlocks struct to hold extracted code blocks +struct CodeBlocks { + wrapper_rs string + engine_rs string + example_rhai string +} + +// Extract code blocks from the AI response +fn extract_code_blocks(response string)! CodeBlocks { + // Extract wrapper.rs content + wrapper_rs_content := utils.extract_code_block(response, 'wrapper.rs', 'rust') + if wrapper_rs_content == '' { + return error('Failed to extract wrapper.rs content from response. Please ensure your code is properly formatted inside a code block that starts with ```rust\n// wrapper.rs and ends with ```') + } + + // Extract engine.rs content + mut engine_rs_content := utils.extract_code_block(response, 'engine.rs', 'rust') + if engine_rs_content == '' { + // Try to extract from the response without explicit language marker + engine_rs_content = utils.extract_code_block(response, 'engine.rs', '') + } + + // Extract example.rhai content + mut example_rhai_content := utils.extract_code_block(response, 'example.rhai', 'rhai') + if example_rhai_content == '' { + // Try to extract from the response without explicit language marker + example_rhai_content = utils.extract_code_block(response, 'example.rhai', '') + if example_rhai_content == '' { + return error('Failed to extract example.rhai content from response. Please ensure your code is properly formatted inside a code block that starts with ```rhai\n// example.rhai and ends with ```') + } + } + + return CodeBlocks{ + wrapper_rs: wrapper_rs_content + engine_rs: engine_rs_content + example_rhai: example_rhai_content + } +} + +// Format success message +fn format_success_message(project_dir string, build_output string, run_output string) string { + return 'Successfully generated Rhai wrappers and ran the example!\n\nProject created at: ${project_dir}\n\nBuild output:\n${build_output}\n\nRun output:\n${run_output}' +} diff --git a/lib/mcp/servers/rhai/logic/prompts/engine.md b/lib/mcp/rhai/logic/prompts/engine.md similarity index 100% rename from lib/mcp/servers/rhai/logic/prompts/engine.md rename to lib/mcp/rhai/logic/prompts/engine.md diff --git a/lib/mcp/servers/rhai/logic/prompts/errors.md b/lib/mcp/rhai/logic/prompts/errors.md similarity index 100% rename from lib/mcp/servers/rhai/logic/prompts/errors.md rename to lib/mcp/rhai/logic/prompts/errors.md diff --git a/lib/mcp/servers/rhai/logic/prompts/example_script.md b/lib/mcp/rhai/logic/prompts/example_script.md similarity index 100% rename from lib/mcp/servers/rhai/logic/prompts/example_script.md rename to lib/mcp/rhai/logic/prompts/example_script.md diff --git a/lib/mcp/rhai/logic/prompts/main.md b/lib/mcp/rhai/logic/prompts/main.md new file mode 100644 index 00000000..b5b73092 --- /dev/null +++ b/lib/mcp/rhai/logic/prompts/main.md @@ -0,0 +1,101 @@ +You are a Rust developer tasked with creating Rhai wrappers for Rust functions. Please review the following best practices for Rhai wrappers and then create the necessary files. +@{guides} +@{vector_vs_array} +@{example_rhai} +@{wrapper_md} + +## Common Errors to Avoid +@{errors_md} +@{rhai_integration_fixes} +@{rhai_syntax_guide} + +## Your Task + +Please create a wrapper.rs file that implements Rhai wrappers for the provided Rust code, and an example.rhai script that demonstrates how to use these wrappers: + +## Rust Code to Wrap + +```rust +@{source_code} +``` + +IMPORTANT NOTES: +1. For Rhai imports, use: `use rhai::{Engine, EvalAltResult, plugin::*, Dynamic, Map, Array};` - only import what you actually use +2. The following dependencies are available in Cargo.toml: + - rhai = "1.21.0" + - serde = { version = "1.0", features = ["derive"] } + - serde_json = "1.0" + - sal = { path = "../../../" } + +3. For the wrapper: `use sal::@{name};` this way you can access the module functions and objects with @{name}:: + +4. The generic_wrapper.rs file will be hardcoded into the package, you can use code from there. + +```rust +@{generic_wrapper_rs} +``` + +5. IMPORTANT: Prefer strongly typed return values over Dynamic types whenever possible. Only use Dynamic when absolutely necessary. + - For example, return `Result>` instead of `Dynamic` when a function returns a string + - Use `Result>` instead of `Dynamic` when a function returns a boolean + - Use `Result, Box>` instead of `Dynamic` when a function returns a list of strings + +6. Your code should include public functions that can be called from Rhai scripts + +7. Make sure to implement all necessary helper functions for type conversion + +8. DO NOT use the #[rhai_fn] attribute - functions will be registered directly in the engine + +9. Make sure to handle string type consistency - use String::from() for string literals when returning in match arms with format!() strings + +10. When returning path references, convert them to owned strings (e.g., path().to_string()) + +11. For error handling, use proper Result types with Box for the error type: + ```rust + // INCORRECT: + pub fn some_function(arg: &str) -> Dynamic { + match some_operation(arg) { + Ok(result) => Dynamic::from(result), + Err(err) => Dynamic::from(format!("Error: {}", err)) + } + } + + // CORRECT: + pub fn some_function(arg: &str) -> Result> { + some_operation(arg).map_err(|err| { + Box::new(EvalAltResult::ErrorRuntime( + format!("Error: {}", err).into(), + rhai::Position::NONE + )) + }) + } + ``` + +12. IMPORTANT: Format your response with the code between triple backticks as follows: + +```rust +// wrapper.rs +// Your wrapper implementation here +``` + +```rust +// engine.rs +// Your engine.rs implementation here +``` + +```rhai +// example.rhai +// Your example Rhai script here +``` + +13. The example.rhai script should demonstrate the use of all the wrapper functions you create + +14. The engine.rs file should contain a register_module function that registers all the wrapper functions and types with the Rhai engine, and a create function. For example: + +@{engine} + +MOST IMPORTANT: +import package being wrapped as `use sal::` +your engine create function is called `create_rhai_engine` + +``` diff --git a/lib/mcp/servers/rhai/logic/prompts/wrapper.md b/lib/mcp/rhai/logic/prompts/wrapper.md similarity index 100% rename from lib/mcp/servers/rhai/logic/prompts/wrapper.md rename to lib/mcp/rhai/logic/prompts/wrapper.md diff --git a/lib/mcp/servers/rhai/logic/templates/cargo.toml b/lib/mcp/rhai/logic/templates/cargo.toml similarity index 100% rename from lib/mcp/servers/rhai/logic/templates/cargo.toml rename to lib/mcp/rhai/logic/templates/cargo.toml diff --git a/lib/mcp/servers/rhai/logic/templates/engine.rs b/lib/mcp/rhai/logic/templates/engine.rs similarity index 100% rename from lib/mcp/servers/rhai/logic/templates/engine.rs rename to lib/mcp/rhai/logic/templates/engine.rs diff --git a/lib/mcp/servers/rhai/logic/templates/example.rs b/lib/mcp/rhai/logic/templates/example.rs similarity index 100% rename from lib/mcp/servers/rhai/logic/templates/example.rs rename to lib/mcp/rhai/logic/templates/example.rs diff --git a/lib/mcp/servers/rhai/logic/templates/functions.rs b/lib/mcp/rhai/logic/templates/functions.rs similarity index 100% rename from lib/mcp/servers/rhai/logic/templates/functions.rs rename to lib/mcp/rhai/logic/templates/functions.rs diff --git a/lib/mcp/servers/rhai/logic/templates/generic_wrapper.rs b/lib/mcp/rhai/logic/templates/generic_wrapper.rs similarity index 100% rename from lib/mcp/servers/rhai/logic/templates/generic_wrapper.rs rename to lib/mcp/rhai/logic/templates/generic_wrapper.rs diff --git a/lib/mcp/servers/rhai/logic/templates/lib.rs b/lib/mcp/rhai/logic/templates/lib.rs similarity index 100% rename from lib/mcp/servers/rhai/logic/templates/lib.rs rename to lib/mcp/rhai/logic/templates/lib.rs diff --git a/lib/mcp/rhai/mcp/command.v b/lib/mcp/rhai/mcp/command.v new file mode 100644 index 00000000..fc8c82d7 --- /dev/null +++ b/lib/mcp/rhai/mcp/command.v @@ -0,0 +1,23 @@ +module mcp + +import cli + +pub const command := cli.Command{ + sort_flags: true + name: 'rhai' + // execute: cmd_mcpgen + description: 'rhai command' + commands: [ + cli.Command{ + name: 'start' + execute: cmd_start + description: 'start the Rhai server' + } + ] +} + +fn cmd_start(cmd cli.Command) ! { + mut server := new_mcp_server()! + server.start()! +} + diff --git a/lib/mcp/servers/rhai/mcp/mcp.v b/lib/mcp/rhai/mcp/mcp.v similarity index 54% rename from lib/mcp/servers/rhai/mcp/mcp.v rename to lib/mcp/rhai/mcp/mcp.v index c74f1d15..a47bfdac 100644 --- a/lib/mcp/servers/rhai/mcp/mcp.v +++ b/lib/mcp/rhai/mcp/mcp.v @@ -1,27 +1,33 @@ -module pugconvert +module mcp import freeflowuniverse.herolib.mcp -import freeflowuniverse.herolib.mcp.logger import freeflowuniverse.herolib.schemas.jsonrpc +import log pub fn new_mcp_server() !&mcp.Server { - logger.info('Creating new Rhai MCP server') + log.info('Creating new Developer MCP server') // Initialize the server with the empty handlers map mut server := mcp.new_server(mcp.MemoryBackend{ tools: { - 'rhai_interface': specs + 'generate_rhai_wrapper': generate_rhai_wrapper_spec } tool_handlers: { - 'rhai_interface': handler + 'generate_rhai_wrapper': generate_rhai_wrapper_handler + } + prompts: { + 'rhai_wrapper': rhai_wrapper_prompt_spec + } + prompt_handlers: { + 'rhai_wrapper': rhai_wrapper_prompt_handler } }, mcp.ServerParams{ config: mcp.ServerConfiguration{ server_info: mcp.ServerInfo{ - name: 'developer' + name: 'rhai' version: '1.0.0' } } })! return server -} +} \ No newline at end of file diff --git a/lib/mcp/rhai/mcp/prompts.v b/lib/mcp/rhai/mcp/prompts.v new file mode 100644 index 00000000..95432ccf --- /dev/null +++ b/lib/mcp/rhai/mcp/prompts.v @@ -0,0 +1,42 @@ +module mcp + +import freeflowuniverse.herolib.mcp +import freeflowuniverse.herolib.core.code +import freeflowuniverse.herolib.mcp.rhai.logic +import freeflowuniverse.herolib.schemas.jsonschema +import freeflowuniverse.herolib.lang.rust +import x.json2 as json { Any } + +// Tool definition for the create_rhai_wrapper function +const rhai_wrapper_prompt_spec = mcp.Prompt{ + name: 'rhai_wrapper' + description: 'provides a prompt for creating Rhai wrappers for Rust functions that follow builder pattern and create examples corresponding to the provided example file' + arguments: [ + mcp.PromptArgument{ + name: 'source_path' + description: 'Path to the source directory' + required: true + } + ] +} + +// Tool handler for the create_rhai_wrapper function +pub fn rhai_wrapper_prompt_handler(arguments []string) ![]mcp.PromptMessage { + source_path := arguments[0] + + // Read and combine all Rust files in the source directory + source_code := rust.read_source_code(source_path)! + + // Extract the module name from the directory path (last component) + name := rust.extract_module_name_from_path(source_path) + + +result := logic.rhai_wrapper_generation_prompt(name, source_code)! +return [mcp.PromptMessage{ + role: 'assistant' + content: mcp.PromptContent{ + typ: 'text' + text: result + } +}] +} \ No newline at end of file diff --git a/lib/mcp/servers/rhai/mcp/specifications.v b/lib/mcp/rhai/mcp/specifications.v similarity index 89% rename from lib/mcp/servers/rhai/mcp/specifications.v rename to lib/mcp/rhai/mcp/specifications.v index a1f43a07..61e0cdd1 100644 --- a/lib/mcp/servers/rhai/mcp/specifications.v +++ b/lib/mcp/rhai/mcp/specifications.v @@ -1,9 +1,9 @@ -module pugconvert +module mcp import freeflowuniverse.herolib.mcp import x.json2 as json { Any } import freeflowuniverse.herolib.schemas.jsonschema -import freeflowuniverse.herolib.mcp.logger +import log const specs = mcp.Tool{ name: 'rhai_interface' diff --git a/lib/mcp/rhai/mcp/tools.v b/lib/mcp/rhai/mcp/tools.v new file mode 100644 index 00000000..2732a92d --- /dev/null +++ b/lib/mcp/rhai/mcp/tools.v @@ -0,0 +1,39 @@ +module mcp + +import freeflowuniverse.herolib.mcp +import freeflowuniverse.herolib.core.code +import freeflowuniverse.herolib.mcp.rhai.logic +import freeflowuniverse.herolib.schemas.jsonschema +import x.json2 as json { Any } + +// Tool definition for the generate_rhai_wrapper function +const generate_rhai_wrapper_spec = mcp.Tool{ + name: 'generate_rhai_wrapper' + description: 'generate_rhai_wrapper receives the name of a V language function string, and the path to the module in which it exists.' + input_schema: jsonschema.Schema{ + typ: 'object' + properties: { + 'name': jsonschema.SchemaRef(jsonschema.Schema{ + typ: 'string' + }), + 'source_path': jsonschema.SchemaRef(jsonschema.Schema{ + typ: 'string' + }) + } + required: ['name', 'source_path'] + } +} + +// Tool handler for the generate_rhai_wrapper function +pub fn generate_rhai_wrapper_handler(arguments map[string]Any) !mcp.ToolCallResult { + name := arguments['name'].str() + source_path := arguments['source_path'].str() + result := logic.generate_rhai_wrapper(name, source_path) + or { + return mcp.error_tool_call_result(err) + } + return mcp.ToolCallResult{ + is_error: false + content: mcp.result_to_mcp_tool_contents[string](result) + } +} diff --git a/lib/mcp/rhai/rhai.v b/lib/mcp/rhai/rhai.v new file mode 100644 index 00000000..28d59606 --- /dev/null +++ b/lib/mcp/rhai/rhai.v @@ -0,0 +1 @@ +module rhai \ No newline at end of file diff --git a/lib/mcp/core/server.v b/lib/mcp/server.v similarity index 90% rename from lib/mcp/core/server.v rename to lib/mcp/server.v index 2fdfdbcd..c2055f11 100644 --- a/lib/mcp/core/server.v +++ b/lib/mcp/server.v @@ -5,7 +5,6 @@ import os import log import x.json2 import freeflowuniverse.herolib.schemas.jsonrpc -import freeflowuniverse.herolib.mcp.logger // Server is the main MCP server struct @[heap] @@ -19,7 +18,7 @@ pub mut: // start starts the MCP server pub fn (mut s Server) start() ! { - logger.info('Starting MCP server') + log.info('Starting MCP server') for { // Read a message from stdin message := os.get_line() @@ -30,6 +29,7 @@ pub fn (mut s Server) start() ! { // Handle the message using the JSON-RPC handler response := s.handler.handle(message) or { + log.error('message: ${message}') log.error('Error handling message: ${err}') // Try to extract the request ID @@ -49,6 +49,7 @@ pub fn (mut s Server) start() ! { // send sends a response to the client pub fn (mut s Server) send(response string) { // Send the response + log.error('Sending response: ${response}') println(response) flush_stdout() } diff --git a/lib/mcp/servers/rhai/cmd/main.v b/lib/mcp/servers/rhai/cmd/main.v deleted file mode 100644 index 815dc23b..00000000 --- a/lib/mcp/servers/rhai/cmd/main.v +++ /dev/null @@ -1,17 +0,0 @@ -module main - -import freeflowuniverse.herolib.mcp.pugconvert - -fn main() { - // Create a new MCP server - mut server := pugconvert.new_mcp_server() or { - eprintln('Failed to create MCP server: ${err}') - return - } - - // Start the server - server.start() or { - eprintln('Failed to start MCP server: ${err}') - return - } -} diff --git a/lib/mcp/servers/rhai/mcp/handlers.v b/lib/mcp/servers/rhai/mcp/handlers.v deleted file mode 100644 index 2bfc0627..00000000 --- a/lib/mcp/servers/rhai/mcp/handlers.v +++ /dev/null @@ -1,58 +0,0 @@ -module rhaiconvert - -import freeflowuniverse.herolib.mcp -import x.json2 as json { Any } -import freeflowuniverse.herolib.mcp.servers.rhai.logic as rhaido -import freeflowuniverse.herolib.core.pathlib -import os - -//TODO: implement - -pub fn handler(arguments map[string]Any) !mcp.ToolCallResult { - path := arguments['path'].str() - - // Check if path exists - if !os.exists(path) { - return mcp.ToolCallResult{ - is_error: true - content: mcp.result_to_mcp_tool_contents[string]("Error: Path '${path}' does not exist") - } - } - - // Determine if path is a file or directory - is_directory := os.is_dir(path) - - mut message := "" - - //TODO: implement - - if is_directory { - // Convert all rhai files in the directory - rhaido.convert_rhai(path) or { - return mcp.ToolCallResult{ - is_error: true - content: mcp.result_to_mcp_tool_contents[string]("Error converting rhai files in directory: ${err}") - } - } - message = "Successfully converted all rhai files in directory '${path}'" - } else if path.ends_with(".rhai") { - // Convert a single rhai file - rhaido.convert_rhai_file(path) or { - return mcp.ToolCallResult{ - is_error: true - content: mcp.result_to_mcp_tool_contents[string]("Error converting rhai file: ${err}") - } - } - message = "Successfully converted rhai file '${path}'" - } else { - return mcp.ToolCallResult{ - is_error: true - content: mcp.result_to_mcp_tool_contents[string]("Error: Path '${path}' is not a directory or .rhai file") - } - } - - return mcp.ToolCallResult{ - is_error: false - content: mcp.result_to_mcp_tool_contents[string](message) - } -} diff --git a/lib/mcp/servers/vcode/README.md b/lib/mcp/vcode/README.md similarity index 100% rename from lib/mcp/servers/vcode/README.md rename to lib/mcp/vcode/README.md diff --git a/lib/mcp/servers/vcode/cmd/.gitignore b/lib/mcp/vcode/cmd/.gitignore similarity index 100% rename from lib/mcp/servers/vcode/cmd/.gitignore rename to lib/mcp/vcode/cmd/.gitignore diff --git a/lib/mcp/servers/vcode/cmd/compile.sh b/lib/mcp/vcode/cmd/compile.sh similarity index 100% rename from lib/mcp/servers/vcode/cmd/compile.sh rename to lib/mcp/vcode/cmd/compile.sh diff --git a/lib/mcp/servers/vcode/cmd/main.v b/lib/mcp/vcode/cmd/main.v similarity index 84% rename from lib/mcp/servers/vcode/cmd/main.v rename to lib/mcp/vcode/cmd/main.v index 96eef4c8..eb7841f8 100644 --- a/lib/mcp/servers/vcode/cmd/main.v +++ b/lib/mcp/vcode/cmd/main.v @@ -1,6 +1,6 @@ module main -import freeflowuniverse.herolib.mcp.servers.vcode +import freeflowuniverse.herolib.mcp.vcode fn main() { // Create a new MCP server diff --git a/lib/mcp/servers/vcode/logic/server.v b/lib/mcp/vcode/logic/server.v similarity index 100% rename from lib/mcp/servers/vcode/logic/server.v rename to lib/mcp/vcode/logic/server.v diff --git a/lib/mcp/servers/vcode/logic/test_client.vsh b/lib/mcp/vcode/logic/test_client.vsh similarity index 100% rename from lib/mcp/servers/vcode/logic/test_client.vsh rename to lib/mcp/vcode/logic/test_client.vsh diff --git a/lib/mcp/servers/vcode/logic/vlang.v b/lib/mcp/vcode/logic/vlang.v similarity index 100% rename from lib/mcp/servers/vcode/logic/vlang.v rename to lib/mcp/vcode/logic/vlang.v diff --git a/lib/mcp/servers/vcode/logic/vlang_test.v b/lib/mcp/vcode/logic/vlang_test.v similarity index 100% rename from lib/mcp/servers/vcode/logic/vlang_test.v rename to lib/mcp/vcode/logic/vlang_test.v diff --git a/lib/mcp/servers/vcode/logic/vlang_tools.v b/lib/mcp/vcode/logic/vlang_tools.v similarity index 100% rename from lib/mcp/servers/vcode/logic/vlang_tools.v rename to lib/mcp/vcode/logic/vlang_tools.v diff --git a/lib/mcp/servers/vcode/logic/write_vfile_tool.v b/lib/mcp/vcode/logic/write_vfile_tool.v similarity index 100% rename from lib/mcp/servers/vcode/logic/write_vfile_tool.v rename to lib/mcp/vcode/logic/write_vfile_tool.v diff --git a/lib/mcp/servers/vcode/mcp/handlers.v b/lib/mcp/vcode/mcp/handlers.v similarity index 100% rename from lib/mcp/servers/vcode/mcp/handlers.v rename to lib/mcp/vcode/mcp/handlers.v diff --git a/lib/mcp/servers/vcode/mcp/mcp.v b/lib/mcp/vcode/mcp/mcp.v similarity index 100% rename from lib/mcp/servers/vcode/mcp/mcp.v rename to lib/mcp/vcode/mcp/mcp.v diff --git a/lib/mcp/servers/vcode/mcp/specifications.v b/lib/mcp/vcode/mcp/specifications.v similarity index 100% rename from lib/mcp/servers/vcode/mcp/specifications.v rename to lib/mcp/vcode/mcp/specifications.v