// Code generated by scripts/generate-mbt-blender-sdk.py — DO NOT EDIT.
///|
/// Client manages a connection to the Blender MCP server.
/// It launches the server as a subprocess and communicates via JSON-RPC over stdio.
/// All tool methods are async and must be called within an `async fn main` block.
///
/// Usage:
/// async fn main {
/// let client = @blender.new_client("")
/// let res = client.execute_blender_code("import bpy; result = {'count': len(bpy.data.objects)}")
/// println(res)
/// client.close()
/// }
///
pub struct Client {
pid : Int
stdin : @process.WriteToProcess
stdout : @process.ReadFromProcess
mut msg_id : Int
}
///|
/// default_server_bin returns the default command to launch the Blender MCP server.
///
let default_server_bin : String = "uv"
///|
/// VERSION is the semver version of this package.
///
const VERSION : String = "0.2.0"
///|
/// default_server_args returns the default arguments for the Blender MCP server.
///
let default_server_args : Array[String] = [
"--directory",
expand_path("~/Projects/Blender/blender_mcp/mcp/blmcp"),
"run",
"blender-mcp",
]
///|
/// new_client launches the Blender MCP server and returns a Client.
/// If server_bin is empty, uses the default (uv run blender-mcp).
/// Must be called within an async function.
///
pub async fn new_client(server_bin : String) -> Client {
let (cmd, args) = if server_bin == "" {
(default_server_bin, default_server_args)
} else {
(server_bin, [])
}
let path = expand_path(cmd)
let (stdin_read, stdin_write) = @process.write_to_process()
let (stdout_read, stdout_write) = @process.read_from_process()
let pid = @process.spawn_orphan(
path,
args,
stdin=stdin_read,
stdout=stdout_write,
)
let client = Client::{
pid,
stdin: stdin_write,
stdout: stdout_read,
msg_id: 0,
}
client.initialize()
client
}
///|
/// close shuts down the MCP server process.
///
pub fn Client::close(self : Client) -> Unit {
self.stdin.close()
self.stdout.close()
}
///|
/// expand_path expands a leading ~ to the user's home directory.
///
fn expand_path(path : String) -> String {
if path.has_prefix("~") {
let home = @env.get_env_var("HOME").unwrap_or("")
home + path[1:].to_owned()
} else {
path
}
}
// --- JSON-RPC internals ---
///|
/// next_id returns the next JSON-RPC request ID.
///
fn Client::next_id(self : Client) -> Int {
self.msg_id = self.msg_id + 1
self.msg_id
}
///|
/// send_line writes a JSON-RPC message to the server's stdin.
///
async fn Client::send_line(self : Client, json_str : String) -> Unit {
let data = @utf8.encode(json_str + "\n")
let _ = self.stdin.write_once(data, offset=0, len=data.length())
}
///|
/// read_line reads a JSON-RPC response line from the server's stdout.
///
async fn Client::read_line(self : Client) -> String {
match self.stdout.read_until("\n") {
Some(line) => line
None => raise Failure::Failure("EOF: no more data from server")
}
}
///|
/// call_tool invokes an MCP tool by name and returns the text response.
///
pub async fn Client::call_tool(
self : Client,
tool_name : String,
args : Map[String, Json],
) -> String {
let id = self.next_id()
let req = Json::object(
Map([
("jsonrpc", Json::string("2.0")),
("id", Json::number(id.to_double())),
("method", Json::string("tools/call")),
(
"params",
Json::object(
Map([
("name", Json::string(tool_name)),
("arguments", Json::object(args)),
]),
),
),
]),
)
self.send_line(req.stringify())
let response = self.read_line()
try @json.parse(response) catch {
_ => return response
} noraise {
resp =>
match resp {
Object(fields) => {
let result = fields.get("result").unwrap()
match result {
Object(result_fields) => {
let content = result_fields.get("content").unwrap()
match content {
Array(items) => {
let mut text = ""
for item in items {
match item {
Object(item_fields) =>
match item_fields.get("text") {
Some(String(s)) => text = text + s
_ => ()
}
_ => ()
}
}
text
}
_ => raise Failure::Failure("unexpected content format")
}
}
_ => raise Failure::Failure("unexpected result format")
}
}
_ => response
}
}
}
///|
/// initialize performs the MCP initialize handshake.
///
async fn Client::initialize(self : Client) -> Unit {
let id = self.next_id()
let req = Json::object(
Map([
("jsonrpc", Json::string("2.0")),
("id", Json::number(id.to_double())),
("method", Json::string("initialize")),
(
"params",
Json::object(
Map([
("protocolVersion", Json::string("2024-11-05")),
("capabilities", Json::object(Map([]))),
(
"clientInfo",
Json::object(
Map([
("name", Json::string("blender")),
("version", Json::string(VERSION)),
]),
),
),
]),
),
),
]),
)
self.send_line(req.stringify())
let _ = self.read_line()
let notif = Json::object(
Map([
("jsonrpc", Json::string("2.0")),
("method", Json::string("notifications/initialized")),
]),
)
self.send_line(notif.stringify())
}