Skip to content

Commit d6c9600

Browse files
committed
refactor(meta-tools): remove debug auto-approve; writes always prompt
The DEBUG auto-approve bypass was dev-only (and inert in release after the earlier security gating) and overlapped confusingly with the planned, persisted "require approval" production switch. Remove it entirely so the app surfaces true production behavior — every `mcpmux_*` write goes through the approval dialog. - ApprovalBroker: drop the `auto_approve` field, the `MCPMUX_DEBUG_AUTO_APPROVE` env read, and `set_auto_approve` / `auto_approve_enabled` (plus the step-0 bypass in `request_approval`). Always-allow, rate-limit, and fail-closed paths are unchanged. - Remove the `set/get_meta_tools_auto_approve` Tauri commands + their registration, the frontend API wrappers, and the "Auto-approve writes (debug)" toggle from the meta-tool grants panel. - Drop the obsolete broker unit test and e2e TC-MT-003. Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent 601043a commit d6c9600

7 files changed

Lines changed: 3 additions & 249 deletions

File tree

apps/desktop/src-tauri/src/commands/meta_tool_approval.rs

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -109,44 +109,3 @@ pub async fn revoke_meta_tool_grant(
109109
};
110110
Ok(broker.revoke_always_allow(&client_id, &tool_name))
111111
}
112-
113-
/// DEBUG/dev only: toggle auto-approval of all write meta tools.
114-
///
115-
/// When on, every `mcpmux_*` write tool (manage_feature_set,
116-
/// bind_current_workspace, …) is approved without a dialog. This exists so a
117-
/// developer (or the in-app assistant) can self-create feature sets / bindings
118-
/// and exercise the routing end-to-end without clicking through approvals.
119-
/// Session-only — it is **not** persisted and resets to the
120-
/// `MCPMUX_DEBUG_AUTO_APPROVE` env default on gateway restart.
121-
#[tauri::command]
122-
pub async fn set_meta_tools_auto_approve(
123-
enabled: bool,
124-
gateway_state: State<'_, Arc<RwLock<GatewayAppState>>>,
125-
) -> Result<bool, String> {
126-
let broker = {
127-
let state = gateway_state.read().await;
128-
state.approval_broker.clone()
129-
};
130-
let Some(broker) = broker else {
131-
warn!("[meta-tool] set_meta_tools_auto_approve called but gateway is not running");
132-
return Err("gateway is not running".into());
133-
};
134-
broker.set_auto_approve(enabled);
135-
warn!(enabled, "[meta-tool] auto-approve toggled (DEBUG)");
136-
Ok(enabled)
137-
}
138-
139-
/// Whether write meta tools are currently auto-approved (DEBUG mode state).
140-
#[tauri::command]
141-
pub async fn get_meta_tools_auto_approve(
142-
gateway_state: State<'_, Arc<RwLock<GatewayAppState>>>,
143-
) -> Result<bool, String> {
144-
let broker = {
145-
let state = gateway_state.read().await;
146-
state.approval_broker.clone()
147-
};
148-
let Some(broker) = broker else {
149-
return Ok(false);
150-
};
151-
Ok(broker.auto_approve_enabled())
152-
}

