Reference guide for adding new MCP tools that expose existing BackendTask variants to the CLI and MCP clients. Follow this checklist exactly — it enforces the architecture boundaries that keep the GUI, backend, and MCP layers decoupled.
- Tools live in
src/mcp/tools/— never in the CLI binary (src/bin/det_cli/). The CLI discovers tools dynamically; it requires zero code changes when a tool is added. - Tools must not contain business logic. A tool is a thin adapter: validate parameters, resolve context, dispatch a
BackendTask, reshape the result. All domain logic stays inBackendTaskhandlers (e.g.run_identity_task(),run_wallet_task()). - One tool struct = one
BackendTaskdispatch. If a feature needs multiple backend calls, compose them in the backend layer, not in the tool. - Errors flow through
McpToolError— never return raw strings or SDK errors to the client. UseMcpToolError::TaskFailed(e)for backend errors,McpToolError::InvalidParam { message }for input validation. - No GUI dependencies. Tool code must not reference
egui, screens, UI components, orAppAction. The only bridge to the app isAppContext+BackendTask.
Confirm the BackendTask variant and its corresponding BackendTaskSuccessResult variant exist in src/backend_task/mod.rs. If they don't, add them first — that is a separate change with its own review scope.
Add a new file or extend an existing file in src/mcp/tools/ following the domain grouping (wallet.rs, platform.rs, network.rs, etc.).
pub struct MyNewTool;- Parameters: Derive
Deserialize+schemars::JsonSchema. Reuse shared types fromsrc/mcp/tools/mod.rs(WalletIdParams,NetworkParams,EmptyParams) when they fit. Add#[serde(default)]on optional fields. - Output: Derive
Serialize+schemars::JsonSchema. Keep output flat and JSON-friendly — no nested enums, noArc, no internal types. Use primitive types the client can consume directly. - Schema quirk: If a field is
serde_json::Value, apply#[schemars(transform)]to emit"type": "object"instead of baretrue(seesrc/mcp/tools/meta.rs).
impl ToolBase for MyNewTool {
type Parameter = MyParams;
type Output = MyOutput;
type Error = McpToolError;
fn name() -> Cow<'static, str> { "domain_object_action".into() }
fn description() -> Option<Cow<'static, str>> { Some("...".into()) }
fn annotations() -> Option<ToolAnnotations> {
Some(ToolAnnotations::default()
.read_only(true) // false if it mutates state
.destructive(false) // true if it spends funds, deletes data
.idempotent(true) // false if repeated calls have side effects
.open_world(true)) // true if it talks to the network
}
}Naming: {domain}_{object}_{action} — e.g. core_address_create, platform_withdrawals_get. The CLI converts underscores to hyphens automatically.
Annotations: Set these accurately — MCP clients use them to decide confirmation prompts and caching.
Follow the standard invocation sequence:
impl AsyncTool<DashMcpService> for MyNewTool {
async fn invoke(
service: &DashMcpService,
param: MyParams,
) -> Result<MyOutput, McpToolError> {
// 1. Obtain context
let ctx = service.ctx().await
.map_err(|e| McpToolError::Internal(e.to_string()))?;
// 2. Verify network (skip only for network-info or meta tools)
resolve::verify_network(&ctx, param.network.as_deref())?;
// 3. Resolve wallet if needed
let seed_hash = resolve::wallet(&ctx, ¶m.wallet_id)?;
// 4. Wait for SPV sync (see SPV gate rule below)
resolve::ensure_spv_synced(&ctx).await?;
// 5. Build and dispatch the backend task
let task = BackendTask::DomainTask(DomainTask::MyVariant { ... });
let result = dispatch_task(&ctx, task).await
.map_err(McpToolError::TaskFailed)?;
// 6. Match the expected result variant, reshape to output
match result {
BackendTaskSuccessResult::MyVariant { .. } => Ok(MyOutput { .. }),
other => Err(McpToolError::Internal(
format!("Unexpected task result: {other:?}")
)),
}
}
}Steps 2–4 are conditional:
- Skip
verify_networkonly fornetwork_infoandtool_describe. - For destructive tools (
read_only: false), thenetworkparameter must be required (not optional with#[serde(default)]). Useresolve::require_network()instead ofresolve::verify_network()to prevent accidental cross-network operations that could spend funds on the wrong network. - Skip wallet resolution if the tool doesn't operate on a wallet.
- SPV gate rule: Call
ensure_spv_syncedfor all wallet-facing tools — both core-chain and platform/DAPI. The SDK verifies DAPI proofs against quorum and masternode list data from the synced SPV chain, so even platform-only queries fail without it. Skip only for metadata tools that make no network calls (core_wallets_list,network_info,tool_describe).
In src/mcp/server.rs, add one line:
.with_async_tool::<tools::domain::MyNewTool>()This is the only registration step. The CLI, list_tools, and tool_describe pick it up automatically.
Add the tool to the tool reference table with its name, parameters, and description.
| Don't | Why |
|---|---|
Put tool logic in src/bin/det_cli/ |
CLI discovers tools dynamically — it must stay tool-agnostic |
Call AppContext methods directly instead of dispatching a BackendTask |
Breaks the task system contract; backend errors won't be handled uniformly |
Return impl Debug or internal types in output |
MCP clients consume JSON — output must be Serialize + JsonSchema |
Skip verify_network on a stateful tool |
Risk of operating on the wrong network |
Add a String field to McpToolError for a new category |
Add a dedicated enum variant instead |
| Store business logic in the tool | Tools are adapters — logic belongs in backend task handlers |
| File | Role |
|---|---|
src/mcp/tools/*.rs |
Tool definitions (ToolBase + AsyncTool) |
src/mcp/tools/mod.rs |
Shared parameter types |
src/mcp/server.rs |
tool_router() registration, DashMcpService |
src/mcp/dispatch.rs |
dispatch_task() — bridges MCP to backend task system |
src/mcp/resolve.rs |
verify_network(), wallet(), ensure_spv_synced() |
src/mcp/error.rs |
McpToolError enum |
src/backend_task/mod.rs |
BackendTask and BackendTaskSuccessResult enums |
docs/MCP.md |
End-user tool reference and server configuration |
docs/CLI.md |
CLI usage and examples |
- CLAUDE.md — project conventions, build commands, architecture overview (MCP section)
- CONTRIBUTING.md — development setup, feature flags, code quality, PR workflow
- docs/MCP.md — server modes, configuration, client setup
- docs/CLI.md — CLI binary usage and shell completion