Skip to content

Commit 0b457e2

Browse files
maxholmanclaude
andcommitted
refactor: rename hint_set → role across MCP, REST, and OpenAPI
Every UAT session flagged hint_set as confusing — "hints" is protocol jargon. The REPL and CLI already use "role" (role entry, role prefer exit, role auto). This aligns MCP and REST to match: - MCP: hint_set → role, hint_set_auto → role_auto - REST: PUT/DELETE /hints → PUT/DELETE /role - OpenAPI: HintSetRequest → RoleSetRequest, operationIds updated - level parameter now defaults to "fixed" (matches REPL: "role entry" means "role fixed entry") Also: removed confusing connect=false/listen=false from info output capabilities — the address fields already show this information. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b91a1c5 commit 0b457e2

7 files changed

Lines changed: 50 additions & 48 deletions

File tree

crates/api/src/handlers.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,13 +125,21 @@ pub struct ListenResponse {
125125
pub fingerprint: String,
126126
}
127127

128-
/// Set hint request body.
128+
/// Role set request body.
129+
///
130+
/// `role` is always required. `level` defaults to `"fixed"` if omitted,
131+
/// matching the REPL behaviour where `role entry` means `role fixed entry`.
129132
#[derive(Debug, Deserialize)]
130-
pub struct HintSetRequestBody {
133+
pub struct RoleSetRequestBody {
134+
#[serde(default = "default_level")]
131135
pub level: String,
132136
pub role: String,
133137
}
134138

139+
fn default_level() -> String {
140+
"fixed".to_string()
141+
}
142+
135143
/// Logs query parameters.
136144
#[derive(Debug, Deserialize)]
137145
pub struct LogsQuery {
@@ -655,9 +663,9 @@ pub async fn shutdown(State(state): State<ApiState>) -> (StatusCode, Json<Succes
655663
}
656664
}
657665

658-
pub async fn hint_set(
666+
pub async fn role_set(
659667
State(state): State<ApiState>,
660-
Json(req): Json<HintSetRequestBody>,
668+
Json(req): Json<RoleSetRequestBody>,
661669
) -> (StatusCode, Json<SuccessResponse>) {
662670
let level = match req.level.as_str() {
663671
"prefer" => HintLevel::Prefer,
@@ -738,7 +746,7 @@ pub async fn hint_set(
738746
}
739747
}
740748

741-
pub async fn hint_set_auto(State(state): State<ApiState>) -> (StatusCode, Json<SuccessResponse>) {
749+
pub async fn role_auto(State(state): State<ApiState>) -> (StatusCode, Json<SuccessResponse>) {
742750
let resp = state
743751
.ipc
744752
.lock()

crates/api/src/lib.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,7 @@ pub fn router(state: State) -> Router {
7777
.route("/listen", post(handlers::listen))
7878
.route("/disconnect", post(handlers::disconnect))
7979
.route("/shutdown", post(handlers::shutdown))
80-
.route(
81-
"/hints",
82-
put(handlers::hint_set).delete(handlers::hint_set_auto),
83-
)
80+
.route("/role", put(handlers::role_set).delete(handlers::role_auto))
8481
.layer(middleware::from_fn(move |req, next| {
8582
let auth = auth.clone();
8683
auth::middleware(auth, req, next)

crates/cli/src/output.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,7 @@ pub fn print_response(resp: &ManagementResponse) -> Result<(), CtlError> {
115115
if !s.listen_addr.is_empty() {
116116
println!("{:<18} {}", "listen addr:", s.listen_addr);
117117
}
118-
println!(
119-
"{:<18} tun={} listen={} connect={}",
120-
"capabilities:", s.tun_capable, s.listening, s.connecting
121-
);
118+
println!("{:<18} {}", "tun:", s.tun_capable);
122119
println!("{:<18} {}", "version:", s.version);
123120
println!("{:<18} {}", "uptime:", uptime);
124121
}

crates/daemon/src/lib.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,7 @@ pub fn start_node(
140140
ModeConfig::Auto(cfg) => match &cfg.hint {
141141
Some(hint) if hint.level == wallhack_wire::data::HintLevel::Fixed as i32 => {
142142
wallhack_wire::data::NodeRole::try_from(hint.target)
143-
.ok()
144-
.map(NodeRole::from)
145-
.unwrap_or(NodeRole::Indeterminate)
143+
.map_or(NodeRole::Indeterminate, NodeRole::from)
146144
}
147145
_ => NodeRole::Indeterminate,
148146
},

crates/mcp/src/convert.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,7 @@ pub fn format_response(resp: &ManagementResponse) -> Result<String, String> {
1818
if !s.listen_addr.is_empty() {
1919
let _ = writeln!(out, "listen addr: {}", s.listen_addr);
2020
}
21-
let _ = writeln!(
22-
out,
23-
"capabilities: tun={} listen={} connect={}",
24-
s.tun_capable, s.listening, s.connecting,
25-
);
21+
let _ = writeln!(out, "tun: {}", s.tun_capable);
2622
let _ = writeln!(out, "version: {}", s.version);
2723
let _ = writeln!(out, "uptime: {}", format_uptime(s.uptime_ms));
2824
Ok(out)

crates/mcp/src/tools.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,16 @@ pub struct LogsParams {
4343
}
4444

4545
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
46-
pub struct HintSetParams {
47-
/// How strongly to apply the hint: "prefer" (soft), "exclude" (avoid), or "fixed" (force)
48-
pub level: String,
46+
pub struct RoleParams {
4947
/// Target role: "entry", "exit", or "relay"
5048
pub role: String,
49+
/// How to apply: "fixed" (default, force this role), "prefer" (soft preference), or "exclude" (avoid this role)
50+
#[serde(default = "default_role_level")]
51+
pub level: String,
52+
}
53+
54+
fn default_role_level() -> String {
55+
"fixed".to_string()
5156
}
5257

5358
/// Wallhack MCP server — exposes daemon management as MCP tools.
@@ -182,19 +187,19 @@ impl WallhackServer {
182187
}
183188

184189
#[tool(
185-
description = "Set a role hint to influence auto-negotiation: \"prefer\" makes the role more likely, \"exclude\" prevents it, \"fixed\" forces it"
190+
description = "Set this node's role: \"fixed\" forces a role (e.g. role fixed entry), \"prefer\" is a soft preference, \"exclude\" avoids a role. Use role_auto to clear."
186191
)]
187-
async fn hint_set(
192+
async fn role(
188193
&self,
189-
Parameters(params): Parameters<HintSetParams>,
194+
Parameters(params): Parameters<RoleParams>,
190195
) -> Result<String, rmcp::ErrorData> {
191196
let level = match params.level.as_str() {
192197
"prefer" => HintLevel::Prefer,
193198
"exclude" => HintLevel::Exclude,
194199
"fixed" => HintLevel::Fixed,
195200
other => {
196201
return Err(rmcp::ErrorData::invalid_params(
197-
format!("invalid hint level '{other}' (expected: prefer, exclude, fixed)"),
202+
format!("invalid level '{other}' (expected: prefer, exclude, fixed)"),
198203
None,
199204
));
200205
}
@@ -218,9 +223,9 @@ impl WallhackServer {
218223
}
219224

220225
#[tool(
221-
description = "Remove all role hints and return to automatic role negotiation based on peer capabilities. Use to undo a previous hint_set."
226+
description = "Clear all role preferences and return to automatic negotiation based on peer capabilities"
222227
)]
223-
async fn hint_set_auto(&self) -> Result<String, rmcp::ErrorData> {
228+
async fn role_auto(&self) -> Result<String, rmcp::ErrorData> {
224229
ipc_call(management_request::Request::HintSetAuto(
225230
HintSetAutoRequest {},
226231
))

website/src/data/openapi.json

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -282,19 +282,20 @@
282282
}
283283
}
284284
},
285-
"HintSetRequest": {
285+
"RoleSetRequest": {
286286
"type": "object",
287-
"required": ["level", "role"],
287+
"required": ["role"],
288288
"properties": {
289-
"level": {
290-
"type": "string",
291-
"enum": ["prefer", "exclude", "fixed"],
292-
"description": "Hint strength: prefer (soft), exclude (medium), fixed (hard)."
293-
},
294289
"role": {
295290
"type": "string",
296291
"enum": ["entry", "exit", "relay"],
297-
"description": "Role to apply the hint to."
292+
"description": "Target role."
293+
},
294+
"level": {
295+
"type": "string",
296+
"enum": ["prefer", "exclude", "fixed"],
297+
"default": "fixed",
298+
"description": "How to apply: fixed (default, force), prefer (soft), exclude (avoid)."
298299
}
299300
}
300301
}
@@ -623,41 +624,41 @@
623624
}
624625
}
625626
},
626-
"/hints": {
627+
"/role": {
627628
"put": {
628-
"summary": "Set role hint",
629-
"description": "Configures a negotiation hint to influence auto-role resolution.",
630-
"operationId": "hintSet",
629+
"summary": "Set role",
630+
"description": "Set this node's role preference: fixed (force), prefer (soft), or exclude (avoid).",
631+
"operationId": "roleSet",
631632
"security": [{ "basicAuth": [] }],
632633
"requestBody": {
633634
"required": true,
634635
"content": {
635636
"application/json": {
636-
"schema": { "$ref": "#/components/schemas/HintSetRequest" }
637+
"schema": { "$ref": "#/components/schemas/RoleSetRequest" }
637638
}
638639
}
639640
},
640641
"responses": {
641642
"200": {
642-
"description": "Hint applied.",
643+
"description": "Role preference applied.",
643644
"content": {
644645
"application/json": {
645646
"schema": { "$ref": "#/components/schemas/SuccessResponse" }
646647
}
647648
}
648649
},
649-
"400": { "description": "Invalid hint level or role." },
650+
"400": { "description": "Invalid level or role." },
650651
"401": { "description": "Unauthorized." }
651652
}
652653
},
653654
"delete": {
654655
"summary": "Auto-negotiate",
655-
"description": "Returns to pure capability-based negotiation by removing all role hints.",
656-
"operationId": "hintSetAuto",
656+
"description": "Clear all role preferences and return to capability-based negotiation.",
657+
"operationId": "roleAuto",
657658
"security": [{ "basicAuth": [] }],
658659
"responses": {
659660
"200": {
660-
"description": "Hints cleared.",
661+
"description": "Role preferences cleared.",
661662
"content": {
662663
"application/json": {
663664
"schema": { "$ref": "#/components/schemas/SuccessResponse" }

0 commit comments

Comments
 (0)