mcp: rename tool names to underscore-only (Claude Code compat)
Claude Code's MCP client validates tool names against
[a-zA-Z0-9_-]{1,64} and silently drops names containing dots.
aldabra was registering wallet.address etc. with dots; despite the
daemon running fine and rmcp accepting the names, Claude Code's
tools/list cache was empty for aldabra after `/exit + relaunch`.
discovered integration-time 2026-05-04 after first real session
restart with the wallet registered.
renamed:
wallet.address → wallet_address
wallet.network → wallet_network
wallet.balance → wallet_balance
wallet.utxos → wallet_utxos
wallet.send → wallet_send
wallet.send.unsigned → wallet_send_unsigned
wallet.tx_status → wallet_tx_status
wallet.tx_summary → wallet_tx_summary
wallet.sign_partial → wallet_sign_partial (already underscored)
wallet.submit_signed_tx → wallet_submit_signed_tx (ditto)
wallet.policy.create → wallet_policy_create
wallet.mint → wallet_mint (no change)
wallet.mint.cip68_nft → wallet_mint_cip68_nft
wallet.mint.unsigned → wallet_mint_unsigned
wallet.script.spend → wallet_script_spend
wallet.stake.address → wallet_stake_address
wallet.stake.delegate → wallet_stake_delegate
instructions blurb + module docstring updated. all 93 unit tests
still pass. fresh tools/list smoke confirmed: 17 tools all
underscore-only.
cobb needs to /exit + relaunch one more time for Claude Code to
re-handshake with the rebuilt binary.
This commit is contained in:
parent
f17479ab92
commit
36bbd8033f
1 changed files with 41 additions and 38 deletions
|
|
@ -1,21 +1,24 @@
|
|||
//! MCP tool handlers.
|
||||
//!
|
||||
//! Each `#[tool]` becomes a discoverable MCP tool. Tool names use
|
||||
//! dotted notation per the MCP convention; the underlying Rust fn
|
||||
//! names use snake_case.
|
||||
//! `snake_case` only (no dots) — Claude Code's MCP client validates
|
||||
//! tool names against `[a-zA-Z0-9_-]{1,64}` and silently drops names
|
||||
//! with dots. This was an integration-time discovery 2026-05-04 after
|
||||
//! the first session restart found zero aldabra tools advertised
|
||||
//! despite the daemon running.
|
||||
//!
|
||||
//! ## Phase 1 — read path
|
||||
//!
|
||||
//! - `wallet.address` — bech32 base address
|
||||
//! - `wallet.network` — mainnet | preview | preprod
|
||||
//! - `wallet.balance` — JSON `{lovelace, assets}`
|
||||
//! - `wallet.utxos` — JSON list of UTXOs
|
||||
//! - `wallet_address` — bech32 base address
|
||||
//! - `wallet_network` — mainnet | preview | preprod
|
||||
//! - `wallet_balance` — JSON `{lovelace, assets}`
|
||||
//! - `wallet_utxos` — JSON list of UTXOs
|
||||
//!
|
||||
//! ## Phase 2 — send path
|
||||
//!
|
||||
//! - `wallet.send` — build + sign + submit ADA payment, with hard
|
||||
//! - `wallet_send` — build + sign + submit ADA payment, with hard
|
||||
//! cap guard (`max_send_lovelace`)
|
||||
//! - `wallet.tx_status` — poll a submitted tx hash
|
||||
//! - `wallet_tx_status` — poll a submitted tx hash
|
||||
//!
|
||||
//! Returns:
|
||||
//! - `String` results pass through `IntoContents` directly.
|
||||
|
|
@ -94,9 +97,9 @@ impl WalletService {
|
|||
|
||||
/// Reject if `lovelace` exceeds the wallet's hard cap unless
|
||||
/// `force=true`. Used by every tool that moves lovelace to a
|
||||
/// non-wallet destination — wallet.send, wallet.mint,
|
||||
/// wallet.mint.cip68_nft, wallet.script.spend.
|
||||
/// (HIGH-1 audit fix: previously only wallet.send had this guard.)
|
||||
/// non-wallet destination — wallet_send, wallet_mint,
|
||||
/// wallet_mint_cip68_nft, wallet_script_spend.
|
||||
/// (HIGH-1 audit fix: previously only wallet_send had this guard.)
|
||||
fn enforce_value_cap(&self, lovelace: u64, force: bool) -> Result<(), String> {
|
||||
if lovelace > self.inner.max_send_lovelace && !force {
|
||||
return Err(format!(
|
||||
|
|
@ -127,7 +130,7 @@ pub struct SendArgs {
|
|||
|
||||
#[derive(Debug, Deserialize, schemars::JsonSchema)]
|
||||
pub struct TxStatusArgs {
|
||||
/// Hex-encoded transaction hash returned by `wallet.send`.
|
||||
/// Hex-encoded transaction hash returned by `wallet_send`.
|
||||
pub tx_hash: String,
|
||||
}
|
||||
|
||||
|
|
@ -146,7 +149,7 @@ pub struct UnsignedSendArgs {
|
|||
pub struct SubmitSignedArgs {
|
||||
/// Hex-encoded signed transaction CBOR — produced by an external
|
||||
/// cold-signer that consumed the unsigned CBOR returned by
|
||||
/// `wallet.send.unsigned`.
|
||||
/// `wallet_send_unsigned`.
|
||||
pub signed_cbor_hex: String,
|
||||
}
|
||||
|
||||
|
|
@ -168,7 +171,7 @@ pub struct MintUnsignedArgs {
|
|||
/// PolicySpec as a JSON object with `type`: `single_sig` |
|
||||
/// `single_sig_timelock` | `nofk`. If omitted, defaults to a
|
||||
/// single-sig policy bound to this wallet's payment key (same as
|
||||
/// `wallet.mint`).
|
||||
/// `wallet_mint`).
|
||||
#[serde(default)]
|
||||
pub policy: Option<serde_json::Value>,
|
||||
/// Optional CIP-25 v2 metadata.
|
||||
|
|
@ -243,7 +246,7 @@ pub struct TxSummaryArgs {
|
|||
/// Hex-encoded Conway-era tx CBOR — unsigned, partially-signed,
|
||||
/// or fully signed. Decoded read-only and turned into a
|
||||
/// human-reviewable JSON summary. **Always run this before
|
||||
/// `wallet.sign_partial` or `wallet.submit_signed_tx` on a
|
||||
/// `wallet_sign_partial` or `wallet_submit_signed_tx` on a
|
||||
/// CBOR you didn't build yourself.**
|
||||
pub cbor_hex: String,
|
||||
}
|
||||
|
|
@ -332,7 +335,7 @@ pub struct MintArgs {
|
|||
#[tool(tool_box)]
|
||||
impl WalletService {
|
||||
#[tool(
|
||||
name = "wallet.address",
|
||||
name = "wallet_address",
|
||||
description = "Return the wallet's primary base address (CIP-1852, account 0, index 0) as a bech32 string"
|
||||
)]
|
||||
async fn wallet_address(&self) -> String {
|
||||
|
|
@ -340,7 +343,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.stake.address",
|
||||
name = "wallet_stake_address",
|
||||
description = "Return the wallet's reward (stake) address as bech32 — `stake1...` on mainnet, `stake_test1...` on testnet. This is what gets pointed at a stake pool when delegating."
|
||||
)]
|
||||
async fn wallet_stake_address(&self) -> Result<String, String> {
|
||||
|
|
@ -351,7 +354,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.network",
|
||||
name = "wallet_network",
|
||||
description = "Return the configured Cardano network: mainnet, preview, or preprod"
|
||||
)]
|
||||
async fn wallet_network(&self) -> String {
|
||||
|
|
@ -363,7 +366,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.balance",
|
||||
name = "wallet_balance",
|
||||
description = "Query ADA + native-asset balance at the wallet address. Returns JSON {lovelace, assets}."
|
||||
)]
|
||||
async fn wallet_balance(&self) -> Result<String, String> {
|
||||
|
|
@ -377,7 +380,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.utxos",
|
||||
name = "wallet_utxos",
|
||||
description = "List UTXOs at the wallet address as a JSON array of {tx_hash, output_index, lovelace, assets}."
|
||||
)]
|
||||
async fn wallet_utxos(&self) -> Result<String, String> {
|
||||
|
|
@ -391,7 +394,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.send",
|
||||
name = "wallet_send",
|
||||
description = "Build, sign, and submit a payment (ADA + optional native assets) from this wallet. Args: to_address (bech32), lovelace (u64), assets (optional array of {policy_id_hex, asset_name_hex, quantity}), force (bool, optional). Refuses sends > max_send_lovelace unless force=true. Returns the tx hash on success."
|
||||
)]
|
||||
async fn wallet_send(
|
||||
|
|
@ -454,7 +457,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.tx_status",
|
||||
name = "wallet_tx_status",
|
||||
description = "Poll a submitted transaction's confirmation status. Args: tx_hash (hex). Returns JSON {status: confirmed|not_found, block_height?, epoch?}."
|
||||
)]
|
||||
async fn wallet_tx_status(
|
||||
|
|
@ -471,8 +474,8 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.send.unsigned",
|
||||
description = "Build a payment without signing or submitting. Returns JSON {cbor_hex, summary}: the unsigned tx CBOR for a cold-signer + a human-readable summary (predicted tx_hash, send/fee/change amounts). For high-value flows where the daemon must not auto-sign. After review + offline signing, submit the signed bytes via wallet.submit_signed_tx."
|
||||
name = "wallet_send_unsigned",
|
||||
description = "Build a payment without signing or submitting. Returns JSON {cbor_hex, summary}: the unsigned tx CBOR for a cold-signer + a human-readable summary (predicted tx_hash, send/fee/change amounts). For high-value flows where the daemon must not auto-sign. After review + offline signing, submit the signed bytes via wallet_submit_signed_tx."
|
||||
)]
|
||||
async fn wallet_send_unsigned(
|
||||
&self,
|
||||
|
|
@ -523,8 +526,8 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.submit_signed_tx",
|
||||
description = "Submit a pre-signed transaction. Args: signed_cbor_hex (hex-encoded signed tx CBOR from a cold-signer). Returns the on-chain tx hash on success. Use after wallet.send.unsigned + offline signing."
|
||||
name = "wallet_submit_signed_tx",
|
||||
description = "Submit a pre-signed transaction. Args: signed_cbor_hex (hex-encoded signed tx CBOR from a cold-signer). Returns the on-chain tx hash on success. Use after wallet_send_unsigned + offline signing."
|
||||
)]
|
||||
async fn wallet_submit_signed_tx(
|
||||
&self,
|
||||
|
|
@ -539,7 +542,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.policy.create",
|
||||
name = "wallet_policy_create",
|
||||
description = "Generate a single-sig native policy bound to this wallet's payment key. Args: invalid_after_slot (optional u64 — omit for open-ended, supply for time-locked supply). Returns JSON {policy_id_hex, script_cbor_hex, type}."
|
||||
)]
|
||||
async fn wallet_policy_create(
|
||||
|
|
@ -570,7 +573,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.mint",
|
||||
name = "wallet_mint",
|
||||
description = "Mint or burn a native asset under a wallet-generated single-sig policy, optionally with CIP-25 v2 metadata. Args: dest_address, dest_lovelace (≥ 1.5 ADA for asset-bearing UTXO), asset_name_hex, quantity (positive=mint, negative=burn), invalid_after_slot (optional), metadata (optional CIP-25 JSON object: {name, image, description, mediaType, files, ...}). Returns the tx hash on success."
|
||||
)]
|
||||
async fn wallet_mint(
|
||||
|
|
@ -650,7 +653,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.mint.cip68_nft",
|
||||
name = "wallet_mint_cip68_nft",
|
||||
description = "Mint a CIP-68 NFT pair (label 100 ref + label 222 user) under a wallet-generated single-sig policy. Args: user_address, name_body_hex (raw asset-name body, ≤28 bytes), metadata (JSON object: name, image, description, mediaType, files, ...), user_lovelace (defaults 1.5 ADA), ref_address (defaults wallet — mutable NFT), ref_lovelace (defaults 1.5 ADA), invalid_after_slot? Returns the tx hash."
|
||||
)]
|
||||
async fn wallet_mint_cip68_nft(
|
||||
|
|
@ -738,7 +741,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.script.spend",
|
||||
name = "wallet_script_spend",
|
||||
description = "Spend a Plutus-locked UTXO. Args: locked_tx_hash, locked_output_index, locked_lovelace, plutus_version (v1|v2|v3), script_cbor_hex, redeemer_cbor_hex, witness_datum_hex (optional, omit if datum is inline on the locked utxo), payout_address, payout_lovelace, ex_units (optional {mem,steps} — defaults to a generous budget for trivial validators). Wallet picks its own collateral UTXO (≥ 5 ADA). Returns the tx hash."
|
||||
)]
|
||||
async fn wallet_script_spend(
|
||||
|
|
@ -838,7 +841,7 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.stake.delegate",
|
||||
name = "wallet_stake_delegate",
|
||||
description = "Delegate this wallet's stake to a Cardano pool. Args: pool_id (bech32 'pool1...'), register_first (bool, defaults true — prepends a 2 ADA stake-registration cert; set false if the stake key is already registered). Signs with both the payment and stake keys, submits, returns the tx hash."
|
||||
)]
|
||||
async fn wallet_stake_delegate(
|
||||
|
|
@ -892,8 +895,8 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.mint.unsigned",
|
||||
description = "Build a mint TX without signing — for cold-sign or multi-sig flows. Args: dest_address, dest_lovelace, asset_name_hex, quantity, policy (optional, defaults to wallet single-sig; pass {type:'nofk',n:2,signer_pkhs_hex:[..]} for multi-sig treasury), metadata (optional CIP-25), disclosed_signer_pkh_hex (optional, defaults to wallet's pkh). Returns JSON {cbor_hex, summary}. Pass through wallet.sign_partial chain, then wallet.submit_signed_tx."
|
||||
name = "wallet_mint_unsigned",
|
||||
description = "Build a mint TX without signing — for cold-sign or multi-sig flows. Args: dest_address, dest_lovelace, asset_name_hex, quantity, policy (optional, defaults to wallet single-sig; pass {type:'nofk',n:2,signer_pkhs_hex:[..]} for multi-sig treasury), metadata (optional CIP-25), disclosed_signer_pkh_hex (optional, defaults to wallet's pkh). Returns JSON {cbor_hex, summary}. Pass through wallet_sign_partial chain, then wallet_submit_signed_tx."
|
||||
)]
|
||||
async fn wallet_mint_unsigned(
|
||||
&self,
|
||||
|
|
@ -976,8 +979,8 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.tx_summary",
|
||||
description = "Decode a Conway-era tx CBOR (unsigned, partial, or signed) into a human-reviewable JSON summary: tx_hash, inputs count, outputs (address+lovelace+assets+inline_datum flag), fee, certificates, mint, witness count, aux-data presence. **Read-only — does not sign or submit.** Run this before `wallet.sign_partial` on any CBOR you didn't build yourself."
|
||||
name = "wallet_tx_summary",
|
||||
description = "Decode a Conway-era tx CBOR (unsigned, partial, or signed) into a human-reviewable JSON summary: tx_hash, inputs count, outputs (address+lovelace+assets+inline_datum flag), fee, certificates, mint, witness count, aux-data presence. **Read-only — does not sign or submit.** Run this before `wallet_sign_partial` on any CBOR you didn't build yourself."
|
||||
)]
|
||||
async fn wallet_tx_summary(
|
||||
&self,
|
||||
|
|
@ -989,8 +992,8 @@ impl WalletService {
|
|||
}
|
||||
|
||||
#[tool(
|
||||
name = "wallet.sign_partial",
|
||||
description = "Append this wallet's VKeyWitness to a Conway-era tx (unsigned or partially-signed). Args: cbor_hex (hex-encoded tx CBOR). Returns the updated CBOR hex with our signature added. For multi-sig flows (e.g. ADAMaps treasury 2-of-2): each party calls this in turn, then any party submits via wallet.submit_signed_tx."
|
||||
name = "wallet_sign_partial",
|
||||
description = "Append this wallet's VKeyWitness to a Conway-era tx (unsigned or partially-signed). Args: cbor_hex (hex-encoded tx CBOR). Returns the updated CBOR hex with our signature added. For multi-sig flows (e.g. ADAMaps treasury 2-of-2): each party calls this in turn, then any party submits via wallet_submit_signed_tx."
|
||||
)]
|
||||
async fn wallet_sign_partial(
|
||||
&self,
|
||||
|
|
@ -1012,7 +1015,7 @@ impl ServerHandler for WalletService {
|
|||
fn get_info(&self) -> ServerInfo {
|
||||
ServerInfo {
|
||||
instructions: Some(
|
||||
"aldabra — Cardano lite wallet over MCP. Phase 1 (read): wallet.address, wallet.network, wallet.balance, wallet.utxos. Phase 2 (send): wallet.send (with native-asset bundle), wallet.send.unsigned + wallet.submit_signed_tx + wallet.sign_partial (cold-sign + multi-sig), wallet.tx_status. Phase 3 (mint): wallet.policy.create, wallet.mint (with CIP-25 metadata), wallet.mint.cip68_nft (ref + user NFT pair w/ inline datum). Plutus + stake delegation land in Phase 4.".into(),
|
||||
"aldabra — Cardano lite wallet over MCP. Phase 1 (read): wallet_address, wallet_network, wallet_balance, wallet_utxos. Phase 2 (send): wallet_send (with native-asset bundle), wallet_send_unsigned + wallet_submit_signed_tx + wallet_sign_partial (cold-sign + multi-sig), wallet_tx_status. Phase 3 (mint): wallet_policy_create, wallet_mint (with CIP-25 metadata), wallet_mint_cip68_nft (ref + user NFT pair w/ inline datum). Plutus + stake delegation land in Phase 4.".into(),
|
||||
),
|
||||
..Default::default()
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue