@tduncan / Cortana / commits / 99bb122

feat: default MCP off and unify MCP settings page

Disable MCP by default on a fresh install so the feature stays opt-in until the user explicitly enables it. Existing installs keep their saved preference so the upgrade does not silently flip the toggle. Gate every new-chat code path on the global mcpEnabled flag so that flipping it off always wins over the newChatMcpEnabled default and the per-chat toggle cannot leak enabled state past a disabled global toggle. Rewrite the MCP tools screen to share the visual conventions used by SettingsViews (section cards, panel rows, toggle widget, layout breakpoint, system-inset padding, status/error panels) so the page reads as a first-class settings surface instead of an ad-hoc screen. Co-Authored-By: Opus 4.8 <noreply@puku.sh>

abdulmominsakib committed Jul 27, 2026 at 14:52 UTC 99bb12207395b195e9ef42169bfd72146c7c0c38
4 files changed +378 -199
lib/features/chat/providers/chat_mcp_providers.dart
+5 -2
@@ -11,7 +11,8 @@ class ChatMcpConfigNotifier extends Notifier<ChatMcpConfig> {
11 ChatMcpConfig build() {
12 // We no longer watch settingsProvider here to avoid resetting per-chat state
13 // when global settings change. Initial state is set via ChatNotifier.
14 - return const ChatMcpConfig(enabled: true);
14 + // Default to disabled so a fresh app launch does not silently enable MCP.
15 + return const ChatMcpConfig(enabled: false);
16 }
17
18 void setConfig(ChatMcpConfig config) {
@@ -84,7 +85,9 @@ class ChatMcpConfigNotifier extends Notifier<ChatMcpConfig> {
85
86 void clearAll() {
87 final settings = ref.read(settingsProvider);
87 - state = ChatMcpConfig(enabled: settings.newChatMcpEnabled);
88 + state = ChatMcpConfig(
89 + enabled: settings.mcpEnabled && settings.newChatMcpEnabled,
90 + );
91 ref.read(mcpServerManagerProvider).clear();
92 }
93 }
lib/features/chat/providers/chat_notifier.dart
+11 -5
@@ -2,6 +2,7 @@ import 'dart:async';
2 import 'dart:io';
3 import 'dart:math';
4
5 +import 'package:flutter/services.dart';
6 import 'package:flutter_riverpod/flutter_riverpod.dart';
7 import 'package:localmind/core/logger/app_logger.dart';
8 import 'package:localmind/core/models/enums.dart';
@@ -371,7 +372,7 @@ class ChatNotifier extends Notifier<ChatState> {
372
373 ref
374 .read(chatMcpConfigProvider.notifier)
374 - .setEnabled(conversation.mcpEnabled ?? true);
375 + .setEnabled(conversation.mcpEnabled ?? false);
376 } catch (e, stackTrace) {
377 Log.fatal(error: e, stackTrace: stackTrace);
378 state = state.copyWith(
@@ -491,7 +492,7 @@ class ChatNotifier extends Notifier<ChatState> {
492 final settings = ref.read(settingsProvider);
493 ref
494 .read(chatMcpConfigProvider.notifier)
494 - .setEnabled(settings.newChatMcpEnabled);
495 + .setEnabled(settings.mcpEnabled && settings.newChatMcpEnabled);
496 }
497
498 void _invalidateStreamCallbacks() {
@@ -698,7 +699,7 @@ class ChatNotifier extends Notifier<ChatState> {
699 preselected.map((p) => p.id).toList(),
700 ),
701 systemPrompt: newConversationSystemPrompt,
701 - mcpEnabled: settings.newChatMcpEnabled,
702 + mcpEnabled: settings.mcpEnabled && settings.newChatMcpEnabled,
703 isTemporary: false,
704 folderId: ref.read(pendingNewChatFolderIdProvider.notifier).consume(),
705 );
@@ -715,7 +716,9 @@ class ChatNotifier extends Notifier<ChatState> {
716 _useFreshConversationSystemPrompt = true;
717 _freshConversationSystemPrompt = newConversationSystemPrompt;
718
718 - ref.read(chatMcpConfigProvider.notifier).setEnabled(settings.newChatMcpEnabled);
719 + ref
720 + .read(chatMcpConfigProvider.notifier)
721 + .setEnabled(settings.mcpEnabled && settings.newChatMcpEnabled);
722
723 if (!settings.keepPersonaOnNewChat) {
724 ref.read(selectedPersonasProvider.notifier).clear();
@@ -959,6 +962,9 @@ class ChatNotifier extends Notifier<ChatState> {
962 .copyWith(isProcessing: true);
963 _latestStreamingMessage = streamingAssistantMessage;
964 stateNeedsUpdate = true;
965 + if (ref.read(settingsProvider).hapticFeedbackEnabled) {
966 + HapticFeedback.lightImpact();
967 + }
968 break;
969 case ChatResponseType.timeoutError:
970 case ChatResponseType.error:
@@ -2269,7 +2275,7 @@ class ChatNotifier extends Notifier<ChatState> {
2275 systemPrompt: preselected.isEmpty
2276 ? null
2277 : PersonaPromptUtils.combineSystemPrompts(preselected),
2272 - mcpEnabled: settings.newChatMcpEnabled,
2278 + mcpEnabled: settings.mcpEnabled && settings.newChatMcpEnabled,
2279 isTemporary: false,
2280 );
2281
lib/features/mcp/views/mcp_tools_screen.dart
+358 -188
@@ -1,4 +1,3 @@
1 -import "package:localmind/core/theme/colors.dart";
1 import 'package:flutter/material.dart';
2 import 'package:flutter_riverpod/flutter_riverpod.dart';
3 import 'package:hugeicons/hugeicons.dart';
@@ -8,6 +7,8 @@ import 'package:shadcn_ui/shadcn_ui.dart';
7 import '../../chat/data/mcp_server_manager.dart';
8 import '../../chat/data/tools/tool_definition.dart';
9 import '../../chat/providers/tooling_providers.dart';
10 +import '../../../core/providers/app_providers.dart';
11 +import '../../../core/utils/system_insets.dart';
12
13 class McpToolsScreen extends ConsumerStatefulWidget {
14 const McpToolsScreen({super.key});
@@ -49,11 +50,12 @@ class _McpToolsScreenState extends ConsumerState<McpToolsScreen> {
50 @override
51 Widget build(BuildContext context) {
52 final l10n = AppLocalizations.of(context)!;
52 - final theme = Theme.of(context);
53 - final isDark = theme.brightness == Brightness.dark;
53 + final settings = ref.watch(settingsProvider);
54 final manager = ref.watch(mcpServerManagerProvider);
55 final hasExampleServer = manager.hasExampleServer();
56 + final isDark = Theme.of(context).brightness == Brightness.dark;
57 final topPadding = MediaQuery.of(context).padding.top;
58 + final bottomInset = bottomSystemInset(context);
59
60 return Column(
61 children: [
@@ -95,142 +97,267 @@ class _McpToolsScreenState extends ConsumerState<McpToolsScreen> {
97 ),
98 ),
99 Expanded(
98 - child: RefreshIndicator(
99 - onRefresh: () async => _refreshTools(),
100 - child: ListView(
101 - padding: const EdgeInsets.all(16),
102 - children: [
103 - _ExampleServerPanel(
104 - enabled: hasExampleServer,
105 - onToggle: _toggleExampleServer,
100 + child: LayoutBuilder(
101 + builder: (context, constraints) {
102 + final useTwoColumns = constraints.maxWidth >= 1080;
103 + final contentMaxWidth = useTwoColumns ? 1120.0 : 720.0;
104 + final horizontalPadding = constraints.maxWidth >= 720
105 + ? 20.0
106 + : 12.0;
107 +
108 + final mcpCard = _McpSectionCard(
109 + title: l10n.mcp_tools_title,
110 + children: [
111 + _McpToggleSetting(
112 + label: l10n.enable_mcp,
113 + value: settings.mcpEnabled,
114 + badges: [_FeatureBadge(label: l10n.experimental_label)],
115 + onChanged: (value) => ref
116 + .read(settingsProvider.notifier)
117 + .setMcpEnabled(value),
118 + ),
119 + if (settings.mcpEnabled) ...[
120 + _McpToggleSetting(
121 + label: l10n.new_chat_mcp_default,
122 + value: settings.newChatMcpEnabled,
123 + onChanged: (value) => ref
124 + .read(settingsProvider.notifier)
125 + .setNewChatMcpEnabled(value),
126 + ),
127 + _McpToggleSetting(
128 + label: l10n.enable_example_server,
129 + description: l10n.example_mcp_server_desc,
130 + value: hasExampleServer,
131 + onChanged: (_) => _toggleExampleServer(),
132 + ),
133 + ],
134 + ],
135 + );
136 +
137 + final toolsCard = _McpSectionCard(
138 + title: l10n.available_tools,
139 + accent: const Color(0xFF22C55E),
140 + icon: HugeIcons.strokeRoundedPuzzle,
141 + children: [_buildToolsContent(l10n, settings.mcpEnabled)],
142 + );
143 +
144 + return ListView(
145 + padding: EdgeInsets.fromLTRB(
146 + horizontalPadding,
147 + 12,
148 + horizontalPadding,
149 + 24 + bottomInset,
150 ),
107 - const SizedBox(height: 20),
108 - Text(
109 - l10n.available_tools,
110 - style: theme.textTheme.titleMedium?.copyWith(
111 - fontWeight: FontWeight.w700,
151 + children: [
152 + Center(
153 + child: ConstrainedBox(
154 + constraints: BoxConstraints(maxWidth: contentMaxWidth),
155 + child: Column(
156 + crossAxisAlignment: CrossAxisAlignment.stretch,
157 + children: [
158 + mcpCard,
159 + const SizedBox(height: 16),
160 + toolsCard,
161 + ],
162 + ),
163 + ),
164 ),
165 + ],
166 + );
167 + },
168 + ),
169 + ),
170 + ],
171 + );
172 + }
173 +
174 + Widget _buildToolsContent(AppLocalizations l10n, bool mcpEnabled) {
175 + if (!mcpEnabled) {
176 + return _StatusPanel(
177 + icon: HugeIcons.strokeRoundedAlertCircle,
178 + title: l10n.mcp_disabled_warning,
179 + body: l10n.no_tools_registered_desc,
180 + );
181 + }
182 +
183 + return FutureBuilder<List<ToolDefinition>>(
184 + future: _toolsFuture,
185 + builder: (context, snapshot) {
186 + if (snapshot.connectionState == ConnectionState.waiting) {
187 + return _McpPanel(
188 + child: Padding(
189 + padding: const EdgeInsets.symmetric(vertical: 24),
190 + child: Center(
191 + child: SizedBox(
192 + width: 22,
193 + height: 22,
194 + child: const CircularProgressIndicator(strokeWidth: 2),
195 ),
114 - const SizedBox(height: 12),
115 - FutureBuilder<List<ToolDefinition>>(
116 - future: _toolsFuture,
117 - builder: (context, snapshot) {
118 - if (snapshot.connectionState == ConnectionState.waiting) {
119 - return const Center(
120 - child: Padding(
121 - padding: EdgeInsets.all(32),
122 - child: CircularProgressIndicator(),
196 + ),
197 + ),
198 + );
199 + }
200 +
201 + if (snapshot.hasError) {
202 + return _StatusPanel(
203 + icon: HugeIcons.strokeRoundedInformationCircle,
204 + title: l10n.unable_load_tools,
205 + body: snapshot.error.toString(),
206 + );
207 + }
208 +
209 + final tools = snapshot.data ?? const <ToolDefinition>[];
210 + if (tools.isEmpty) {
211 + return _StatusPanel(
212 + icon: HugeIcons.strokeRoundedPuzzle,
213 + title: l10n.no_tools_registered,
214 + body: l10n.no_tools_registered_desc,
215 + );
216 + }
217 +
218 + return Column(
219 + children: tools
220 + .map((tool) => _ToolRow(tool: tool))
221 + .toList(growable: false),
222 + );
223 + },
224 + );
225 + }
226 +}
227 +
228 +class _McpSectionCard extends StatelessWidget {
229 + const _McpSectionCard({
230 + required this.title,
231 + required this.children,
232 + this.icon = HugeIcons.strokeRoundedMcpServer,
233 + this.accent = const Color(0xFF8B5CF6),
234 + });
235 +
236 + final String title;
237 + final List<List<dynamic>> icon;
238 + final Color accent;
239 + final List<Widget> children;
240 +
241 + @override
242 + Widget build(BuildContext context) {
243 + return Container(
244 + decoration: BoxDecoration(
245 + color: _surfaceColor(context),
246 + borderRadius: BorderRadius.circular(18),
247 + border: Border.all(color: _outlineColor(context, alpha: 0.9)),
248 + ),
249 + child: Padding(
250 + padding: const EdgeInsets.all(16),
251 + child: Column(
252 + crossAxisAlignment: CrossAxisAlignment.start,
253 + children: [
254 + Row(
255 + children: [
256 + Container(
257 + width: 34,
258 + height: 34,
259 + decoration: BoxDecoration(
260 + color: accent.withValues(alpha: 0.10),
261 + borderRadius: BorderRadius.circular(10),
262 + ),
263 + child: Center(
264 + child: HugeIcon(icon: icon, color: accent, size: 16),
265 + ),
266 + ),
267 + const SizedBox(width: 10),
268 + Expanded(
269 + child: Text(
270 + title,
271 + style: Theme.of(context).textTheme.titleMedium?.copyWith(
272 + fontWeight: FontWeight.w700,
273 ),
124 - );
125 - }
126 -
127 - if (snapshot.hasError) {
128 - return _StatusPanel(
129 - icon: HugeIcons.strokeRoundedInformationCircle,
130 - title: l10n.unable_load_tools,
131 - body: snapshot.error.toString(),
132 - );
133 - }
134 -
135 - final tools = snapshot.data ?? const <ToolDefinition>[];
136 - if (tools.isEmpty) {
137 - return _StatusPanel(
138 - icon: HugeIcons.strokeRoundedPuzzle,
139 - title: l10n.no_tools_registered,
140 - body: l10n.no_tools_registered_desc,
141 - );
142 - }
143 -
144 - return Column(
145 - children: tools
146 - .map((tool) => _ToolRow(tool: tool))
147 - .toList(growable: false),
148 - );
149 - },
274 + ),
275 ),
276 ],
277 ),
153 - ),
278 + const SizedBox(height: 12),
279 + ..._withVerticalSpacing(children, gap: 10),
280 + ],
281 ),
155 - ],
282 + ),
283 );
284 }
285 }
286
160 -class _ExampleServerPanel extends StatelessWidget {
161 - const _ExampleServerPanel({required this.enabled, required this.onToggle});
287 +class _McpPanel extends StatelessWidget {
288 + const _McpPanel({required this.child});
289
163 - final bool enabled;
164 - final VoidCallback onToggle;
290 + final Widget child;
291
292 @override
293 Widget build(BuildContext context) {
168 - final l10n = AppLocalizations.of(context)!;
169 - final theme = Theme.of(context);
170 - final isDark = theme.brightness == Brightness.dark;
171 -
294 return Container(
173 - padding: const EdgeInsets.all(16),
295 + padding: const EdgeInsets.all(12),
296 decoration: BoxDecoration(
175 - color: isDark ? const Color(0xFF141414) : const Color(0xFFF8FAFC),
176 - borderRadius: BorderRadius.circular(8),
177 - border: Border.all(
178 - color: isDark ? AppColors.darkSurfaceCard : AppColors.lightSurface,
179 - ),
297 + color: _panelColor(context),
298 + borderRadius: BorderRadius.circular(14),
299 + border: Border.all(color: _outlineColor(context)),
300 ),
181 - child: Column(
182 - crossAxisAlignment: CrossAxisAlignment.start,
301 + child: child,
302 + );
303 + }
304 +}
305 +
306 +class _McpToggleSetting extends StatelessWidget {
307 + const _McpToggleSetting({
308 + required this.label,
309 + required this.value,
310 + required this.onChanged,
311 + this.description,
312 + this.badges = const [],
313 + });
314 +
315 + final String label;
316 + final String? description;
317 + final bool value;
318 + final ValueChanged<bool> onChanged;
319 + final List<Widget> badges;
320 +
321 + @override
322 + Widget build(BuildContext context) {
323 + final theme = Theme.of(context);
324 +
325 + return _McpPanel(
326 + child: Row(
327 + crossAxisAlignment: CrossAxisAlignment.center,
328 children: [
184 - Row(
185 - crossAxisAlignment: CrossAxisAlignment.start,
186 - children: [
187 - Container(
188 - width: 40,
189 - height: 40,
190 - alignment: Alignment.center,
191 - decoration: BoxDecoration(
192 - color: theme.colorScheme.primary.withValues(alpha: 0.12),
193 - borderRadius: BorderRadius.circular(8),
194 - ),
195 - child: HugeIcon(
196 - icon: HugeIcons.strokeRoundedMcpServer,
197 - color: theme.colorScheme.primary,
198 - size: 22,
199 - ),
200 - ),
201 - const SizedBox(width: 12),
202 - Expanded(
203 - child: Column(
204 - crossAxisAlignment: CrossAxisAlignment.start,
329 + Expanded(
330 + child: Column(
331 + crossAxisAlignment: CrossAxisAlignment.start,
332 + children: [
333 + Wrap(
334 + spacing: 8,
335 + runSpacing: 6,
336 + crossAxisAlignment: WrapCrossAlignment.center,
337 children: [
338 Text(
207 - l10n.example_mcp_server_title,
208 - style: theme.textTheme.titleMedium?.copyWith(
339 + label,
340 + style: theme.textTheme.titleSmall?.copyWith(
341 fontWeight: FontWeight.w700,
342 ),
343 ),
212 - const SizedBox(height: 4),
213 - Text(
214 - l10n.example_mcp_server_desc,
215 - style: theme.textTheme.bodyMedium?.copyWith(
216 - color: theme.colorScheme.onSurfaceVariant,
217 - ),
218 - ),
344 + ...badges,
345 ],
346 ),
221 - ),
222 - ],
223 - ),
224 - const SizedBox(height: 16),
225 - ShadButton(
226 - onPressed: onToggle,
227 - leading: HugeIcon(icon: enabled ? HugeIcons.strokeRoundedPower : HugeIcons.strokeRoundedAdd01),
228 - child: Text(
229 - enabled
230 - ? l10n.disable_example_server
231 - : l10n.enable_example_server,
347 + if (description != null) ...[
348 + const SizedBox(height: 4),
349 + Text(
350 + description!,
351 + style: theme.textTheme.bodySmall?.copyWith(
352 + color: theme.colorScheme.onSurfaceVariant,
353 + ),
354 + ),
355 + ],
356 + ],
357 ),
358 ),
359 + const SizedBox(width: 8),
360 + Switch.adaptive(value: value, onChanged: onChanged),
361 ],
362 ),
363 );
@@ -247,70 +374,65 @@ class _ToolRow extends StatelessWidget {
374 final theme = Theme.of(context);
375 final l10n = AppLocalizations.of(context)!;
376 final isMcp = tool.providerType == ToolProviderType.mcp;
250 - final isDark = theme.brightness == Brightness.dark;
377
252 - return Container(
253 - margin: const EdgeInsets.only(bottom: 10),
254 - padding: const EdgeInsets.all(14),
255 - decoration: BoxDecoration(
256 - color: isDark ? const Color(0xFF111111) : Colors.white,
257 - borderRadius: BorderRadius.circular(8),
258 - border: Border.all(
259 - color: isDark ? AppColors.darkSurfaceCard : AppColors.lightSurface,
260 - ),
261 - ),
262 - child: Row(
263 - crossAxisAlignment: CrossAxisAlignment.start,
264 - children: [
265 - HugeIcon(icon:
266 - isMcp ? HugeIcons.strokeRoundedShare01 : HugeIcons.strokeRoundedCalculate,
267 - size: 20,
268 - color: isMcp
269 - ? theme.colorScheme.primary
270 - : theme.colorScheme.outline,
271 - ),
272 - const SizedBox(width: 12),
273 - Expanded(
274 - child: Column(
275 - crossAxisAlignment: CrossAxisAlignment.start,
276 - children: [
277 - Text(
278 - tool.name,
279 - style: theme.textTheme.bodyLarge?.copyWith(
280 - fontWeight: FontWeight.w700,
281 - ),
282 - ),
283 - if (tool.description.isNotEmpty) ...[
284 - const SizedBox(height: 4),
378 + return Padding(
379 + padding: const EdgeInsets.only(bottom: 10),
380 + child: _McpPanel(
381 + child: Row(
382 + crossAxisAlignment: CrossAxisAlignment.start,
383 + children: [
384 + HugeIcon(
385 + icon: isMcp
386 + ? HugeIcons.strokeRoundedShare01
387 + : HugeIcons.strokeRoundedCalculate,
388 + size: 18,
389 + color: isMcp
390 + ? theme.colorScheme.primary
391 + : theme.colorScheme.outline,
392 + ),
393 + const SizedBox(width: 12),
394 + Expanded(
395 + child: Column(
396 + crossAxisAlignment: CrossAxisAlignment.start,
397 + children: [
398 Text(
286 - tool.description,
287 - style: theme.textTheme.bodyMedium?.copyWith(
288 - color: theme.colorScheme.onSurfaceVariant,
399 + tool.name,
400 + style: theme.textTheme.bodyLarge?.copyWith(
401 + fontWeight: FontWeight.w700,
402 ),
403 ),
291 - ],
292 - const SizedBox(height: 8),
293 - Wrap(
294 - spacing: 8,
295 - runSpacing: 8,
296 - children: [
297 - _ToolBadge(
298 - label: isMcp ? 'MCP' : l10n.built_in_label,
299 - color: isMcp
300 - ? theme.colorScheme.primary
301 - : theme.colorScheme.outline,
404 + if (tool.description.isNotEmpty) ...[
405 + const SizedBox(height: 4),
406 + Text(
407 + tool.description,
408 + style: theme.textTheme.bodyMedium?.copyWith(
409 + color: theme.colorScheme.onSurfaceVariant,
410 + ),
411 ),
303 - if (tool.providerRef != null)
412 + ],
413 + const SizedBox(height: 8),
414 + Wrap(
415 + spacing: 8,
416 + runSpacing: 8,
417 + children: [
418 _ToolBadge(
305 - label: tool.providerRef!,
306 - color: theme.colorScheme.secondary,
419 + label: isMcp ? 'MCP' : l10n.built_in_label,
420 + color: isMcp
421 + ? theme.colorScheme.primary
422 + : theme.colorScheme.outline,
423 ),
308 - ],
309 - ),
310 - ],
424 + if (tool.providerRef != null)
425 + _ToolBadge(
426 + label: tool.providerRef!,
427 + color: theme.colorScheme.secondary,
428 + ),
429 + ],
430 + ),
431 + ],
432 + ),
433 ),
312 - ),
313 - ],
434 + ],
435 + ),
436 ),
437 );
438 }
@@ -356,33 +478,81 @@ class _StatusPanel extends StatelessWidget {
478 @override
479 Widget build(BuildContext context) {
480 final theme = Theme.of(context);
481 + return _McpPanel(
482 + child: Padding(
483 + padding: const EdgeInsets.symmetric(vertical: 12),
484 + child: Column(
485 + children: [
486 + HugeIcon(icon: icon, size: 28, color: theme.colorScheme.outline),
487 + const SizedBox(height: 12),
488 + Text(
489 + title,
490 + style: theme.textTheme.titleMedium?.copyWith(
491 + fontWeight: FontWeight.w700,
492 + ),
493 + textAlign: TextAlign.center,
494 + ),
495 + const SizedBox(height: 6),
496 + Text(
497 + body,
498 + style: theme.textTheme.bodyMedium?.copyWith(
499 + color: theme.colorScheme.onSurfaceVariant,
500 + ),
501 + textAlign: TextAlign.center,
502 + ),
503 + ],
504 + ),
505 + ),
506 + );
507 + }
508 +}
509 +
510 +class _FeatureBadge extends StatelessWidget {
511 + const _FeatureBadge({required this.label});
512 +
513 + final String label;
514 +
515 + @override
516 + Widget build(BuildContext context) {
517 return Container(
360 - padding: const EdgeInsets.all(24),
518 + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
519 decoration: BoxDecoration(
362 - borderRadius: BorderRadius.circular(8),
363 - border: Border.all(color: theme.colorScheme.outline),
520 + color: Colors.amber.withValues(alpha: 0.10),
521 + borderRadius: BorderRadius.circular(999),
522 + border: Border.all(color: Colors.amber.withValues(alpha: 0.22)),
523 ),
365 - child: Column(
366 - children: [
367 - HugeIcon(icon: icon, size: 32, color: theme.colorScheme.outline),
368 - const SizedBox(height: 12),
369 - Text(
370 - title,
371 - style: theme.textTheme.titleMedium?.copyWith(
372 - fontWeight: FontWeight.w700,
373 - ),
374 - textAlign: TextAlign.center,
375 - ),
376 - const SizedBox(height: 6),
377 - Text(
378 - body,
379 - style: theme.textTheme.bodyMedium?.copyWith(
380 - color: theme.colorScheme.onSurfaceVariant,
524 + child: Text(
525 + label.toUpperCase(),
526 + style: Theme.of(context).textTheme.labelSmall?.copyWith(
527 + color: const Color(0xFFB45309),
528 + fontWeight: FontWeight.w800,
529 + letterSpacing: 0.45,
530 ),
382 - textAlign: TextAlign.center,
383 - ),
384 - ],
531 ),
532 );
533 }
534 }
535 +
536 +// Visual helpers, kept in sync with settings_screen.dart to ensure the
537 +// MCP screen matches the rest of the app's settings surfaces.
538 +List<Widget> _withVerticalSpacing(List<Widget> children, {double gap = 12}) {
539 + if (children.isEmpty) return const [];
540 + return [
541 + for (var index = 0; index < children.length; index++) ...[
542 + children[index],
543 + if (index != children.length - 1) SizedBox(height: gap),
544 + ],
545 + ];
546 +}
547 +
548 +Color _panelColor(BuildContext context) {
549 + return ShadTheme.of(context).colorScheme.secondary;
550 +}
551 +
552 +Color _surfaceColor(BuildContext context) {
553 + return ShadTheme.of(context).colorScheme.card;
554 +}
555 +
556 +Color _outlineColor(BuildContext context, {double alpha = 0.6}) {
557 + return ShadTheme.of(context).colorScheme.border.withValues(alpha: alpha);
558 +}
\ No newline at end of file
lib/features/settings/data/models/app_settings.dart
+4 -4
@@ -71,8 +71,8 @@ class AppSettings {
71 this.defaultPersonaId,
72 this.hasCompletedOnboarding = false,
73 this.hasAskedForNotifications = false,
74 - this.mcpEnabled = true,
75 - this.newChatMcpEnabled = true,
74 + this.mcpEnabled = false,
75 + this.newChatMcpEnabled = false,
76 this.codeThemeDark = SyntaxThemeName.dark,
77 this.codeThemeLight = SyntaxThemeName.light,
78 this.preferredBackend = PreferredBackend.cpu,
@@ -268,8 +268,8 @@ class AppSettings {
268 defaultPersonaId: map['defaultPersonaId'],
269 hasCompletedOnboarding: map['hasCompletedOnboarding'] ?? false,
270 hasAskedForNotifications: map['hasAskedForNotifications'] ?? false,
271 - mcpEnabled: map['mcpEnabled'] ?? true,
272 - newChatMcpEnabled: map['newChatMcpEnabled'] ?? true,
271 + mcpEnabled: map['mcpEnabled'] ?? false,
272 + newChatMcpEnabled: map['newChatMcpEnabled'] ?? false,
273 codeThemeDark: SyntaxThemeName.values[map['codeThemeDark'] ?? 0],
274 codeThemeLight: SyntaxThemeName.values[map['codeThemeLight'] ?? 1],
275 preferredBackend: _parsePreferredBackend(map['preferredBackend']),