mcp: declare tools capability in ServerInfo

rmcp 0.1.5's #[tool(tool_box)] macro doesn't backfill
ServerInfo::capabilities. Without an explicit ToolsCapability,
clients read "capabilities":{} from initialize and skip tools/list
entirely — the server looks connected (instructions field lands)
but the tool surface is empty. Claude Code's MCP log:
  "hasTools":false,"hasPrompts":false,"hasResources":false

Fix: capabilities = ServerCapabilities::builder().enable_tools().build()
in get_info(). Adds a regression test on the wire shape.
This commit is contained in:
Kayos 2026-05-05 09:41:19 -07:00
parent ffdafc2028
commit 66829c9aea

View file

@ -36,7 +36,10 @@ use aldabra_core::{
InputUtxo, Network, PaymentKey, PlutusExUnits, PlutusInput, PlutusVersion, PolicySpec,
ProtocolParams, StakeKey, DEFAULT_EX_UNITS,
};
use rmcp::{model::ServerInfo, schemars, tool, ServerHandler};
use rmcp::{
model::{ServerCapabilities, ServerInfo},
schemars, tool, ServerHandler,
};
use serde::Deserialize;
/// MCP-facing asset spec — separate from `aldabra_core::AssetSpec`
@ -1268,7 +1271,13 @@ impl WalletService {
#[tool(tool_box)]
impl ServerHandler for WalletService {
fn get_info(&self) -> ServerInfo {
// Declare `tools` capability explicitly. rmcp 0.1.5's `#[tool(tool_box)]` macro
// does NOT backfill ServerInfo::capabilities; without this line Claude Code (and
// any spec-compliant MCP client) reads `"capabilities": {}` from the initialize
// response and skips `tools/list` entirely. The instructions field still lands,
// so the failure mode is silent — server appears connected, no tool surface.
ServerInfo {
capabilities: ServerCapabilities::builder().enable_tools().build(),
instructions: Some(
"aldabra — Cardano lite wallet over MCP. wallet_*: read (address/balance/utxos/network/stake_address), send (wallet_send with optional inline datum for script locks, wallet_send_unsigned + wallet_sign_partial + wallet_submit_signed_tx for cold/multi-sig, wallet_tx_status), mint (wallet_policy_create, wallet_mint with CIP-25 metadata, wallet_mint_cip68_nft for ref+user NFT pairs, wallet_mint_unsigned), Plutus (wallet_script_spend), stake (wallet_stake_delegate). chain_*: read-only Koios passthroughs (chain_tx_info, chain_address_info, chain_pool_list, chain_pool_info, chain_epoch_params, chain_asset_info, chain_account_info, chain_tip) — for inspecting the chain at addresses/txs/pools beyond this wallet.".into(),
),
@ -1276,3 +1285,25 @@ impl ServerHandler for WalletService {
}
}
}
#[cfg(test)]
mod server_info_tests {
use super::*;
#[test]
fn capabilities_builder_declares_tools() {
// Regression for the silent "hasTools:false" bug. Claude Code reads
// `serverCapabilities.tools` from initialize and skips `tools/list`
// entirely if it's missing — leaving the server connected but with
// zero callable tools. If anyone refactors `get_info()` and drops
// the `enable_tools()` call, this test catches it.
let cap = ServerCapabilities::builder().enable_tools().build();
assert!(cap.tools.is_some(), "tools capability must be declared");
let wire = serde_json::to_value(&cap).expect("ServerCapabilities serializes");
assert!(
wire.get("tools").is_some(),
"wire-format capabilities must include 'tools' key, got: {wire}"
);
}
}