Skip to content

Commit

Permalink
feat: add ENABLE_MCP env var to toggle MCP feature globally and in Do…
Browse files Browse the repository at this point in the history
…cker
  • Loading branch information
Kadxy committed Jan 18, 2025
1 parent 0112b54 commit bc71ae2
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 87 deletions.
5 changes: 5 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ CODE=your-password
# You can start service behind a proxy. (optional)
PROXY_URL=http://localhost:7890

# Enable MCP functionality (optional)
# Default: Empty (disabled)
# Set to "true" to enable MCP functionality
ENABLE_MCP=

# (optional)
# Default: Empty
# Google Gemini Pro API key, set if you want to use Google Gemini Pro API.
Expand Down
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,16 @@ ENV PROXY_URL=""
ENV OPENAI_API_KEY=""
ENV GOOGLE_API_KEY=""
ENV CODE=""
ENV ENABLE_MCP=""

COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/.next/server ./.next/server

RUN mkdir -p /app/app/mcp && chmod 777 /app/app/mcp
COPY --from=builder /app/app/mcp/mcp_config.json /app/app/mcp/

EXPOSE 3000

CMD if [ -n "$PROXY_URL" ]; then \
Expand Down
17 changes: 12 additions & 5 deletions app/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ import { isEmpty } from "lodash-es";
import { getModelProvider } from "../utils/model";
import { RealtimeChat } from "@/app/components/realtime-chat";
import clsx from "clsx";
import { getAvailableClientsCount } from "../mcp/actions";
import { getAvailableClientsCount, isMcpEnabled } from "../mcp/actions";

const localStorage = safeLocalStorage();

