mirror of
https://github.com/SECTL/SecScore.git
synced 2026-07-19 19:04:23 +08:00
设置页新增 MCP 服务启动停止与状态控制
This commit is contained in:
@@ -84,6 +84,20 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
const [urlRegisterLoading, setUrlRegisterLoading] = useState(false)
|
const [urlRegisterLoading, setUrlRegisterLoading] = useState(false)
|
||||||
const [appQuitLoading, setAppQuitLoading] = useState(false)
|
const [appQuitLoading, setAppQuitLoading] = useState(false)
|
||||||
const [appRestartLoading, setAppRestartLoading] = useState(false)
|
const [appRestartLoading, setAppRestartLoading] = useState(false)
|
||||||
|
const [mcpLoading, setMcpLoading] = useState(false)
|
||||||
|
const [mcpConfig, setMcpConfig] = useState<{ host: string; port: number }>({
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 3901,
|
||||||
|
})
|
||||||
|
const [mcpStatus, setMcpStatus] = useState<{
|
||||||
|
is_running: boolean
|
||||||
|
config: { host: string; port: number }
|
||||||
|
url?: string | null
|
||||||
|
}>({
|
||||||
|
is_running: false,
|
||||||
|
config: { host: "127.0.0.1", port: 3901 },
|
||||||
|
url: null,
|
||||||
|
})
|
||||||
const canAdmin = permission === "admin"
|
const canAdmin = permission === "admin"
|
||||||
const [messageApi, contextHolder] = message.useMessage()
|
const [messageApi, contextHolder] = message.useMessage()
|
||||||
|
|
||||||
@@ -115,6 +129,21 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category } }))
|
window.dispatchEvent(new CustomEvent("ss:data-updated", { detail: { category } }))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const loadMcpStatus = async () => {
|
||||||
|
if (!(window as any).api?.mcpServerStatus) return
|
||||||
|
try {
|
||||||
|
const res = await (window as any).api.mcpServerStatus()
|
||||||
|
if (res.success && res.data) {
|
||||||
|
setMcpStatus(res.data)
|
||||||
|
if (res.data.config?.host && res.data.config?.port) {
|
||||||
|
setMcpConfig({ host: res.data.config.host, port: res.data.config.port })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore status polling errors in settings page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const loadAll = async () => {
|
const loadAll = async () => {
|
||||||
if (!(window as any).api) return
|
if (!(window as any).api) return
|
||||||
const res = await (window as any).api.getAllSettings()
|
const res = await (window as any).api.getAllSettings()
|
||||||
@@ -125,6 +154,7 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
}
|
}
|
||||||
const authRes = await (window as any).api.authGetStatus()
|
const authRes = await (window as any).api.authGetStatus()
|
||||||
if (authRes.success && authRes.data) setSecurityStatus(authRes.data)
|
if (authRes.success && authRes.data) setSecurityStatus(authRes.data)
|
||||||
|
await loadMcpStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -456,6 +486,61 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startMcpServer = async () => {
|
||||||
|
if (!(window as any).api?.mcpServerStart) return
|
||||||
|
const host = mcpConfig.host.trim()
|
||||||
|
const port = Number(mcpConfig.port)
|
||||||
|
if (!host) {
|
||||||
|
messageApi.warning(t("settings.mcp.hostRequired"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!Number.isInteger(port) || port <= 0 || port > 65535) {
|
||||||
|
messageApi.warning(t("settings.mcp.portInvalid"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setMcpLoading(true)
|
||||||
|
try {
|
||||||
|
const res = await withTimeout(
|
||||||
|
(window as any).api.mcpServerStart({ host, port }),
|
||||||
|
10_000,
|
||||||
|
t("settings.mcp.startTimeout")
|
||||||
|
)
|
||||||
|
if (res.success) {
|
||||||
|
messageApi.success(t("settings.mcp.startSuccess"))
|
||||||
|
} else {
|
||||||
|
messageApi.error(res.message || t("settings.mcp.startFailed"))
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
messageApi.error(e?.message || t("settings.mcp.startFailed"))
|
||||||
|
} finally {
|
||||||
|
await loadMcpStatus()
|
||||||
|
setMcpLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopMcpServer = async () => {
|
||||||
|
if (!(window as any).api?.mcpServerStop) return
|
||||||
|
setMcpLoading(true)
|
||||||
|
try {
|
||||||
|
const res = await withTimeout(
|
||||||
|
(window as any).api.mcpServerStop(),
|
||||||
|
10_000,
|
||||||
|
t("settings.mcp.stopTimeout")
|
||||||
|
)
|
||||||
|
if (res.success) {
|
||||||
|
messageApi.success(t("settings.mcp.stopSuccess"))
|
||||||
|
} else {
|
||||||
|
messageApi.error(res.message || t("settings.mcp.stopFailed"))
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
messageApi.error(e?.message || t("settings.mcp.stopFailed"))
|
||||||
|
} finally {
|
||||||
|
await loadMcpStatus()
|
||||||
|
setMcpLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const currentYear = new Date().getFullYear()
|
const currentYear = new Date().getFullYear()
|
||||||
|
|
||||||
const confirmQuitApp = () => {
|
const confirmQuitApp = () => {
|
||||||
@@ -1057,6 +1142,76 @@ export const Settings: React.FC<{ permission: permissionLevel }> = ({ permission
|
|||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<div style={{ fontSize: "16px", fontWeight: 600, marginBottom: "8px" }}>
|
||||||
|
{t("settings.mcp.title")}
|
||||||
|
</div>
|
||||||
|
<div style={{ color: "var(--ss-text-secondary)", marginBottom: "12px", fontSize: "12px" }}>
|
||||||
|
{t("settings.mcp.description")}
|
||||||
|
</div>
|
||||||
|
<Space style={{ marginBottom: "12px" }}>
|
||||||
|
<Tag color={mcpStatus.is_running ? "success" : "default"}>
|
||||||
|
{mcpStatus.is_running ? t("settings.mcp.running") : t("settings.mcp.stopped")}
|
||||||
|
</Tag>
|
||||||
|
{mcpStatus.url ? (
|
||||||
|
<Tag color="blue">{mcpStatus.url}</Tag>
|
||||||
|
) : (
|
||||||
|
<Tag>{t("settings.mcp.noUrl")}</Tag>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
<Form layout="horizontal" labelCol={{ span: 4 }} wrapperCol={{ span: 20 }}>
|
||||||
|
<Form.Item label={t("settings.mcp.host")}>
|
||||||
|
<Input
|
||||||
|
value={mcpConfig.host}
|
||||||
|
onChange={(e) => setMcpConfig((prev) => ({ ...prev, host: e.target.value }))}
|
||||||
|
disabled={!canAdmin || mcpStatus.is_running}
|
||||||
|
style={{ width: "280px" }}
|
||||||
|
placeholder="127.0.0.1"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("settings.mcp.port")}>
|
||||||
|
<Input
|
||||||
|
value={String(mcpConfig.port)}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = Number(e.target.value.replace(/[^\d]/g, ""))
|
||||||
|
if (Number.isFinite(next) && next > 0) {
|
||||||
|
setMcpConfig((prev) => ({ ...prev, port: next }))
|
||||||
|
} else if (!e.target.value) {
|
||||||
|
setMcpConfig((prev) => ({ ...prev, port: 0 }))
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!canAdmin || mcpStatus.is_running}
|
||||||
|
style={{ width: "180px" }}
|
||||||
|
placeholder="3901"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("common.operation")}>
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={startMcpServer}
|
||||||
|
loading={mcpLoading}
|
||||||
|
disabled={!canAdmin || mcpStatus.is_running}
|
||||||
|
>
|
||||||
|
{t("settings.mcp.start")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
onClick={stopMcpServer}
|
||||||
|
loading={mcpLoading}
|
||||||
|
disabled={!canAdmin || !mcpStatus.is_running}
|
||||||
|
>
|
||||||
|
{t("settings.mcp.stop")}
|
||||||
|
</Button>
|
||||||
|
<Button onClick={loadMcpStatus} disabled={mcpLoading}>
|
||||||
|
{t("settings.mcp.refresh")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
<div style={{ color: "var(--ss-text-secondary)", marginTop: "-8px", fontSize: "12px" }}>
|
||||||
|
{t("settings.mcp.hint")}
|
||||||
|
</div>
|
||||||
|
<Divider />
|
||||||
<div style={{ fontSize: "16px", fontWeight: 600, marginBottom: "8px" }}>
|
<div style={{ fontSize: "16px", fontWeight: 600, marginBottom: "8px" }}>
|
||||||
{t("settings.app.title")}
|
{t("settings.app.title")}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -276,6 +276,27 @@
|
|||||||
"registerFailed": "Registration failed",
|
"registerFailed": "Registration failed",
|
||||||
"installerRequired": "Requires installed SecScore, may not work in development mode."
|
"installerRequired": "Requires installed SecScore, may not work in development mode."
|
||||||
},
|
},
|
||||||
|
"mcp": {
|
||||||
|
"title": "MCP Service",
|
||||||
|
"description": "Manage the built-in MCP HTTP service for external MCP clients.",
|
||||||
|
"running": "Running",
|
||||||
|
"stopped": "Stopped",
|
||||||
|
"noUrl": "No URL",
|
||||||
|
"host": "Host",
|
||||||
|
"port": "Port",
|
||||||
|
"start": "Start",
|
||||||
|
"stop": "Stop",
|
||||||
|
"refresh": "Refresh Status",
|
||||||
|
"hint": "Recommended to bind only to 127.0.0.1; after start, endpoint is http://host:port/mcp",
|
||||||
|
"hostRequired": "Please enter MCP host",
|
||||||
|
"portInvalid": "Please enter a valid port (1-65535)",
|
||||||
|
"startSuccess": "MCP server started",
|
||||||
|
"startFailed": "Failed to start MCP server",
|
||||||
|
"stopSuccess": "MCP server stopped",
|
||||||
|
"stopFailed": "Failed to stop MCP server",
|
||||||
|
"startTimeout": "MCP server start timed out, please try again",
|
||||||
|
"stopTimeout": "MCP server stop timed out, please try again"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"title": "App Controls",
|
"title": "App Controls",
|
||||||
"description": "Quickly restart or quit the app.",
|
"description": "Quickly restart or quit the app.",
|
||||||
|
|||||||
@@ -276,6 +276,27 @@
|
|||||||
"registerFailed": "注册失败",
|
"registerFailed": "注册失败",
|
||||||
"installerRequired": "需要安装版 SecScore,开发模式下可能无效。"
|
"installerRequired": "需要安装版 SecScore,开发模式下可能无效。"
|
||||||
},
|
},
|
||||||
|
"mcp": {
|
||||||
|
"title": "MCP 服务",
|
||||||
|
"description": "管理内置 MCP HTTP 服务,供外部 MCP 客户端调用。",
|
||||||
|
"running": "运行中",
|
||||||
|
"stopped": "已停止",
|
||||||
|
"noUrl": "暂无地址",
|
||||||
|
"host": "主机",
|
||||||
|
"port": "端口",
|
||||||
|
"start": "启动",
|
||||||
|
"stop": "停止",
|
||||||
|
"refresh": "刷新状态",
|
||||||
|
"hint": "建议仅绑定到 127.0.0.1;启动后 MCP 入口为 http://host:port/mcp",
|
||||||
|
"hostRequired": "请输入 MCP 主机地址",
|
||||||
|
"portInvalid": "请输入有效端口(1-65535)",
|
||||||
|
"startSuccess": "MCP 服务启动成功",
|
||||||
|
"startFailed": "MCP 服务启动失败",
|
||||||
|
"stopSuccess": "MCP 服务已停止",
|
||||||
|
"stopFailed": "MCP 服务停止失败",
|
||||||
|
"startTimeout": "启动 MCP 服务超时,请稍后重试",
|
||||||
|
"stopTimeout": "停止 MCP 服务超时,请稍后重试"
|
||||||
|
},
|
||||||
"app": {
|
"app": {
|
||||||
"title": "应用控制",
|
"title": "应用控制",
|
||||||
"description": "快速重启或退出应用。",
|
"description": "快速重启或退出应用。",
|
||||||
|
|||||||
@@ -291,6 +291,22 @@ const api = {
|
|||||||
data: { isRunning: boolean; config?: any; url?: string }
|
data: { isRunning: boolean; config?: any; url?: string }
|
||||||
}> => invoke("http_server_status"),
|
}> => invoke("http_server_status"),
|
||||||
|
|
||||||
|
// MCP Server
|
||||||
|
mcpServerStart: (config?: {
|
||||||
|
port?: number
|
||||||
|
host?: string
|
||||||
|
}): Promise<{ success: boolean; data?: { url: string; config: { port: number; host: string } } }> =>
|
||||||
|
invoke("mcp_server_start", { config }),
|
||||||
|
mcpServerStop: (): Promise<{ success: boolean }> => invoke("mcp_server_stop"),
|
||||||
|
mcpServerStatus: (): Promise<{
|
||||||
|
success: boolean
|
||||||
|
data?: {
|
||||||
|
is_running: boolean
|
||||||
|
config: { port: number; host: string }
|
||||||
|
url?: string | null
|
||||||
|
}
|
||||||
|
}> => invoke("mcp_server_status"),
|
||||||
|
|
||||||
// File System
|
// File System
|
||||||
fsGetConfigStructure: (): Promise<{
|
fsGetConfigStructure: (): Promise<{
|
||||||
success: boolean
|
success: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user