apps/desktop/src-tauri/src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -910,8 +910,6 @@ pub fn run() {
910910
commands::respond_to_meta_tool_approval,
911911
commands::list_meta_tool_grants,
912912
commands::revoke_meta_tool_grant,
913-
commands::set_meta_tools_auto_approve,
914-
commands::get_meta_tools_auto_approve,
915913
// Built-in servers (per-Space enablement + per-tool toggles)
916914
commands::list_builtin_servers,
917915
commands::set_builtin_server_enabled,

apps/desktop/src/features/metaTools/MetaToolGrantsPanel.tsx

Lines changed: 2 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { useCallback, useEffect, useState } from 'react';
2-
import { FlaskConical, KeyRound, Loader2, Trash2 } from 'lucide-react';
3-
import { Button, Card, CardContent, CardHeader, CardTitle, Switch } from '@mcpmux/ui';
2+
import { KeyRound, Loader2, Trash2 } from 'lucide-react';
3+
import { Button, Card, CardContent, CardHeader, CardTitle } from '@mcpmux/ui';
44
import {
5-
getMetaToolsAutoApprove,
65
listMetaToolGrants,
76
revokeMetaToolGrant,
8-
setMetaToolsAutoApprove,
97
type MetaToolGrantEntry,
108
} from '@/lib/api/metaTools';
119

@@ -23,7 +21,6 @@ export function MetaToolGrantsPanel() {
2321
const [grants, setGrants] = useState<MetaToolGrantEntry[] | null>(null);
2422
const [error, setError] = useState<string | null>(null);
2523
const [revoking, setRevoking] = useState<string | null>(null);
26-
const [autoApprove, setAutoApprove] = useState<boolean | null>(null);
2724

2825
const load = useCallback(async () => {
2926
try {
@@ -41,23 +38,6 @@ export function MetaToolGrantsPanel() {
4138
return () => clearInterval(i);
4239
}, [load]);
4340

44-
useEffect(() => {
45-
getMetaToolsAutoApprove()
46-
.then(setAutoApprove)
47-
.catch(() => setAutoApprove(false));
48-
}, []);
49-
50-
const handleToggleAutoApprove = async (next: boolean) => {
51-
const prev = autoApprove;
52-
setAutoApprove(next);
53-
try {
54-
await setMetaToolsAutoApprove(next);
55-
} catch (e) {
56-
setAutoApprove(prev);
57-
setError(e instanceof Error ? e.message : String(e));
58-
}
59-
};
60-
6141
const handleRevoke = async (g: MetaToolGrantEntry) => {
6242
const key = `${g.client_id}:${g.tool_name}`;
6343
setRevoking(key);
@@ -86,33 +66,6 @@ export function MetaToolGrantsPanel() {
8666
<CardContent>
8767
{error && <div className="mb-2 text-sm text-red-600 dark:text-red-400">{error}</div>}
8868

89-
{/* DEBUG: bypass the approval dialog entirely. For developers driving
90-
mcpmux_* writes (create feature sets / bindings) from a client to
91-
self-test routing. Session-only; resets on gateway restart. */}
92-
<div
93-
className="mb-4 flex items-start justify-between gap-3 rounded-lg border border-amber-300/60 bg-amber-50 p-3 dark:border-amber-700/50 dark:bg-amber-900/20"
94-
data-testid="meta-tool-auto-approve"
95-
>
96-
<div className="flex min-w-0 gap-2">
97-
<FlaskConical className="mt-0.5 h-4 w-4 flex-shrink-0 text-amber-600 dark:text-amber-400" />
98-
<div className="min-w-0">
99-
<div className="text-sm font-medium text-amber-900 dark:text-amber-200">
100-
Auto-approve writes (debug)
101-
</div>
102-
<p className="mt-0.5 text-xs text-amber-800/80 dark:text-amber-300/80">
103-
Skip the approval dialog for every <code className="font-mono">mcpmux_*</code>{' '}
104-
write. For self-testing only — resets on gateway restart.
105-
</p>
106-
</div>
107-
</div>
108-
<Switch
109-
checked={autoApprove ?? false}
110-
disabled={autoApprove === null}
111-
onCheckedChange={(v) => void handleToggleAutoApprove(v)}
112-
data-testid="meta-tool-auto-approve-toggle"
113-
/>
114-
</div>
115-
11669
{grants === null ? (
11770
<div className="flex items-center gap-2 text-sm text-[rgb(var(--muted))]">
11871
<Loader2 className="h-4 w-4 animate-spin" /> Loading…

apps/desktop/src/lib/api/metaTools.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,6 @@ export async function revokeMetaToolGrant(clientId: string, toolName: string): P
3333
// `@/lib/api/builtinServers` (listBuiltinServers / setBuiltinServerEnabled /
3434
// setBuiltinToolEnabled). The old global get/set_meta_tools_enabled were removed.
3535

36-
/**
37-
* DEBUG/dev only: toggle auto-approval of every write meta tool.
38-
*
39-
* When on, `mcpmux_manage_feature_set` / `mcpmux_bind_current_workspace` and
40-
* friends are approved without a dialog — so a developer can self-create
41-
* feature sets and bindings and exercise routing end-to-end. Session-only:
42-
* resets on gateway restart (to the `MCPMUX_DEBUG_AUTO_APPROVE` env default).
43-
*/
44-
export async function setMetaToolsAutoApprove(enabled: boolean): Promise<boolean> {
45-
return invoke('set_meta_tools_auto_approve', { enabled });
46-
}
47-
48-
/** Whether write meta tools are currently auto-approved (DEBUG mode state). */
49-
export async function getMetaToolsAutoApprove(): Promise<boolean> {
50-
return invoke('get_meta_tools_auto_approve');
51-
}
52-
5336
/**
5437
* Respond to a pending approval request. Normally called by
5538
* `<MetaToolApprovalDialog>`; exported here for tests and advanced flows.

crates/mcpmux-gateway/src/services/meta_tools/approval.rs

Lines changed: 0 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
//! client_metadata URL for DCR-registered clients like Claude Code). The
2525
//! broker doesn't parse it; equality + hashing is enough.
2626
27-
use std::sync::atomic::{AtomicBool, Ordering};
2827
use std::sync::Arc;
2928
use std::time::{Duration, Instant};
3029

@@ -118,12 +117,6 @@ pub struct ApprovalBroker {
118117
/// Published to the desktop layer; `None` means headless.
119118
publisher: Mutex<Option<ApprovalPublisher>>,
120119
timeout: Duration,
121-
/// DEBUG/dev only: when set, every write meta-tool is auto-approved without
122-
/// a dialog. Lets self-tests drive `mcpmux_manage_feature_set` /
123-
/// `mcpmux_bind_current_workspace` headlessly. Off by default; enabled via
124-
/// `MCPMUX_DEBUG_AUTO_APPROVE=1` at startup or the
125-
/// `set_meta_tools_auto_approve` command at runtime.
126-
auto_approve: AtomicBool,
127120
}
128121

129122
impl Default for ApprovalBroker {
@@ -134,31 +127,12 @@ impl Default for ApprovalBroker {
134127

135128
impl ApprovalBroker {
136129
pub fn new() -> Self {
137-
// Auto-approve is a DEBUG-ONLY bypass of the sole human-in-the-loop
138-
// control for write meta-tools. Compile the env-var read out of
139-
// release builds entirely (`debug_assertions` is off in `--release`)
140-
// so a stray `MCPMUX_DEBUG_AUTO_APPROVE` inherited from the parent
141-
// shell / CI / launcher can never silently disable the approval
142-
// dialog in a shipped binary.
143-
#[cfg(debug_assertions)]
144-
let auto = std::env::var("MCPMUX_DEBUG_AUTO_APPROVE")
145-
.map(|v| matches!(v.as_str(), "1" | "true"))
146-
.unwrap_or(false);
147-
#[cfg(not(debug_assertions))]
148-
let auto = false;
149-
150-
if auto {
151-
warn!(
152-
"[ApprovalBroker] MCPMUX_DEBUG_AUTO_APPROVE set — meta-tool writes auto-approved"
153-
);
154-
}
155130
Self {
156131
pending: DashMap::new(),
157132
always_allow: DashMap::new(),
158133
rate_limit: DashMap::new(),
159134
publisher: Mutex::new(None),
160135
timeout: DEFAULT_TIMEOUT,
161-
auto_approve: AtomicBool::new(auto),
162136
}
163137
}
164138

@@ -167,34 +141,6 @@ impl ApprovalBroker {
167141
self
168142
}
169143

170-
/// DEBUG/dev only: toggle auto-approval of all write meta tools.
171-
///
172-
/// In release builds this is inert — a request to enable auto-approve is
173-
/// ignored and logged, so production binaries cannot turn off the
174-
/// approval dialog at runtime (the Tauri command stays registered for a
175-
/// uniform IPC surface, but has no effect).
176-
pub fn set_auto_approve(&self, on: bool) {
177-
#[cfg(debug_assertions)]
178-
{
179-
self.auto_approve.store(on, Ordering::Relaxed);
180-
warn!(on, "[ApprovalBroker] auto-approve toggled (DEBUG)");
181-
}
182-
#[cfg(not(debug_assertions))]
183-
{
184-
if on {
185-
warn!(
186-
"[ApprovalBroker] set_auto_approve(true) ignored in release build — \
187-
meta-tool approval cannot be disabled in production"
188-
);
189-
}
190-
}
191-
}
192-
193-
/// Whether write meta tools are currently auto-approved.
194-
pub fn auto_approve_enabled(&self) -> bool {
195-
self.auto_approve.load(Ordering::Relaxed)
196-
}
197-
198144
/// Attach the desktop subscriber. Call once at app startup.
199145
pub async fn set_publisher(&self, publisher: ApprovalPublisher) {
200146
*self.publisher.lock().await = Some(publisher);
@@ -266,16 +212,6 @@ impl ApprovalBroker {
266212
tool_name: &str,
267213
payload: ApprovalPayload,
268214
) -> Result<ApprovalDecision, MetaToolError> {
269-
// 0. DEBUG auto-approve — bypass the dialog entirely (dev/self-test).
270-
if self.auto_approve.load(Ordering::Relaxed) {
271-
warn!(
272-
%client_id,
273-
tool = tool_name,
274-
"[ApprovalBroker] DEBUG auto-approve — bypassing dialog",
275-
);
276-
return Ok(ApprovalDecision::AllowOnce);
277-
}
278-
279215
// 1. Always-allow short-circuit.
280216
if self
281217
.always_allow
@@ -396,37 +332,6 @@ mod tests {
396332
assert!(matches!(err, MetaToolError::ApprovalRequiredNoDesktop));
397333
}
398334

399-
#[tokio::test]
400-
async fn auto_approve_bypasses_dialog() {
401-
// DEBUG mode: even with NO publisher attached (which normally yields
402-
// ApprovalRequiredNoDesktop), auto-approve returns AllowOnce so
403-
// self-tests can drive write meta tools headlessly. Toggling off
404-
// restores the safe headless deny.
405-
let broker = ApprovalBroker::new();
406-
broker.set_auto_approve(true);
407-
assert!(broker.auto_approve_enabled());
408-
let d = broker
409-
.request_approval(
410-
&Uuid::new_v4().to_string(),
411-
"mcpmux_bind_current_workspace",
412-
make_payload(),
413-
)
414-
.await
415-
.unwrap();
416-
assert_eq!(d, ApprovalDecision::AllowOnce);
417-
418-
broker.set_auto_approve(false);
419-
let err = broker
420-
.request_approval(
421-
&Uuid::new_v4().to_string(),
422-
"mcpmux_bind_current_workspace",
423-
make_payload(),
424-
)
425-
.await
426-
.unwrap_err();
427-
assert!(matches!(err, MetaToolError::ApprovalRequiredNoDesktop));
428-
}
429-
430335
#[tokio::test]
431336
async fn always_allow_short_circuits() {
432337
let broker = ApprovalBroker::new();

tests/e2e/helpers/tauri-api.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -217,16 +217,6 @@ export async function grantOAuthClientFeatureSet(
217217
});
218218
}
219219

220-
/** DEBUG: toggle auto-approval of every write meta tool (session-only). */
221-
export async function setMetaToolsAutoApprove(enabled: boolean): Promise<boolean> {
222-
return invoke<boolean>('set_meta_tools_auto_approve', { enabled });
223-
}
224-
225-
/** Read the current DEBUG auto-approve state. */
226-
export async function getMetaToolsAutoApprove(): Promise<boolean> {
227-
return invoke<boolean>('get_meta_tools_auto_approve');
228-
}
229-
230220
// ============================================================================
231221
// Server Feature Seeding API (for E2E / screenshots)
232222
// ============================================================================

tests/e2e/specs/meta-tools.wdio.ts

Lines changed: 1 addition & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,7 @@
1313
*/
1414

1515
import { byTestId, TIMEOUT, safeClick } from '../helpers/selectors';
16-
import {
17-
emitEvent,
18-
getDefaultSpace,
19-
invoke,
20-
getMetaToolsAutoApprove,
21-
setMetaToolsAutoApprove,
22-
} from '../helpers/tauri-api';
16+
import { emitEvent, getDefaultSpace, invoke } from '../helpers/tauri-api';
2317

2418
interface BuiltinServerRow {
2519
id: string;
@@ -74,34 +68,6 @@ describe('Built-in Servers - Tool Optimization UI', () => {
7468
await expect(audit).toBeDisplayed();
7569
});
7670

77-
it('TC-MT-003: DEBUG auto-approve toggle round-trips and reflects backend state', async () => {
78-
const nav = await byTestId('nav-builtin-servers');
79-
await safeClick(nav);
80-
await browser.pause(1000);
81-
82-
// The amber debug control lives inside the grants panel.
83-
const toggleWrap = await byTestId('meta-tool-auto-approve');
84-
await expect(toggleWrap).toBeDisplayed();
85-
86-
// Start from a known-off baseline (session-only state).
87-
await setMetaToolsAutoApprove(false);
88-
expect(await getMetaToolsAutoApprove()).toBe(false);
89-
90-
// Flip it on via the UI switch and confirm the backend agrees.
91-
const toggle = await byTestId('meta-tool-auto-approve-toggle');
92-
await safeClick(toggle);
93-
await browser.waitUntil(async () => (await getMetaToolsAutoApprove()) === true, {
94-
timeout: TIMEOUT.medium,
95-
timeoutMsg: 'auto-approve did not turn on after clicking the switch',
96-
});
97-
98-
// Flip back off so later tests see the safe default.
99-
await safeClick(toggle);
100-
await browser.waitUntil(async () => (await getMetaToolsAutoApprove()) === false, {
101-
timeout: TIMEOUT.medium,
102-
timeoutMsg: 'auto-approve did not turn off after clicking the switch',
103-
});
104-
});
10571
});
10672

10773
describe('Meta tools - Approval dialog', () => {

0 commit comments

Comments
 (0)