Expand All @@ -135,15 +135,22 @@ const Markdown = dynamic(async () => (await import("./markdown")).Markdown, {
const MCPAction = () => {
const navigate = useNavigate();
const [count, setCount] = useState<number>(0);
const [mcpEnabled, setMcpEnabled] = useState(false);

useEffect(() => {
const loadCount = async () => {
const count = await getAvailableClientsCount();
setCount(count);
const checkMcpStatus = async () => {
const enabled = await isMcpEnabled();
setMcpEnabled(enabled);
if (enabled) {
const count = await getAvailableClientsCount();
setCount(count);
}
};
loadCount();
checkMcpStatus();
}, []);

if (!mcpEnabled) return null;

return (
<ChatAction
onClick={() => navigate(Path.McpMarket)}
Expand Down
23 changes: 14 additions & 9 deletions app/components/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ import { getClientConfig } from "../config/client";
import { type ClientApi, getClientApi } from "../client/api";
import { useAccessStore } from "../store";
import clsx from "clsx";
import { initializeMcpSystem } from "../mcp/actions";
import { showToast } from "./ui-lib";
import { initializeMcpSystem, isMcpEnabled } from "../mcp/actions";

export function Loading(props: { noLogo?: boolean }) {
return (
Expand Down Expand Up @@ -243,14 +242,20 @@ export function Home() {
useEffect(() => {
console.log("[Config] got config from build time", getClientConfig());
useAccessStore.getState().fetch();
}, []);

useEffect(() => {
// 初始化 MCP 系统
initializeMcpSystem().catch((error) => {
console.error("Failed to initialize MCP system:", error);
showToast("Failed to initialize MCP system");
});
const initMcp = async () => {
try {
const enabled = await isMcpEnabled();
if (enabled) {
console.log("[MCP] initializing...");
await initializeMcpSystem();
console.log("[MCP] initialized");
}
} catch (err) {
console.error("[MCP] failed to initialize:", err);
}
};
initMcp();
}, []);

if (!useHasHydrated()) {
Expand Down
128 changes: 73 additions & 55 deletions app/components/mcp-market.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import {
getClientStatus,
getClientTools,
getMcpConfigFromFile,
restartAllClients,
isMcpEnabled,
pauseMcpServer,
restartAllClients,
resumeMcpServer,
} from "../mcp/actions";
import {
Expand All @@ -30,6 +31,7 @@ import {
import clsx from "clsx";
import PlayIcon from "../icons/play.svg";
import StopIcon from "../icons/pause.svg";
import { Path } from "../constant";

interface ConfigProperty {
type: string;
Expand All @@ -40,6 +42,7 @@ interface ConfigProperty {

export function McpMarketPage() {
const navigate = useNavigate();
const [mcpEnabled, setMcpEnabled] = useState(false);
const [searchText, setSearchText] = useState("");
const [userConfig, setUserConfig] = useState<Record<string, any>>({});
const [editingServerId, setEditingServerId] = useState<string | undefined>();
Expand All @@ -56,8 +59,22 @@ export function McpMarketPage() {
{},
);

// 检查 MCP 是否启用
useEffect(() => {
const checkMcpStatus = async () => {
const enabled = await isMcpEnabled();
setMcpEnabled(enabled);
if (!enabled) {
navigate(Path.Home);
}
};
checkMcpStatus();
}, [navigate]);

// 加载预设服务器
useEffect(() => {
const loadPresetServers = async () => {
if (!mcpEnabled) return;
try {
setLoadingPresets(true);
const response = await fetch("https://nextchat.club/mcp/list");
Expand All @@ -73,17 +90,13 @@ export function McpMarketPage() {
setLoadingPresets(false);
}
};
loadPresetServers().then();
}, []);

// 检查服务器是否已添加
const isServerAdded = (id: string) => {
return id in (config?.mcpServers ?? {});
};
loadPresetServers();
}, [mcpEnabled]);

// 从服务器获取初始状态
// 加载初始状态
useEffect(() => {
const loadInitialState = async () => {
if (!mcpEnabled) return;
try {
setIsLoading(true);
const config = await getMcpConfigFromFile();
Expand All @@ -103,42 +116,50 @@ export function McpMarketPage() {
}
};
loadInitialState();
}, []);
}, [mcpEnabled]);

// 加载当前编辑服务器的配置
useEffect(() => {
if (editingServerId && config) {
const currentConfig = config.mcpServers[editingServerId];
if (currentConfig) {
// 从当前配置中提取用户配置
const preset = presetServers.find((s) => s.id === editingServerId);
if (preset?.configSchema) {
const userConfig: Record<string, any> = {};
Object.entries(preset.argsMapping || {}).forEach(([key, mapping]) => {
if (mapping.type === "spread") {
// 对于 spread 类型,从 args 中提取数组
const startPos = mapping.position ?? 0;
userConfig[key] = currentConfig.args.slice(startPos);
} else if (mapping.type === "single") {
// 对于 single 类型,获取单个值
userConfig[key] = currentConfig.args[mapping.position ?? 0];
} else if (
mapping.type === "env" &&
mapping.key &&
currentConfig.env
) {
// 对于 env 类型,从环境变量中获取值
userConfig[key] = currentConfig.env[mapping.key];
}
});
setUserConfig(userConfig);
}
} else {
setUserConfig({});
if (!editingServerId || !config) return;
const currentConfig = config.mcpServers[editingServerId];
if (currentConfig) {
// 从当前配置中提取用户配置
const preset = presetServers.find((s) => s.id === editingServerId);
if (preset?.configSchema) {
const userConfig: Record<string, any> = {};
Object.entries(preset.argsMapping || {}).forEach(([key, mapping]) => {
if (mapping.type === "spread") {
// For spread types, extract the array from args.
const startPos = mapping.position ?? 0;
userConfig[key] = currentConfig.args.slice(startPos);
} else if (mapping.type === "single") {
// For single types, get a single value
userConfig[key] = currentConfig.args[mapping.position ?? 0];
} else if (
mapping.type === "env" &&
mapping.key &&
currentConfig.env
) {
// For env types, get values from environment variables
userConfig[key] = currentConfig.env[mapping.key];
}
});
setUserConfig(userConfig);
}
} else {
setUserConfig({});
}
}, [editingServerId, config, presetServers]);

if (!mcpEnabled) {
return null;
}

// 检查服务器是否已添加
const isServerAdded = (id: string) => {
return id in (config?.mcpServers ?? {});
};

// 保存服务器配置
const saveServerConfig = async () => {
const preset = presetServers.find((s) => s.id === editingServerId);
Expand Down Expand Up @@ -291,8 +312,8 @@ export function McpMarketPage() {
}
};

// 修改恢复服务器函数
const resumeServer = async (id: string) => {
// Restart server
const restartServer = async (id: string) => {
try {
updateLoadingState(id, "Starting server...");

Expand Down Expand Up @@ -320,7 +341,7 @@ export function McpMarketPage() {
}
};

// 修改重启所有客户端函数
// Restart all clients
const handleRestartAll = async () => {
try {
updateLoadingState("all", "Restarting all servers...");
Expand All @@ -342,7 +363,7 @@ export function McpMarketPage() {
}
};

// 渲染配置表单
// Render configuration form
const renderConfigForm = () => {
const preset = presetServers.find((s) => s.id === editingServerId);
if (!preset?.configSchema) return null;
Expand Down Expand Up @@ -422,12 +443,10 @@ export function McpMarketPage() {
);
};

// 检查服务器状态
const checkServerStatus = (clientId: string) => {
return clientStatuses[clientId] || { status: "undefined", errorMsg: null };
};

// 修改状态显示逻辑
const getServerStatusDisplay = (clientId: string) => {
const status = checkServerStatus(clientId);

Expand All @@ -450,7 +469,7 @@ export function McpMarketPage() {
return statusMap[status.status];
};

// 获取操作状态的类型
// Get the type of operation status
const getOperationStatusType = (message: string) => {
if (message.toLowerCase().includes("stopping")) return "stopping";
if (message.toLowerCase().includes("starting")) return "starting";
Expand Down Expand Up @@ -496,15 +515,15 @@ export function McpMarketPage() {

// 定义状态优先级
const statusPriority: Record<string, number> = {
error: 0, // 错误状态最高优先级
active: 1, // 已启动次之
starting: 2, // 正在启动
stopping: 3, // 正在停止
paused: 4, // 已暂停
undefined: 5, // 未配置最低优先级
error: 0, // Highest priority for error status
active: 1, // Second for active
starting: 2, // Starting
stopping: 3, // Stopping
paused: 4, // Paused
undefined: 5, // Lowest priority for undefined
};

// 获取实际状态(包括加载状态)
// Get actual status (including loading status)
const getEffectiveStatus = (status: string, loading?: string) => {
if (loading) {
const operationType = getOperationStatusType(loading);
Expand All @@ -524,7 +543,7 @@ export function McpMarketPage() {
);
}

// 状态相同时按名称排序
// Sort by name when statuses are the same
return a.name.localeCompare(b.name);
})
.map((server) => (
Expand Down Expand Up @@ -591,7 +610,7 @@ export function McpMarketPage() {
<IconButton
icon={<PlayIcon />}
text="Start"
onClick={() => resumeServer(server.id)}
onClick={() => restartServer(server.id)}
disabled={isLoading}
/>
{/* <IconButton
Expand Down Expand Up @@ -720,7 +739,6 @@ export function McpMarketPage() {
</div>
)}

{/*支持的Tools*/}
{viewingServerId && (
<div className="modal-mask">
<Modal
Expand Down
Loading

0 comments on commit bc71ae2

Please sign in to comment.