feat: add MCP support (#1471)

This commit is contained in:
Levi authored and GitHub committed 2026-09-05 20:21:39 +08:00
1 parent 39cd9034cd
commit f2145de571
83 files changed
+3119 -363

No files matched your search

@@ -1,6 +1,7 @@
class ApiServerState {
const ApiServerState({
required this.enabled,
required this.mcpEnabled,
required this.running,
required this.network,
required this.address,
@@ -10,6 +11,7 @@ class ApiServerState {
});
final bool enabled;
final bool mcpEnabled;
final bool running;
final String network;
final String address;
@@ -20,6 +22,7 @@ class ApiServerState {
factory ApiServerState.fromJson(Map<String, dynamic> json) {
return ApiServerState(
enabled: json['enabled'] as bool? ?? false,
mcpEnabled: json['mcpEnabled'] as bool? ?? false,
running: json['running'] as bool? ?? false,
network: json['network'] as String? ?? '',
address: json['address'] as String? ?? '',
@@ -50,6 +53,7 @@ class ApiServerOperationResult {
state: rawState == null
? const ApiServerState(
enabled: false,
mcpEnabled: false,
running: false,
network: '',
address: '',
@@ -20,6 +20,7 @@ class StartConfig {
late String network;
late String address;
bool apiEnable = false;
bool mcpEnable = false;
late String storage;
late String storageDir;
late int refreshInterval;
@@ -23,6 +23,7 @@ StartConfig _$StartConfigFromJson(Map<String, dynamic> json) => StartConfig()
..network = json['network'] as String
..address = json['address'] as String
..apiEnable = json['apiEnable'] as bool
..mcpEnable = json['mcpEnable'] as bool? ?? false
..storage = json['storage'] as String
..storageDir = json['storageDir'] as String
..refreshInterval = (json['refreshInterval'] as num).toInt()
@@ -38,6 +39,7 @@ Map<String, dynamic> _$StartConfigToJson(StartConfig instance) =>
'network': instance.network,
'address': instance.address,
'apiEnable': instance.apiEnable,
'mcpEnable': instance.mcpEnable,
'storage': instance.storage,
'storageDir': instance.storageDir,
'refreshInterval': instance.refreshInterval,
@@ -43,6 +43,7 @@ class LibgopeedBootBrowser implements LibgopeedBoot {
const _unsupportedApiServerResult = ApiServerOperationResult(
state: ApiServerState(
enabled: false,
mcpEnabled: false,
running: false,
network: '',
address: '',
@@ -8,7 +8,9 @@ class RestGopeedTransport implements GopeedTransport {
RestGopeedTransport(GopeedTransportConfig config)
: _dio = Dio(
BaseOptions(
baseUrl: '${Uri.base.origin}/',
// Keep release requests relative so deployments under a reverse
// proxy path continue to work, matching the legacy Web client.
baseUrl: webTransportBaseUrl(config.address, Uri.base),
contentType: Headers.jsonContentType,
sendTimeout: const Duration(seconds: 5),
connectTimeout: const Duration(seconds: 5),
@@ -1,4 +1,5 @@
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'entry/gopeed_transport_stub.dart'
if (dart.library.html) 'entry/gopeed_transport_web.dart'
@@ -18,6 +19,44 @@ class GopeedTransportConfig {
final void Function()? onUnauthorized;
}
const defaultWebApiPort = 9999;
/// Resolves the API base URL used by Flutter Web.
///
/// This preserves the legacy behavior: debug builds connect to the local
/// backend on port 9999, while release builds use the page's server origin.
String webApiBaseUrl(Uri pageUri, {bool? debugMode}) {
final useDebugPort = debugMode ?? kDebugMode;
if (useDebugPort) return 'http://127.0.0.1:$defaultWebApiPort/';
return '${pageUri.origin}/';
}
/// Returns the Dio base URL, preserving relative requests in release builds.
String webTransportBaseUrl(String address, Uri pageUri, {bool? debugMode}) {
final useDebugPort = debugMode ?? kDebugMode;
return useDebugPort ? normalizeWebApiBaseUrl(address, pageUri) : '';
}
/// Normalizes either an absolute API URL or a host:port address.
String normalizeWebApiBaseUrl(String address, Uri pageUri) {
var value = address.trim();
if (value.isEmpty) return '${pageUri.origin}/';
final parsed = Uri.tryParse(value);
if (parsed != null && parsed.hasScheme && parsed.host.isNotEmpty) {
value = Uri(
scheme: parsed.scheme,
userInfo: parsed.userInfo,
host: parsed.host,
port: parsed.hasPort ? parsed.port : null,
path: parsed.path,
).toString();
} else {
value = '${pageUri.scheme}://${value.replaceAll(RegExp(r'/+$'), '')}';
}
return value.endsWith('/') ? value : '$value/';
}
abstract interface class GopeedTransport {
Future<dynamic> request(String path, {String method = 'GET', dynamic data, Map<String, dynamic>? queryParameters});
@@ -10,7 +10,14 @@ abstract final class AppWindowChrome {
static bool get usesCustomChrome =>
!kIsWeb && (defaultTargetPlatform == TargetPlatform.windows || defaultTargetPlatform == TargetPlatform.linux);
static bool get reservesHeaderInset => isDesktopWindow;
static bool get reservesHeaderInset => reservesHeaderInsetFor(web: kIsWeb, platform: defaultTargetPlatform);
static bool reservesHeaderInsetFor({required bool web, required TargetPlatform platform}) {
return web ||
platform == TargetPlatform.windows ||
platform == TargetPlatform.macOS ||
platform == TargetPlatform.linux;
}
static bool get clipsRoundedCorners => !kIsWeb && defaultTargetPlatform == TargetPlatform.windows;
}