From e679874939b8bd3118adf09234957654243e2b29 Mon Sep 17 00:00:00 2001 From: Kayos Date: Fri, 8 May 2026 18:50:26 -0700 Subject: [PATCH] feat(mcp): add policy_cbor_path arg to wallet_plutus_mint_unsigned MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors the reference_script_path workaround already on wallet_send. Lets the caller hand aldabra a path to a hex-CBOR file inside the container instead of pasting the hex inline as a JSON-RPC arg, which lets us bypass the 2026-05-07 MCP large-string transport bug (>~ 4500 char hex strings get a 1-byte truncation between Claude Code and aldabra's stdio reader, surfacing as 'odd length' decode errors and blocking debug-build minting policies). policy_cbor_hex becomes Option; new policy_cbor_path: Option sits next to it. New resolver helper resolve_policy_cbor_bytes mirrors resolve_ref_script_bytes — at most one of the two may be set, exactly one must be set. Whitespace is stripped from file contents so the file may have trailing newlines. Unblocks Track #33 / preprod_test3: debug-build validators that overshoot the transport ceiling can now be minted via path arg. --- crates/aldabra-mcp/src/tools.rs | 68 +++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/crates/aldabra-mcp/src/tools.rs b/crates/aldabra-mcp/src/tools.rs index 1de9609..7e41ad3 100644 --- a/crates/aldabra-mcp/src/tools.rs +++ b/crates/aldabra-mcp/src/tools.rs @@ -103,6 +103,48 @@ fn resolve_ref_script_bytes( } } +/// Resolve the Plutus minting-policy CBOR from EITHER an inline +/// hex argument OR a file path inside the container. Caller passes +/// both raw options; this fn enforces the "exactly one" rule and +/// reads the file when path is set. +/// +/// Mirrors [`resolve_ref_script_bytes`] — same workaround for the +/// 2026-05-07 MCP transport bug where hex strings >~ 4500 chars +/// get a 1-byte truncation between Claude Code and aldabra's stdio +/// reader, surfacing as "odd length" hex decode errors and blocking +/// debug-build minting policies. Reading from a file inside the +/// container bypasses the JSON-RPC arg path entirely. +fn resolve_policy_cbor_bytes( + cbor_hex: Option<&str>, + path: Option<&str>, +) -> Result, String> { + match (cbor_hex, path) { + (Some(_), Some(_)) => Err( + "set at most one of policy_cbor_hex / policy_cbor_path".into(), + ), + (Some(s), None) => { + let cleaned: String = s.chars().filter(|c| !c.is_whitespace()).collect(); + hex_decode(&cleaned).map_err(|e| format!("decode policy_cbor_hex: {e}")) + } + (None, Some(p)) => { + let raw = std::fs::read_to_string(p) + .map_err(|e| format!("read policy_cbor_path '{p}': {e}"))?; + let cleaned: String = raw.chars().filter(|c| !c.is_whitespace()).collect(); + if cleaned.is_empty() { + return Err(format!( + "policy_cbor_path '{p}' contained no hex characters" + )); + } + hex_decode(&cleaned).map_err(|e| { + format!("decode policy_cbor_path '{p}' contents: {e}") + }) + } + (None, None) => { + Err("must set exactly one of policy_cbor_hex / policy_cbor_path".into()) + } + } +} + /// Parse a user-supplied script-kind string ("PlutusV1" / "PlutusV2" /// / "PlutusV3" / "Native") into the pallas `ScriptKind` enum used /// by the reference-script attachment helper. Case-insensitive, @@ -398,7 +440,22 @@ pub struct PolicyCreateArgs { pub struct PlutusMintUnsignedArgs { /// Plutus minting policy script CBOR (hex). 28-byte blake2b /// hash with the version tag becomes the policy_id. - pub policy_cbor_hex: String, + /// At most one of `policy_cbor_hex` or `policy_cbor_path` may + /// be set; exactly one must be set. + #[serde(default)] + pub policy_cbor_hex: Option, + /// Path INSIDE THE ALDABRA CONTAINER to a file containing + /// hex-encoded Plutus policy CBOR. Use INSTEAD of + /// `policy_cbor_hex` for scripts >~ 4500 chars to bypass the + /// MCP large-string transport bug (caught 2026-05-07: hex strings + /// > ~4500 chars get a 1-byte truncation + structural rearrangement + /// somewhere between Claude Code and aldabra's stdio reader, + /// surfacing as "odd length" hex decode errors). File contents + /// may include leading/trailing whitespace; only hex chars are + /// decoded. At most one of `policy_cbor_hex` or `policy_cbor_path` + /// may be set; exactly one must be set. + #[serde(default)] + pub policy_cbor_path: Option, /// Plutus version: "v1", "v2", or "v3". pub policy_version: String, /// PlutusData CBOR redeemer (hex) for the mint redeemer entry. @@ -1612,12 +1669,13 @@ impl WalletService { #[tool( name = "wallet_plutus_mint_unsigned", - description = "Build (no sign) a Plutus-policy mint with custom output. Distinct from wallet_mint (native script only). Args: policy_cbor_hex + policy_version (v1/v2/v3) + redeemer_cbor_hex + ex_units (mem+steps), mint_assets (array of {asset_name_hex, quantity}), dest_address + dest_lovelace + dest_extra_assets + dest_inline_datum_cbor_hex (req for script addrs), required_input_refs (array of 'txhash#index' UTxOs that MUST be spent — e.g. gstOutRef for parameterized policies). Returns JSON {cbor_hex, summary}. Pass through wallet_sign_partial → wallet_submit_signed_tx. Designed for Agora-style DAO bringup: governor bootstrap, stake bootstrap, proposal create." + description = "Build (no sign) a Plutus-policy mint with custom output. Distinct from wallet_mint (native script only). Args: policy_cbor_hex OR policy_cbor_path (use path for >~4500-char scripts to bypass the MCP large-string transport bug) + policy_version (v1/v2/v3) + redeemer_cbor_hex + ex_units (mem+steps), mint_assets (array of {asset_name_hex, quantity}), dest_address + dest_lovelace + dest_extra_assets + dest_inline_datum_cbor_hex (req for script addrs), required_input_refs (array of 'txhash#index' UTxOs that MUST be spent — e.g. gstOutRef for parameterized policies). Returns JSON {cbor_hex, summary}. Pass through wallet_sign_partial → wallet_submit_signed_tx. Designed for Agora-style DAO bringup: governor bootstrap, stake bootstrap, proposal create." )] async fn wallet_plutus_mint_unsigned( &self, #[tool(aggr)] PlutusMintUnsignedArgs { policy_cbor_hex, + policy_cbor_path, policy_version, redeemer_cbor_hex, mint_assets, @@ -1639,8 +1697,10 @@ impl WalletService { )); } - let policy_cbor = - hex_decode(&policy_cbor_hex).map_err(|e| format!("decode policy_cbor: {e}"))?; + let policy_cbor = resolve_policy_cbor_bytes( + policy_cbor_hex.as_deref(), + policy_cbor_path.as_deref(), + )?; let redeemer_cbor = hex_decode(&redeemer_cbor_hex).map_err(|e| format!("decode redeemer: {e}"))?; let policy_ver = match policy_version.trim().to_ascii_lowercase().as_str() {