mcp refactor wip

This commit is contained in:
Timur Gordon
2025-04-13 13:28:55 +02:00
parent 1bc6c6eab8
commit f7dd227cd0
88 changed files with 855 additions and 107 deletions

View File

@@ -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)!

View File

@@ -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
}

34
lib/ai/utils/utils.v Normal file
View File

@@ -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
}

103
lib/lang/rust/rust.v Normal file
View File

@@ -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
}

View File

@@ -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

View File

@@ -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 {

68
lib/mcp/cmd/compile.vsh Executable file
View File

@@ -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**')

92
lib/mcp/cmd/mcp.v Normal file
View File

@@ -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')!
}
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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
})

View File

@@ -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:

View File

@@ -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

View File

@@ -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

18
lib/mcp/rhai/cmd/main.v Normal file
View File

@@ -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
}
}

279
lib/mcp/rhai/logic/logic.v Normal file
View File

@@ -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}'
}

View File

@@ -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<String, Box<EvalAltResult>>` instead of `Dynamic` when a function returns a string
- Use `Result<bool, Box<EvalAltResult>>` instead of `Dynamic` when a function returns a boolean
- Use `Result<Vec<String>, Box<EvalAltResult>>` 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<EvalAltResult> 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<String, Box<EvalAltResult>> {
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::<n>`
your engine create function is called `create_rhai_engine`
```

View File

@@ -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()!
}

View File

@@ -1,24 +1,30 @@
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'
}
}

View File

@@ -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
}
}]
}

View File

@@ -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'

39
lib/mcp/rhai/mcp/tools.v Normal file
View File

@@ -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)
}
}

1
lib/mcp/rhai/rhai.v Normal file
View File

@@ -0,0 +1 @@
module rhai

View File

@@ -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()
}

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -1,6 +1,6 @@
module main
import freeflowuniverse.herolib.mcp.servers.vcode
import freeflowuniverse.herolib.mcp.vcode
fn main() {
// Create a new MCP server