mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Add MCP configuration visualization and editing in settings modal (#8029)
Co-authored-by: openhands <openhands@all-hands.dev> Co-authored-by: Ray Myers <ray.myers@gmail.com>
This commit is contained in:
parent
3ccc4b34c5
commit
ccf2c7f2cb
4
.github/workflows/ghcr-build.yml
vendored
4
.github/workflows/ghcr-build.yml
vendored
@ -40,7 +40,9 @@ jobs:
|
||||
# Only build nikolaik on PRs, otherwise build both nikolaik and ubuntu.
|
||||
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
|
||||
json=$(jq -n -c '[
|
||||
{ image: "nikolaik/python-nodejs:python3.12-nodejs22", tag: "nikolaik" }
|
||||
{ image: "nikolaik/python-nodejs:python3.12-nodejs22", tag: "nikolaik" },
|
||||
{ image: "ubuntu:24.04", tag: "ubuntu" }
|
||||
|
||||
]')
|
||||
else
|
||||
json=$(jq -n -c '[
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MCPConfig } from "#/types/settings";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { MCPSSEServers } from "./mcp-sse-servers";
|
||||
import { MCPStdioServers } from "./mcp-stdio-servers";
|
||||
import { MCPJsonEditor } from "./mcp-json-editor";
|
||||
import { BrandButton } from "../brand-button";
|
||||
|
||||
interface MCPConfigEditorProps {
|
||||
mcpConfig?: MCPConfig;
|
||||
onChange: (config: MCPConfig) => void;
|
||||
}
|
||||
|
||||
export function MCPConfigEditor({ mcpConfig, onChange }: MCPConfigEditorProps) {
|
||||
const { t } = useTranslation();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const handleConfigChange = (newConfig: MCPConfig) => {
|
||||
onChange(newConfig);
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const config = mcpConfig || { sse_servers: [], stdio_servers: [] };
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col gap-2 mb-6">
|
||||
<div className="text-sm font-medium">
|
||||
{t(I18nKey.SETTINGS$MCP_TITLE)}
|
||||
</div>
|
||||
<p className="text-xs text-[#A3A3A3]">
|
||||
{t(I18nKey.SETTINGS$MCP_DESCRIPTION)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="flex items-center">
|
||||
<a
|
||||
href="https://docs.all-hands.dev/modules/usage/mcp"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-sm text-blue-400 hover:underline mr-3"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
<BrandButton
|
||||
type="button"
|
||||
variant="primary"
|
||||
onClick={() => setIsEditing(!isEditing)}
|
||||
>
|
||||
{isEditing
|
||||
? t(I18nKey.SETTINGS$MCP_CANCEL)
|
||||
: t(I18nKey.SETTINGS$MCP_EDIT_CONFIGURATION)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{isEditing ? (
|
||||
<MCPJsonEditor mcpConfig={mcpConfig} onChange={handleConfigChange} />
|
||||
) : (
|
||||
<>
|
||||
<div className="flex flex-col gap-6">
|
||||
<div>
|
||||
<MCPSSEServers servers={config.sse_servers} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<MCPStdioServers servers={config.stdio_servers} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{config.sse_servers.length === 0 &&
|
||||
config.stdio_servers.length === 0 && (
|
||||
<div className="mt-4 p-2 bg-yellow-50 border border-yellow-200 rounded-md text-sm text-yellow-700">
|
||||
{t(I18nKey.SETTINGS$MCP_NO_SERVERS_CONFIGURED)}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,141 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MCPConfig, MCPSSEServer, MCPStdioServer } from "#/types/settings";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface MCPConfigViewerProps {
|
||||
mcpConfig?: MCPConfig;
|
||||
}
|
||||
|
||||
interface SSEServerDisplayProps {
|
||||
server: string | MCPSSEServer;
|
||||
}
|
||||
|
||||
function SSEServerDisplay({ server }: SSEServerDisplayProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (typeof server === "string") {
|
||||
return (
|
||||
<div className="mb-2 p-2 bg-base-tertiary rounded-md">
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">{t(I18nKey.SETTINGS$MCP_URL)}:</span>{" "}
|
||||
{server}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-2 p-2 bg-base-tertiary rounded-md">
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">{t(I18nKey.SETTINGS$MCP_URL)}:</span>{" "}
|
||||
{server.url}
|
||||
</div>
|
||||
{server.api_key && (
|
||||
<div className="text-sm text-gray-500">
|
||||
<span className="font-medium">
|
||||
{t(I18nKey.SETTINGS$MCP_API_KEY)}:
|
||||
</span>{" "}
|
||||
{server.api_key ? "Set" : t(I18nKey.SETTINGS$MCP_API_KEY_NOT_SET)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface StdioServerDisplayProps {
|
||||
server: MCPStdioServer;
|
||||
}
|
||||
|
||||
function StdioServerDisplay({ server }: StdioServerDisplayProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="mb-2 p-2 bg-base-tertiary rounded-md">
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">{t(I18nKey.SETTINGS$MCP_NAME)}:</span>{" "}
|
||||
{server.name}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
<span className="font-medium">{t(I18nKey.SETTINGS$MCP_COMMAND)}:</span>{" "}
|
||||
{server.command}
|
||||
</div>
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div className="text-sm text-gray-500">
|
||||
<span className="font-medium">{t(I18nKey.SETTINGS$MCP_ARGS)}:</span>{" "}
|
||||
{server.args.join(" ")}
|
||||
</div>
|
||||
)}
|
||||
{server.env && Object.keys(server.env).length > 0 && (
|
||||
<div className="text-sm text-gray-500">
|
||||
<span className="font-medium">{t(I18nKey.SETTINGS$MCP_ENV)}:</span>{" "}
|
||||
{Object.entries(server.env)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(", ")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function MCPConfigViewer({ mcpConfig }: MCPConfigViewerProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (
|
||||
!mcpConfig ||
|
||||
(mcpConfig.sse_servers.length === 0 && mcpConfig.stdio_servers.length === 0)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-4 border border-base-tertiary rounded-md p-3">
|
||||
<div className="flex justify-between items-center mb-3">
|
||||
<h3 className="text-sm font-medium">
|
||||
{t(I18nKey.SETTINGS$MCP_CONFIGURATION)}
|
||||
</h3>
|
||||
<a
|
||||
href="https://docs.all-hands.dev/modules/usage/mcp"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-blue-400 hover:underline"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{t(I18nKey.SETTINGS$MCP_LEARN_MORE)}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="mt-2">
|
||||
<div className="flex flex-col gap-4">
|
||||
{mcpConfig.sse_servers.length > 0 && (
|
||||
<div className="mb-3">
|
||||
<h4 className="text-sm font-medium mb-1">
|
||||
{t(I18nKey.SETTINGS$MCP_SSE_SERVERS)}{" "}
|
||||
<span className="text-gray-500">
|
||||
({mcpConfig.sse_servers.length})
|
||||
</span>
|
||||
</h4>
|
||||
{mcpConfig.sse_servers.map((server, index) => (
|
||||
<SSEServerDisplay key={`sse-${index}`} server={server} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{mcpConfig.stdio_servers.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-1">
|
||||
{t(I18nKey.SETTINGS$MCP_STDIO_SERVERS)}{" "}
|
||||
<span className="text-gray-500">
|
||||
({mcpConfig.stdio_servers.length})
|
||||
</span>
|
||||
</h4>
|
||||
{mcpConfig.stdio_servers.map((server, index) => (
|
||||
<StdioServerDisplay key={`stdio-${index}`} server={server} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,97 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MCPConfig } from "#/types/settings";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { BrandButton } from "../brand-button";
|
||||
|
||||
interface MCPJsonEditorProps {
|
||||
mcpConfig?: MCPConfig;
|
||||
onChange: (config: MCPConfig) => void;
|
||||
}
|
||||
|
||||
export function MCPJsonEditor({ mcpConfig, onChange }: MCPJsonEditorProps) {
|
||||
const { t } = useTranslation();
|
||||
const [configText, setConfigText] = useState(() =>
|
||||
mcpConfig
|
||||
? JSON.stringify(mcpConfig, null, 2)
|
||||
: t(I18nKey.SETTINGS$MCP_DEFAULT_CONFIG),
|
||||
);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const handleTextChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setConfigText(e.target.value);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
try {
|
||||
const newConfig = JSON.parse(configText);
|
||||
|
||||
// Validate the structure
|
||||
if (!newConfig.sse_servers || !Array.isArray(newConfig.sse_servers)) {
|
||||
throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_SSE_ARRAY));
|
||||
}
|
||||
|
||||
if (!newConfig.stdio_servers || !Array.isArray(newConfig.stdio_servers)) {
|
||||
throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_STDIO_ARRAY));
|
||||
}
|
||||
|
||||
// Validate SSE servers
|
||||
for (const server of newConfig.sse_servers) {
|
||||
if (
|
||||
typeof server !== "string" &&
|
||||
(!server.url || typeof server.url !== "string")
|
||||
) {
|
||||
throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_SSE_URL));
|
||||
}
|
||||
}
|
||||
|
||||
// Validate stdio servers
|
||||
for (const server of newConfig.stdio_servers) {
|
||||
if (!server.name || !server.command) {
|
||||
throw new Error(t(I18nKey.SETTINGS$MCP_ERROR_STDIO_PROPS));
|
||||
}
|
||||
}
|
||||
|
||||
onChange(newConfig);
|
||||
setError(null);
|
||||
} catch (e) {
|
||||
setError(
|
||||
e instanceof Error
|
||||
? e.message
|
||||
: t(I18nKey.SETTINGS$MCP_ERROR_INVALID_JSON),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-2 text-sm text-gray-400">
|
||||
{t(I18nKey.SETTINGS$MCP_CONFIG_DESCRIPTION)}
|
||||
</div>
|
||||
<textarea
|
||||
className="w-full h-64 p-2 text-sm font-mono bg-base-tertiary rounded-md focus:border-blue-500 focus:outline-none"
|
||||
value={configText}
|
||||
onChange={handleTextChange}
|
||||
spellCheck="false"
|
||||
/>
|
||||
{error && (
|
||||
<div className="mt-2 p-2 bg-red-100 border border-red-300 rounded-md text-sm text-red-700">
|
||||
<strong>{t(I18nKey.SETTINGS$MCP_CONFIG_ERROR)}</strong> {error}
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-2 text-sm text-gray-400">
|
||||
<strong>{t(I18nKey.SETTINGS$MCP_CONFIG_EXAMPLE)}</strong>{" "}
|
||||
<code>
|
||||
{
|
||||
'{ "sse_servers": ["https://example-mcp-server.com/sse"], "stdio_servers": [{ "name": "fetch", "command": "uvx", "args": ["mcp-server-fetch"] }] }'
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
<div className="mt-4 flex justify-end">
|
||||
<BrandButton type="button" variant="primary" onClick={handleSave}>
|
||||
{t(I18nKey.SETTINGS$MCP_APPLY_CHANGES)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MCPSSEServer } from "#/types/settings";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface MCPSSEServersProps {
|
||||
servers: (string | MCPSSEServer)[];
|
||||
}
|
||||
|
||||
export function MCPSSEServers({ servers }: MCPSSEServersProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">
|
||||
{t(I18nKey.SETTINGS$MCP_SSE_SERVERS)}{" "}
|
||||
<span className="text-gray-500">({servers.length})</span>
|
||||
</h4>
|
||||
{servers.map((server, index) => (
|
||||
<div
|
||||
key={`sse-${index}`}
|
||||
className="mb-2 p-2 bg-base-tertiary rounded-md"
|
||||
>
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">{t(I18nKey.SETTINGS$MCP_URL)}:</span>{" "}
|
||||
{typeof server === "string" ? server : server.url}
|
||||
</div>
|
||||
{typeof server !== "string" && server.api_key && (
|
||||
<div className="mt-1 text-sm text-gray-500">
|
||||
<span className="font-medium">
|
||||
{t(I18nKey.SETTINGS$MCP_API_KEY)}:
|
||||
</span>{" "}
|
||||
{server.api_key
|
||||
? "Configured"
|
||||
: t(I18nKey.SETTINGS$MCP_API_KEY_NOT_SET)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { MCPStdioServer } from "#/types/settings";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
|
||||
interface MCPStdioServersProps {
|
||||
servers: MCPStdioServer[];
|
||||
}
|
||||
|
||||
export function MCPStdioServers({ servers }: MCPStdioServersProps) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h4 className="text-sm font-medium mb-2">
|
||||
{t(I18nKey.SETTINGS$MCP_STDIO_SERVERS)}{" "}
|
||||
<span className="text-gray-500">({servers.length})</span>
|
||||
</h4>
|
||||
{servers.map((server, index) => (
|
||||
<div
|
||||
key={`stdio-${index}`}
|
||||
className="mb-2 p-2 bg-base-tertiary rounded-md"
|
||||
>
|
||||
<div className="text-sm">
|
||||
<span className="font-medium">{t(I18nKey.SETTINGS$MCP_NAME)}:</span>{" "}
|
||||
{server.name}
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-gray-500">
|
||||
<span className="font-medium">
|
||||
{t(I18nKey.SETTINGS$MCP_COMMAND)}:
|
||||
</span>{" "}
|
||||
<code className="font-mono">{server.command}</code>
|
||||
</div>
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div className="mt-1 text-sm text-gray-500">
|
||||
<span className="font-medium">
|
||||
{t(I18nKey.SETTINGS$MCP_ARGS)}:
|
||||
</span>{" "}
|
||||
<code className="font-mono">{server.args.join(" ")}</code>
|
||||
</div>
|
||||
)}
|
||||
{server.env && Object.keys(server.env).length > 0 && (
|
||||
<div className="mt-1 text-sm text-gray-500">
|
||||
<span className="font-medium">
|
||||
{t(I18nKey.SETTINGS$MCP_ENV)}:
|
||||
</span>{" "}
|
||||
<code className="font-mono">
|
||||
{Object.entries(server.env)
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join(", ")}
|
||||
</code>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -40,6 +40,7 @@ export function SettingsModal({ onClose, settings }: SettingsModalProps) {
|
||||
{t(I18nKey.SETTINGS$SEE_ADVANCED_SETTINGS)}
|
||||
</Link>
|
||||
</p>
|
||||
|
||||
{aiConfigOptions.isLoading && (
|
||||
<div className="flex justify-center">
|
||||
<LoadingSpinner size="small" />
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import posthog from "posthog-js";
|
||||
import { DEFAULT_SETTINGS } from "#/services/settings";
|
||||
import OpenHands from "#/api/open-hands";
|
||||
import { PostSettings, PostApiSettings } from "#/types/settings";
|
||||
@ -20,6 +21,8 @@ const saveSettingsMutationFn = async (settings: Partial<PostSettings>) => {
|
||||
enable_default_condenser: settings.ENABLE_DEFAULT_CONDENSER,
|
||||
enable_sound_notifications: settings.ENABLE_SOUND_NOTIFICATIONS,
|
||||
user_consents_to_analytics: settings.user_consents_to_analytics,
|
||||
provider_tokens_set: settings.PROVIDER_TOKENS_SET,
|
||||
mcp_config: settings.MCP_CONFIG,
|
||||
enable_proactive_conversation_starters:
|
||||
settings.ENABLE_PROACTIVE_CONVERSATION_STARTERS,
|
||||
};
|
||||
@ -34,6 +37,25 @@ export const useSaveSettings = () => {
|
||||
return useMutation({
|
||||
mutationFn: async (settings: Partial<PostSettings>) => {
|
||||
const newSettings = { ...currentSettings, ...settings };
|
||||
|
||||
// Track MCP configuration changes
|
||||
if (
|
||||
settings.MCP_CONFIG &&
|
||||
currentSettings?.MCP_CONFIG !== settings.MCP_CONFIG
|
||||
) {
|
||||
const hasMcpConfig = !!settings.MCP_CONFIG;
|
||||
const sseServersCount = settings.MCP_CONFIG?.sse_servers?.length || 0;
|
||||
const stdioServersCount =
|
||||
settings.MCP_CONFIG?.stdio_servers?.length || 0;
|
||||
|
||||
// Track MCP configuration usage
|
||||
posthog.capture("mcp_config_updated", {
|
||||
has_mcp_config: hasMcpConfig,
|
||||
sse_servers_count: sseServersCount,
|
||||
stdio_servers_count: stdioServersCount,
|
||||
});
|
||||
}
|
||||
|
||||
await saveSettingsMutationFn(newSettings);
|
||||
},
|
||||
onSuccess: async () => {
|
||||
|
||||
@ -25,6 +25,8 @@ const getSettingsQueryFn = async (): Promise<Settings> => {
|
||||
ENABLE_PROACTIVE_CONVERSATION_STARTERS:
|
||||
apiSettings.enable_proactive_conversation_starters,
|
||||
USER_CONSENTS_TO_ANALYTICS: apiSettings.user_consents_to_analytics,
|
||||
|
||||
MCP_CONFIG: apiSettings.mcp_config,
|
||||
IS_NEW_USER: false,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,5 +1,32 @@
|
||||
// this file generate by script, don't modify it manually!!!
|
||||
export enum I18nKey {
|
||||
SETTINGS$MCP_TITLE = "SETTINGS$MCP_TITLE",
|
||||
SETTINGS$MCP_DESCRIPTION = "SETTINGS$MCP_DESCRIPTION",
|
||||
SETTINGS$NAV_MCP = "SETTINGS$NAV_MCP",
|
||||
SETTINGS$MCP_CONFIGURATION = "SETTINGS$MCP_CONFIGURATION",
|
||||
SETTINGS$MCP_EDIT_CONFIGURATION = "SETTINGS$MCP_EDIT_CONFIGURATION",
|
||||
SETTINGS$MCP_CANCEL = "SETTINGS$MCP_CANCEL",
|
||||
SETTINGS$MCP_APPLY_CHANGES = "SETTINGS$MCP_APPLY_CHANGES",
|
||||
SETTINGS$MCP_CONFIG_DESCRIPTION = "SETTINGS$MCP_CONFIG_DESCRIPTION",
|
||||
SETTINGS$MCP_CONFIG_ERROR = "SETTINGS$MCP_CONFIG_ERROR",
|
||||
SETTINGS$MCP_CONFIG_EXAMPLE = "SETTINGS$MCP_CONFIG_EXAMPLE",
|
||||
SETTINGS$MCP_NO_SERVERS_CONFIGURED = "SETTINGS$MCP_NO_SERVERS_CONFIGURED",
|
||||
SETTINGS$MCP_SSE_SERVERS = "SETTINGS$MCP_SSE_SERVERS",
|
||||
SETTINGS$MCP_STDIO_SERVERS = "SETTINGS$MCP_STDIO_SERVERS",
|
||||
SETTINGS$MCP_API_KEY = "SETTINGS$MCP_API_KEY",
|
||||
SETTINGS$MCP_API_KEY_NOT_SET = "SETTINGS$MCP_API_KEY_NOT_SET",
|
||||
SETTINGS$MCP_COMMAND = "SETTINGS$MCP_COMMAND",
|
||||
SETTINGS$MCP_ARGS = "SETTINGS$MCP_ARGS",
|
||||
SETTINGS$MCP_ENV = "SETTINGS$MCP_ENV",
|
||||
SETTINGS$MCP_NAME = "SETTINGS$MCP_NAME",
|
||||
SETTINGS$MCP_URL = "SETTINGS$MCP_URL",
|
||||
SETTINGS$MCP_LEARN_MORE = "SETTINGS$MCP_LEARN_MORE",
|
||||
SETTINGS$MCP_ERROR_SSE_ARRAY = "SETTINGS$MCP_ERROR_SSE_ARRAY",
|
||||
SETTINGS$MCP_ERROR_STDIO_ARRAY = "SETTINGS$MCP_ERROR_STDIO_ARRAY",
|
||||
SETTINGS$MCP_ERROR_SSE_URL = "SETTINGS$MCP_ERROR_SSE_URL",
|
||||
SETTINGS$MCP_ERROR_STDIO_PROPS = "SETTINGS$MCP_ERROR_STDIO_PROPS",
|
||||
SETTINGS$MCP_ERROR_INVALID_JSON = "SETTINGS$MCP_ERROR_INVALID_JSON",
|
||||
SETTINGS$MCP_DEFAULT_CONFIG = "SETTINGS$MCP_DEFAULT_CONFIG",
|
||||
HOME$CONNECT_PROVIDER_MESSAGE = "HOME$CONNECT_PROVIDER_MESSAGE",
|
||||
HOME$LETS_START_BUILDING = "HOME$LETS_START_BUILDING",
|
||||
HOME$OPENHANDS_DESCRIPTION = "HOME$OPENHANDS_DESCRIPTION",
|
||||
@ -366,6 +393,7 @@ export enum I18nKey {
|
||||
FILE_EXPLORER$UPLOAD = "FILE_EXPLORER$UPLOAD",
|
||||
ACTION_MESSAGE$RUN = "ACTION_MESSAGE$RUN",
|
||||
ACTION_MESSAGE$RUN_IPYTHON = "ACTION_MESSAGE$RUN_IPYTHON",
|
||||
ACTION_MESSAGE$CALL_TOOL_MCP = "ACTION_MESSAGE$CALL_TOOL_MCP",
|
||||
ACTION_MESSAGE$READ = "ACTION_MESSAGE$READ",
|
||||
ACTION_MESSAGE$EDIT = "ACTION_MESSAGE$EDIT",
|
||||
ACTION_MESSAGE$WRITE = "ACTION_MESSAGE$WRITE",
|
||||
@ -379,6 +407,7 @@ export enum I18nKey {
|
||||
OBSERVATION_MESSAGE$EDIT = "OBSERVATION_MESSAGE$EDIT",
|
||||
OBSERVATION_MESSAGE$WRITE = "OBSERVATION_MESSAGE$WRITE",
|
||||
OBSERVATION_MESSAGE$BROWSE = "OBSERVATION_MESSAGE$BROWSE",
|
||||
OBSERVATION_MESSAGE$MCP = "OBSERVATION_MESSAGE$MCP",
|
||||
OBSERVATION_MESSAGE$RECALL = "OBSERVATION_MESSAGE$RECALL",
|
||||
EXPANDABLE_MESSAGE$SHOW_DETAILS = "EXPANDABLE_MESSAGE$SHOW_DETAILS",
|
||||
EXPANDABLE_MESSAGE$HIDE_DETAILS = "EXPANDABLE_MESSAGE$HIDE_DETAILS",
|
||||
|
||||
@ -1,4 +1,409 @@
|
||||
{
|
||||
"SETTINGS$MCP_TITLE": {
|
||||
"en": "Model Context Protocol (MCP)",
|
||||
"ja": "モデルコンテキストプロトコル (MCP)",
|
||||
"zh-CN": "模型上下文协议 (MCP)",
|
||||
"zh-TW": "模型上下文協議 (MCP)",
|
||||
"ko-KR": "모델 컨텍스트 프로토콜 (MCP)",
|
||||
"no": "Modellkontekstprotokoll (MCP)",
|
||||
"it": "Protocollo di Contesto del Modello (MCP)",
|
||||
"pt": "Protocolo de Contexto de Modelo (MCP)",
|
||||
"es": "Protocolo de Contexto de Modelo (MCP)",
|
||||
"ar": "بروتوكول سياق النموذج (MCP)",
|
||||
"fr": "Protocole de Contexte de Modèle (MCP)",
|
||||
"tr": "Model Bağlam Protokolü (MCP)",
|
||||
"de": "Modellkontextprotokoll (MCP)"
|
||||
},
|
||||
"SETTINGS$MCP_DESCRIPTION": {
|
||||
"en": "Configure MCP servers for enhanced model capabilities",
|
||||
"ja": "拡張モデル機能のためのMCPサーバーを設定する",
|
||||
"zh-CN": "配置MCP服务器以增强模型功能",
|
||||
"zh-TW": "配置MCP服務器以增強模型功能",
|
||||
"ko-KR": "향상된 모델 기능을 위한 MCP 서버 구성",
|
||||
"no": "Konfigurer MCP-servere for forbedrede modellfunksjoner",
|
||||
"it": "Configura i server MCP per funzionalità di modello avanzate",
|
||||
"pt": "Configure servidores MCP para capacidades de modelo aprimoradas",
|
||||
"es": "Configure servidores MCP para capacidades mejoradas del modelo",
|
||||
"ar": "قم بتكوين خوادم MCP لتعزيز قدرات النموذج",
|
||||
"fr": "Configurez les serveurs MCP pour des capacités de modèle améliorées",
|
||||
"tr": "Gelişmiş model yetenekleri için MCP sunucularını yapılandırın",
|
||||
"de": "Konfigurieren Sie MCP-Server für erweiterte Modellfunktionen"
|
||||
},
|
||||
"SETTINGS$NAV_MCP": {
|
||||
"en": "MCP",
|
||||
"ja": "MCP",
|
||||
"zh-CN": "MCP",
|
||||
"zh-TW": "MCP",
|
||||
"ko-KR": "MCP",
|
||||
"no": "MCP",
|
||||
"it": "MCP",
|
||||
"pt": "MCP",
|
||||
"es": "MCP",
|
||||
"ar": "MCP",
|
||||
"fr": "MCP",
|
||||
"tr": "MCP",
|
||||
"de": "MCP"
|
||||
},
|
||||
"SETTINGS$MCP_CONFIGURATION": {
|
||||
"en": "MCP Configuration",
|
||||
"ja": "MCP設定",
|
||||
"zh-CN": "MCP配置",
|
||||
"zh-TW": "MCP配置",
|
||||
"ko-KR": "MCP 구성",
|
||||
"no": "MCP-konfigurasjon",
|
||||
"it": "Configurazione MCP",
|
||||
"pt": "Configuração MCP",
|
||||
"es": "Configuración MCP",
|
||||
"ar": "تكوين MCP",
|
||||
"fr": "Configuration MCP",
|
||||
"tr": "MCP Yapılandırması",
|
||||
"de": "MCP-Konfiguration"
|
||||
},
|
||||
"SETTINGS$MCP_EDIT_CONFIGURATION": {
|
||||
"en": "Edit Configuration",
|
||||
"ja": "設定を編集",
|
||||
"zh-CN": "编辑配置",
|
||||
"zh-TW": "編輯配置",
|
||||
"ko-KR": "구성 편집",
|
||||
"no": "Rediger konfigurasjon",
|
||||
"it": "Modifica configurazione",
|
||||
"pt": "Editar configuração",
|
||||
"es": "Editar configuración",
|
||||
"ar": "تعديل التكوين",
|
||||
"fr": "Modifier la configuration",
|
||||
"tr": "Yapılandırmayı Düzenle",
|
||||
"de": "Konfiguration bearbeiten"
|
||||
},
|
||||
"SETTINGS$MCP_CANCEL": {
|
||||
"en": "Cancel",
|
||||
"ja": "キャンセル",
|
||||
"zh-CN": "取消",
|
||||
"zh-TW": "取消",
|
||||
"ko-KR": "취소",
|
||||
"no": "Avbryt",
|
||||
"it": "Annulla",
|
||||
"pt": "Cancelar",
|
||||
"es": "Cancelar",
|
||||
"ar": "إلغاء",
|
||||
"fr": "Annuler",
|
||||
"tr": "İptal",
|
||||
"de": "Abbrechen"
|
||||
},
|
||||
"SETTINGS$MCP_APPLY_CHANGES": {
|
||||
"en": "Apply Changes",
|
||||
"ja": "変更を適用",
|
||||
"zh-CN": "应用更改",
|
||||
"zh-TW": "應用更改",
|
||||
"ko-KR": "변경 사항 적용",
|
||||
"no": "Bruk endringer",
|
||||
"it": "Applica modifiche",
|
||||
"pt": "Aplicar alterações",
|
||||
"es": "Aplicar cambios",
|
||||
"ar": "تطبيق التغييرات",
|
||||
"fr": "Appliquer les modifications",
|
||||
"tr": "Değişiklikleri Uygula",
|
||||
"de": "Änderungen anwenden"
|
||||
},
|
||||
"SETTINGS$MCP_CONFIG_DESCRIPTION": {
|
||||
"en": "Edit the JSON configuration for MCP servers below. The configuration must include both sse_servers and stdio_servers arrays.",
|
||||
"ja": "以下のMCPサーバーのJSON設定を編集してください。設定にはsse_serversとstdio_serversの両方の配列を含める必要があります。",
|
||||
"zh-CN": "在下方编辑MCP服务器的JSON配置。配置必须包含sse_servers和stdio_servers数组。",
|
||||
"zh-TW": "在下方編輯MCP服務器的JSON配置。配置必須包含sse_servers和stdio_servers數組。",
|
||||
"ko-KR": "아래에서 MCP 서버의 JSON 구성을 편집하세요. 구성에는 sse_servers와 stdio_servers 배열이 모두 포함되어야 합니다.",
|
||||
"no": "Rediger JSON-konfigurasjonen for MCP-servere nedenfor. Konfigurasjonen må inkludere både sse_servers og stdio_servers-matriser.",
|
||||
"it": "Modifica la configurazione JSON per i server MCP qui sotto. La configurazione deve includere sia gli array sse_servers che stdio_servers.",
|
||||
"pt": "Edite a configuração JSON para servidores MCP abaixo. A configuração deve incluir os arrays sse_servers e stdio_servers.",
|
||||
"es": "Edite la configuración JSON para los servidores MCP a continuación. La configuración debe incluir tanto los arrays sse_servers como stdio_servers.",
|
||||
"ar": "قم بتحرير تكوين JSON لخوادم MCP أدناه. يجب أن يتضمن التكوين كلاً من مصفوفات sse_servers و stdio_servers.",
|
||||
"fr": "Modifiez la configuration JSON pour les serveurs MCP ci-dessous. La configuration doit inclure à la fois les tableaux sse_servers et stdio_servers.",
|
||||
"tr": "Aşağıdaki MCP sunucuları için JSON yapılandırmasını düzenleyin. Yapılandırma hem sse_servers hem de stdio_servers dizilerini içermelidir.",
|
||||
"de": "Bearbeiten Sie die JSON-Konfiguration für MCP-Server unten. Die Konfiguration muss sowohl sse_servers- als auch stdio_servers-Arrays enthalten."
|
||||
},
|
||||
"SETTINGS$MCP_CONFIG_ERROR": {
|
||||
"en": "Error:",
|
||||
"ja": "エラー:",
|
||||
"zh-CN": "错误:",
|
||||
"zh-TW": "錯誤:",
|
||||
"ko-KR": "오류:",
|
||||
"no": "Feil:",
|
||||
"it": "Errore:",
|
||||
"pt": "Erro:",
|
||||
"es": "Error:",
|
||||
"ar": "خطأ:",
|
||||
"fr": "Erreur :",
|
||||
"tr": "Hata:",
|
||||
"de": "Fehler:"
|
||||
},
|
||||
"SETTINGS$MCP_CONFIG_EXAMPLE": {
|
||||
"en": "Example:",
|
||||
"ja": "例:",
|
||||
"zh-CN": "示例:",
|
||||
"zh-TW": "範例:",
|
||||
"ko-KR": "예시:",
|
||||
"no": "Eksempel:",
|
||||
"it": "Esempio:",
|
||||
"pt": "Exemplo:",
|
||||
"es": "Ejemplo:",
|
||||
"ar": "مثال:",
|
||||
"fr": "Exemple :",
|
||||
"tr": "Örnek:",
|
||||
"de": "Beispiel:"
|
||||
},
|
||||
"SETTINGS$MCP_NO_SERVERS_CONFIGURED": {
|
||||
"en": "No MCP servers are currently configured. Click \"Edit Configuration\" to add servers.",
|
||||
"ja": "現在MCPサーバーが設定されていません。「設定を編集」をクリックしてサーバーを追加してください。",
|
||||
"zh-CN": "当前未配置MCP服务器。点击\"编辑配置\"添加服务器。",
|
||||
"zh-TW": "當前未配置MCP服務器。點擊\"編輯配置\"添加服務器。",
|
||||
"ko-KR": "현재 구성된 MCP 서버가 없습니다. \"구성 편집\"을 클릭하여 서버를 추가하세요.",
|
||||
"no": "Ingen MCP-servere er konfigurert for øyeblikket. Klikk på \"Rediger konfigurasjon\" for å legge til servere.",
|
||||
"it": "Nessun server MCP è attualmente configurato. Fai clic su \"Modifica configurazione\" per aggiungere server.",
|
||||
"pt": "Nenhum servidor MCP está configurado atualmente. Clique em \"Editar configuração\" para adicionar servidores.",
|
||||
"es": "No hay servidores MCP configurados actualmente. Haga clic en \"Editar configuración\" para agregar servidores.",
|
||||
"ar": "لا توجد خوادم MCP مكونة حاليًا. انقر على \"تعديل التكوين\" لإضافة خوادم.",
|
||||
"fr": "Aucun serveur MCP n'est actuellement configuré. Cliquez sur \"Modifier la configuration\" pour ajouter des serveurs.",
|
||||
"tr": "Şu anda yapılandırılmış MCP sunucusu yok. Sunucu eklemek için \"Yapılandırmayı Düzenle\"yi tıklayın.",
|
||||
"de": "Derzeit sind keine MCP-Server konfiguriert. Klicken Sie auf \"Konfiguration bearbeiten\", um Server hinzuzufügen."
|
||||
},
|
||||
"SETTINGS$MCP_SSE_SERVERS": {
|
||||
"en": "SSE Servers",
|
||||
"ja": "SSEサーバー",
|
||||
"zh-CN": "SSE服务器",
|
||||
"zh-TW": "SSE服務器",
|
||||
"ko-KR": "SSE 서버",
|
||||
"no": "SSE-servere",
|
||||
"it": "Server SSE",
|
||||
"pt": "Servidores SSE",
|
||||
"es": "Servidores SSE",
|
||||
"ar": "خوادم SSE",
|
||||
"fr": "Serveurs SSE",
|
||||
"tr": "SSE Sunucuları",
|
||||
"de": "SSE-Server"
|
||||
},
|
||||
"SETTINGS$MCP_STDIO_SERVERS": {
|
||||
"en": "Stdio Servers",
|
||||
"ja": "Stdioサーバー",
|
||||
"zh-CN": "Stdio服务器",
|
||||
"zh-TW": "Stdio服務器",
|
||||
"ko-KR": "Stdio 서버",
|
||||
"no": "Stdio-servere",
|
||||
"it": "Server Stdio",
|
||||
"pt": "Servidores Stdio",
|
||||
"es": "Servidores Stdio",
|
||||
"ar": "خوادم Stdio",
|
||||
"fr": "Serveurs Stdio",
|
||||
"tr": "Stdio Sunucuları",
|
||||
"de": "Stdio-Server"
|
||||
},
|
||||
"SETTINGS$MCP_API_KEY": {
|
||||
"en": "API Key",
|
||||
"ja": "APIキー",
|
||||
"zh-CN": "API密钥",
|
||||
"zh-TW": "API密鑰",
|
||||
"ko-KR": "API 키",
|
||||
"no": "API-nøkkel",
|
||||
"it": "Chiave API",
|
||||
"pt": "Chave API",
|
||||
"es": "Clave API",
|
||||
"ar": "مفتاح API",
|
||||
"fr": "Clé API",
|
||||
"tr": "API Anahtarı",
|
||||
"de": "API-Schlüssel"
|
||||
},
|
||||
"SETTINGS$MCP_API_KEY_NOT_SET": {
|
||||
"en": "Not set",
|
||||
"ja": "未設定",
|
||||
"zh-CN": "未设置",
|
||||
"zh-TW": "未設置",
|
||||
"ko-KR": "설정되지 않음",
|
||||
"no": "Ikke satt",
|
||||
"it": "Non impostato",
|
||||
"pt": "Não definido",
|
||||
"es": "No establecido",
|
||||
"ar": "غير محدد",
|
||||
"fr": "Non défini",
|
||||
"tr": "Ayarlanmadı",
|
||||
"de": "Nicht festgelegt"
|
||||
},
|
||||
"SETTINGS$MCP_COMMAND": {
|
||||
"en": "Command",
|
||||
"ja": "コマンド",
|
||||
"zh-CN": "命令",
|
||||
"zh-TW": "命令",
|
||||
"ko-KR": "명령",
|
||||
"no": "Kommando",
|
||||
"it": "Comando",
|
||||
"pt": "Comando",
|
||||
"es": "Comando",
|
||||
"ar": "أمر",
|
||||
"fr": "Commande",
|
||||
"tr": "Komut",
|
||||
"de": "Befehl"
|
||||
},
|
||||
"SETTINGS$MCP_ARGS": {
|
||||
"en": "Args",
|
||||
"ja": "引数",
|
||||
"zh-CN": "参数",
|
||||
"zh-TW": "參數",
|
||||
"ko-KR": "인수",
|
||||
"no": "Argumenter",
|
||||
"it": "Argomenti",
|
||||
"pt": "Argumentos",
|
||||
"es": "Argumentos",
|
||||
"ar": "وسيطات",
|
||||
"fr": "Arguments",
|
||||
"tr": "Argümanlar",
|
||||
"de": "Argumente"
|
||||
},
|
||||
"SETTINGS$MCP_ENV": {
|
||||
"en": "Env",
|
||||
"ja": "環境変数",
|
||||
"zh-CN": "环境变量",
|
||||
"zh-TW": "環境變數",
|
||||
"ko-KR": "환경",
|
||||
"no": "Miljø",
|
||||
"it": "Ambiente",
|
||||
"pt": "Ambiente",
|
||||
"es": "Entorno",
|
||||
"ar": "بيئة",
|
||||
"fr": "Environnement",
|
||||
"tr": "Ortam",
|
||||
"de": "Umgebung"
|
||||
},
|
||||
"SETTINGS$MCP_NAME": {
|
||||
"en": "Name",
|
||||
"ja": "名前",
|
||||
"zh-CN": "名称",
|
||||
"zh-TW": "名稱",
|
||||
"ko-KR": "이름",
|
||||
"no": "Navn",
|
||||
"it": "Nome",
|
||||
"pt": "Nome",
|
||||
"es": "Nombre",
|
||||
"ar": "اسم",
|
||||
"fr": "Nom",
|
||||
"tr": "İsim",
|
||||
"de": "Name"
|
||||
},
|
||||
"SETTINGS$MCP_URL": {
|
||||
"en": "URL",
|
||||
"ja": "URL",
|
||||
"zh-CN": "URL",
|
||||
"zh-TW": "URL",
|
||||
"ko-KR": "URL",
|
||||
"no": "URL",
|
||||
"it": "URL",
|
||||
"pt": "URL",
|
||||
"es": "URL",
|
||||
"ar": "URL",
|
||||
"fr": "URL",
|
||||
"tr": "URL",
|
||||
"de": "URL"
|
||||
},
|
||||
"SETTINGS$MCP_LEARN_MORE": {
|
||||
"en": "Learn more",
|
||||
"ja": "詳細を見る",
|
||||
"zh-CN": "了解更多",
|
||||
"zh-TW": "了解更多",
|
||||
"ko-KR": "더 알아보기",
|
||||
"no": "Lær mer",
|
||||
"it": "Scopri di più",
|
||||
"pt": "Saiba mais",
|
||||
"es": "Más información",
|
||||
"ar": "تعرف على المزيد",
|
||||
"fr": "En savoir plus",
|
||||
"tr": "Daha fazla bilgi",
|
||||
"de": "Mehr erfahren"
|
||||
},
|
||||
"SETTINGS$MCP_ERROR_SSE_ARRAY": {
|
||||
"en": "sse_servers must be an array",
|
||||
"ja": "sse_serversは配列である必要があります",
|
||||
"zh-CN": "sse_servers必须是一个数组",
|
||||
"zh-TW": "sse_servers必須是一個數組",
|
||||
"ko-KR": "sse_servers는 배열이어야 합니다",
|
||||
"no": "sse_servers må være en matrise",
|
||||
"it": "sse_servers deve essere un array",
|
||||
"pt": "sse_servers deve ser um array",
|
||||
"es": "sse_servers debe ser un array",
|
||||
"ar": "يجب أن يكون sse_servers مصفوفة",
|
||||
"fr": "sse_servers doit être un tableau",
|
||||
"tr": "sse_servers bir dizi olmalıdır",
|
||||
"de": "sse_servers muss ein Array sein"
|
||||
},
|
||||
"SETTINGS$MCP_ERROR_STDIO_ARRAY": {
|
||||
"en": "stdio_servers must be an array",
|
||||
"ja": "stdio_serversは配列である必要があります",
|
||||
"zh-CN": "stdio_servers必须是一个数组",
|
||||
"zh-TW": "stdio_servers必須是一個數組",
|
||||
"ko-KR": "stdio_servers는 배열이어야 합니다",
|
||||
"no": "stdio_servers må være en matrise",
|
||||
"it": "stdio_servers deve essere un array",
|
||||
"pt": "stdio_servers deve ser um array",
|
||||
"es": "stdio_servers debe ser un array",
|
||||
"ar": "يجب أن يكون stdio_servers مصفوفة",
|
||||
"fr": "stdio_servers doit être un tableau",
|
||||
"tr": "stdio_servers bir dizi olmalıdır",
|
||||
"de": "stdio_servers muss ein Array sein"
|
||||
},
|
||||
"SETTINGS$MCP_ERROR_SSE_URL": {
|
||||
"en": "Each SSE server must be a string URL or have a url property",
|
||||
"ja": "各SSEサーバーは文字列URLまたはurlプロパティを持つ必要があります",
|
||||
"zh-CN": "每个SSE服务器必须是字符串URL或具有url属性",
|
||||
"zh-TW": "每個SSE服務器必須是字符串URL或具有url屬性",
|
||||
"ko-KR": "각 SSE 서버는 문자열 URL이거나 url 속성을 가져야 합니다",
|
||||
"no": "Hver SSE-server må være en streng-URL eller ha en url-egenskap",
|
||||
"it": "Ogni server SSE deve essere un URL stringa o avere una proprietà url",
|
||||
"pt": "Cada servidor SSE deve ser uma URL de string ou ter uma propriedade url",
|
||||
"es": "Cada servidor SSE debe ser una URL de cadena o tener una propiedad url",
|
||||
"ar": "يجب أن يكون كل خادم SSE عنوان URL نصيًا أو يحتوي على خاصية url",
|
||||
"fr": "Chaque serveur SSE doit être une URL de chaîne ou avoir une propriété url",
|
||||
"tr": "Her SSE sunucusu bir dize URL'si olmalı veya bir url özelliğine sahip olmalıdır",
|
||||
"de": "Jeder SSE-Server muss eine String-URL sein oder eine URL-Eigenschaft haben"
|
||||
},
|
||||
"SETTINGS$MCP_ERROR_STDIO_PROPS": {
|
||||
"en": "Each stdio server must have name and command properties",
|
||||
"ja": "各stdioサーバーはnameとcommandプロパティを持つ必要があります",
|
||||
"zh-CN": "每个stdio服务器必须具有name和command属性",
|
||||
"zh-TW": "每個stdio服務器必須具有name和command屬性",
|
||||
"ko-KR": "각 stdio 서버는 name 및 command 속성을 가져야 합니다",
|
||||
"no": "Hver stdio-server må ha egenskapene name og command",
|
||||
"it": "Ogni server stdio deve avere le proprietà name e command",
|
||||
"pt": "Cada servidor stdio deve ter propriedades name e command",
|
||||
"es": "Cada servidor stdio debe tener propiedades name y command",
|
||||
"ar": "يجب أن يحتوي كل خادم stdio على خصائص name و command",
|
||||
"fr": "Chaque serveur stdio doit avoir les propriétés name et command",
|
||||
"tr": "Her stdio sunucusu name ve command özelliklerine sahip olmalıdır",
|
||||
"de": "Jeder stdio-Server muss die Eigenschaften name und command haben"
|
||||
},
|
||||
"SETTINGS$MCP_ERROR_INVALID_JSON": {
|
||||
"en": "Invalid JSON",
|
||||
"ja": "無効なJSON",
|
||||
"zh-CN": "无效的JSON",
|
||||
"zh-TW": "無效的JSON",
|
||||
"ko-KR": "잘못된 JSON",
|
||||
"no": "Ugyldig JSON",
|
||||
"it": "JSON non valido",
|
||||
"pt": "JSON inválido",
|
||||
"es": "JSON no válido",
|
||||
"ar": "JSON غير صالح",
|
||||
"fr": "JSON invalide",
|
||||
"tr": "Geçersiz JSON",
|
||||
"de": "Ungültiges JSON"
|
||||
},
|
||||
"SETTINGS$MCP_DEFAULT_CONFIG": {
|
||||
"en": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"ja": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"zh-CN": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"zh-TW": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"ko-KR": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"no": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"it": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"pt": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"es": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"ar": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"fr": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"tr": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}",
|
||||
"de": "{\n \"sse_servers\": [],\n \"stdio_servers\": []\n}"
|
||||
},
|
||||
"HOME$CONNECT_PROVIDER_MESSAGE": {
|
||||
"en": "To get started with suggested tasks, please connect your GitHub or GitLab account.",
|
||||
"ja": "提案されたタスクを始めるには、GitHubまたはGitLabアカウントを接続してください。",
|
||||
@ -5220,6 +5625,21 @@
|
||||
"es": "Ejecutando un comando Python",
|
||||
"tr": "Python komutu çalıştırılıyor"
|
||||
},
|
||||
"ACTION_MESSAGE$CALL_TOOL_MCP": {
|
||||
"en": "Calling MCP Tool: {{action.payload.args.name}}",
|
||||
"zh-CN": "调用 MCP 工具: {{action.payload.args.name}}",
|
||||
"zh-TW": "呼叫 MCP 工具: {{action.payload.args.name}}",
|
||||
"ko-KR": "MCP 도구 호출: {{action.payload.args.name}}",
|
||||
"ja": "MCP ツール呼び出し: {{action.payload.args.name}}",
|
||||
"no": "Kaller MCP-verktøy: {{action.payload.args.name}}",
|
||||
"ar": "استدعاء أداة MCP: {{action.payload.args.name}}",
|
||||
"de": "Ruft MCP-Tool auf: {{action.payload.args.name}}",
|
||||
"fr": "Appel de l'outil MCP: {{action.payload.args.name}}",
|
||||
"it": "Chiamata allo strumento MCP: {{action.payload.args.name}}",
|
||||
"pt": "Chamando ferramenta MCP: {{action.payload.args.name}}",
|
||||
"es": "Llamando a la herramienta MCP: {{action.payload.args.name}}",
|
||||
"tr": "MCP Aracı çağrılıyor: {{action.payload.args.name}}"
|
||||
},
|
||||
"ACTION_MESSAGE$READ": {
|
||||
"en": "Reading <path>{{action.payload.args.path}}</path>",
|
||||
"zh-CN": "读取 <path>{{action.payload.args.path}}</path>",
|
||||
@ -5415,6 +5835,21 @@
|
||||
"es": "Navegación completada",
|
||||
"tr": "Gezinme tamamlandı"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$MCP": {
|
||||
"en": "MCP Tool Result: {{action.payload.args.name}}",
|
||||
"zh-CN": "MCP 工具结果: {{action.payload.args.name}}",
|
||||
"zh-TW": "MCP 工具結果: {{action.payload.args.name}}",
|
||||
"ko-KR": "MCP 도구 결과: {{action.payload.args.name}}",
|
||||
"ja": "MCP ツール結果: {{action.payload.args.name}}",
|
||||
"no": "MCP verktøyresultat: {{action.payload.args.name}}",
|
||||
"ar": "نتيجة أداة MCP: {{action.payload.args.name}}",
|
||||
"de": "MCP-Tool-Ergebnis: {{action.payload.args.name}}",
|
||||
"fr": "Résultat de l'outil MCP: {{action.payload.args.name}}",
|
||||
"it": "Risultato dello strumento MCP: {{action.payload.args.name}}",
|
||||
"pt": "Resultado da ferramenta MCP: {{action.payload.args.name}}",
|
||||
"es": "Resultado de la herramienta MCP: {{action.payload.args.name}}",
|
||||
"tr": "MCP Aracı Sonucu: {{action.payload.args.name}}"
|
||||
},
|
||||
"OBSERVATION_MESSAGE$RECALL": {
|
||||
"en": "Microagent Activated",
|
||||
"ja": "マイクロエージェントが有効化されました",
|
||||
|
||||
6
frontend/src/react-app-env.d.ts
vendored
6
frontend/src/react-app-env.d.ts
vendored
@ -1 +1,7 @@
|
||||
/// <reference types="react-scripts" />
|
||||
|
||||
interface Window {
|
||||
posthog?: {
|
||||
capture: (event: string, properties?: Record<string, unknown>) => void;
|
||||
};
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ export default [
|
||||
route("accept-tos", "routes/accept-tos.tsx"),
|
||||
route("settings", "routes/settings.tsx", [
|
||||
index("routes/llm-settings.tsx"),
|
||||
route("mcp", "routes/mcp-settings.tsx"),
|
||||
route("git", "routes/git-settings.tsx"),
|
||||
route("app", "routes/app-settings.tsx"),
|
||||
route("billing", "routes/billing.tsx"),
|
||||
|
||||
83
frontend/src/routes/mcp-settings.tsx
Normal file
83
frontend/src/routes/mcp-settings.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import posthog from "posthog-js";
|
||||
import { useSettings } from "#/hooks/query/use-settings";
|
||||
import { useSaveSettings } from "#/hooks/mutation/use-save-settings";
|
||||
import { MCPConfig } from "#/types/settings";
|
||||
import { MCPConfigEditor } from "#/components/features/settings/mcp-settings/mcp-config-editor";
|
||||
import { BrandButton } from "#/components/features/settings/brand-button";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import {
|
||||
displayErrorToast,
|
||||
displaySuccessToast,
|
||||
} from "#/utils/custom-toast-handlers";
|
||||
import { retrieveAxiosErrorMessage } from "#/utils/retrieve-axios-error-message";
|
||||
|
||||
function MCPSettingsScreen() {
|
||||
const { t } = useTranslation();
|
||||
const { data: settings, isLoading } = useSettings();
|
||||
const { mutate: saveSettings, isPending } = useSaveSettings();
|
||||
|
||||
const [mcpConfig, setMcpConfig] = useState<MCPConfig | undefined>(
|
||||
settings?.MCP_CONFIG,
|
||||
);
|
||||
const [isDirty, setIsDirty] = useState(false);
|
||||
|
||||
const handleConfigChange = (config: MCPConfig) => {
|
||||
setMcpConfig(config);
|
||||
setIsDirty(true);
|
||||
};
|
||||
|
||||
const formAction = () => {
|
||||
if (!settings) return;
|
||||
|
||||
saveSettings(
|
||||
{ MCP_CONFIG: mcpConfig },
|
||||
{
|
||||
onSuccess: () => {
|
||||
displaySuccessToast(t(I18nKey.SETTINGS$SAVED));
|
||||
posthog.capture("settings_saved", {
|
||||
HAS_MCP_CONFIG: mcpConfig ? "YES" : "NO",
|
||||
MCP_SSE_SERVERS_COUNT: mcpConfig?.sse_servers?.length || 0,
|
||||
MCP_STDIO_SERVERS_COUNT: mcpConfig?.stdio_servers?.length || 0,
|
||||
});
|
||||
setIsDirty(false);
|
||||
},
|
||||
onError: (error) => {
|
||||
const errorMessage = retrieveAxiosErrorMessage(error);
|
||||
displayErrorToast(errorMessage || t(I18nKey.ERROR$GENERIC));
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <div className="p-9">{t(I18nKey.HOME$LOADING)}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
data-testid="mcp-settings-screen"
|
||||
action={formAction}
|
||||
className="flex flex-col h-full justify-between"
|
||||
>
|
||||
<div className="p-9 flex flex-col gap-12">
|
||||
<MCPConfigEditor mcpConfig={mcpConfig} onChange={handleConfigChange} />
|
||||
</div>
|
||||
|
||||
<div className="flex gap-6 p-6 justify-end border-t border-t-tertiary">
|
||||
<BrandButton
|
||||
testId="submit-button"
|
||||
type="submit"
|
||||
variant="primary"
|
||||
isDisabled={!isDirty || isPending}
|
||||
>
|
||||
{!isPending && t(I18nKey.SETTINGS$SAVE_CHANGES)}
|
||||
{isPending && t(I18nKey.SETTINGS$SAVING)}
|
||||
</BrandButton>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
export default MCPSettingsScreen;
|
||||
@ -23,6 +23,7 @@ function SettingsScreen() {
|
||||
|
||||
const ossNavItems = [
|
||||
{ to: "/settings", text: t("SETTINGS$NAV_LLM") },
|
||||
{ to: "/settings/mcp", text: t("SETTINGS$NAV_MCP") },
|
||||
{ to: "/settings/git", text: t("SETTINGS$NAV_GIT") },
|
||||
{ to: "/settings/app", text: t("SETTINGS$NAV_APPLICATION") },
|
||||
];
|
||||
|
||||
@ -53,6 +53,7 @@ export function handleObservationMessage(message: ObservationMessage) {
|
||||
case ObservationType.NULL:
|
||||
case ObservationType.RECALL:
|
||||
case ObservationType.ERROR:
|
||||
case ObservationType.MCP:
|
||||
break; // We don't display the default message for these observations
|
||||
default:
|
||||
store.dispatch(addAssistantMessage(message.message));
|
||||
@ -248,6 +249,14 @@ export function handleObservationMessage(message: ObservationMessage) {
|
||||
}),
|
||||
);
|
||||
break;
|
||||
case "mcp":
|
||||
store.dispatch(
|
||||
addAssistantObservation({
|
||||
...baseObservation,
|
||||
observation: "mcp" as const,
|
||||
}),
|
||||
);
|
||||
break;
|
||||
default:
|
||||
// For any unhandled observation types, just ignore them
|
||||
break;
|
||||
|
||||
@ -17,6 +17,10 @@ export const DEFAULT_SETTINGS: Settings = {
|
||||
USER_CONSENTS_TO_ANALYTICS: false,
|
||||
ENABLE_PROACTIVE_CONVERSATION_STARTERS: false,
|
||||
IS_NEW_USER: true,
|
||||
MCP_CONFIG: {
|
||||
sse_servers: [],
|
||||
stdio_servers: [],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -34,6 +34,8 @@ const HANDLED_ACTIONS: OpenHandsEventType[] = [
|
||||
"recall",
|
||||
"think",
|
||||
"system",
|
||||
"call_tool_mcp",
|
||||
"mcp",
|
||||
];
|
||||
|
||||
function getRiskText(risk: ActionSecurityRisk) {
|
||||
@ -140,6 +142,16 @@ export const chatSlice = createSlice({
|
||||
} else if (actionID === "recall") {
|
||||
// skip recall actions
|
||||
return;
|
||||
} else if (actionID === "call_tool_mcp") {
|
||||
// Format MCP action with name and arguments
|
||||
const name = action.payload.args.name || "";
|
||||
const args = action.payload.args.arguments || {};
|
||||
text = `**MCP Tool Call:** ${name}\n\n`;
|
||||
// Include thought if available
|
||||
if (action.payload.args.thought) {
|
||||
text += `\n\n**Thought:**\n${action.payload.args.thought}`;
|
||||
}
|
||||
text += `\n\n**Arguments:**\n\`\`\`json\n${JSON.stringify(args, null, 2)}\n\`\`\``;
|
||||
}
|
||||
if (actionID === "run" || actionID === "run_ipython") {
|
||||
if (
|
||||
@ -304,6 +316,19 @@ export const chatSlice = createSlice({
|
||||
content = `${content.slice(0, MAX_CONTENT_LENGTH)}...(truncated)`;
|
||||
}
|
||||
causeMessage.content = content;
|
||||
} else if (observationID === "mcp") {
|
||||
// For MCP observations, we want to show the content as formatted output
|
||||
// similar to how run/run_ipython actions are handled
|
||||
let { content } = observation.payload;
|
||||
if (content.length > MAX_CONTENT_LENGTH) {
|
||||
content = `${content.slice(0, MAX_CONTENT_LENGTH)}...`;
|
||||
}
|
||||
content = `${causeMessage.content}\n\n**Output:**\n\`\`\`\n${content.trim() || "[MCP Tool finished execution with no output]"}\n\`\`\``;
|
||||
causeMessage.content = content; // Observation content includes the action
|
||||
// Set success based on whether there's an error message
|
||||
causeMessage.success = !observation.payload.content
|
||||
.toLowerCase()
|
||||
.includes("error:");
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@ -41,6 +41,9 @@ enum ActionType {
|
||||
|
||||
// Changes the state of the agent, e.g. to paused or running
|
||||
CHANGE_AGENT_STATE = "change_agent_state",
|
||||
|
||||
// Interact with the MCP server.
|
||||
MCP = "call_tool_mcp",
|
||||
}
|
||||
|
||||
export default ActionType;
|
||||
|
||||
@ -152,6 +152,15 @@ export interface RecallAction extends OpenHandsActionEvent<"recall"> {
|
||||
};
|
||||
}
|
||||
|
||||
export interface MCPAction extends OpenHandsActionEvent<"call_tool_mcp"> {
|
||||
source: "agent";
|
||||
args: {
|
||||
name: string;
|
||||
arguments: Record<string, unknown>;
|
||||
thought?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type OpenHandsAction =
|
||||
| UserMessageAction
|
||||
| AssistantMessageAction
|
||||
@ -167,4 +176,5 @@ export type OpenHandsAction =
|
||||
| FileEditAction
|
||||
| FileWriteAction
|
||||
| RejectAction
|
||||
| RecallAction;
|
||||
| RecallAction
|
||||
| MCPAction;
|
||||
|
||||
@ -14,7 +14,9 @@ export type OpenHandsEventType =
|
||||
| "think"
|
||||
| "finish"
|
||||
| "error"
|
||||
| "recall";
|
||||
| "recall"
|
||||
| "mcp"
|
||||
| "call_tool_mcp";
|
||||
|
||||
interface OpenHandsBaseEvent {
|
||||
id: number;
|
||||
|
||||
@ -129,6 +129,13 @@ export interface RecallObservation extends OpenHandsObservationEvent<"recall"> {
|
||||
};
|
||||
}
|
||||
|
||||
export interface MCPObservation extends OpenHandsObservationEvent<"mcp"> {
|
||||
source: "agent";
|
||||
extras: {
|
||||
// Add any specific fields for MCP observations
|
||||
};
|
||||
}
|
||||
|
||||
export type OpenHandsObservation =
|
||||
| AgentStateChangeObservation
|
||||
| AgentThinkObservation
|
||||
@ -141,4 +148,5 @@ export type OpenHandsObservation =
|
||||
| ReadObservation
|
||||
| EditObservation
|
||||
| ErrorObservation
|
||||
| RecallObservation;
|
||||
| RecallObservation
|
||||
| MCPObservation;
|
||||
|
||||
@ -32,6 +32,9 @@ enum ObservationType {
|
||||
// An observation that shows agent's context extension
|
||||
RECALL = "recall",
|
||||
|
||||
// A MCP tool call observation
|
||||
MCP = "mcp",
|
||||
|
||||
// An error observation
|
||||
ERROR = "error",
|
||||
|
||||
|
||||
@ -9,6 +9,23 @@ export type ProviderToken = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export type MCPSSEServer = {
|
||||
url: string;
|
||||
api_key?: string;
|
||||
};
|
||||
|
||||
export type MCPStdioServer = {
|
||||
name: string;
|
||||
command: string;
|
||||
args?: string[];
|
||||
env?: Record<string, string>;
|
||||
};
|
||||
|
||||
export type MCPConfig = {
|
||||
sse_servers: (string | MCPSSEServer)[];
|
||||
stdio_servers: MCPStdioServer[];
|
||||
};
|
||||
|
||||
export type Settings = {
|
||||
LLM_MODEL: string;
|
||||
LLM_BASE_URL: string;
|
||||
@ -24,6 +41,7 @@ export type Settings = {
|
||||
ENABLE_PROACTIVE_CONVERSATION_STARTERS: boolean;
|
||||
USER_CONSENTS_TO_ANALYTICS: boolean | null;
|
||||
IS_NEW_USER?: boolean;
|
||||
MCP_CONFIG?: MCPConfig;
|
||||
};
|
||||
|
||||
export type ApiSettings = {
|
||||
@ -41,13 +59,19 @@ export type ApiSettings = {
|
||||
enable_proactive_conversation_starters: boolean;
|
||||
user_consents_to_analytics: boolean | null;
|
||||
provider_tokens_set: Partial<Record<Provider, string | null>>;
|
||||
mcp_config?: {
|
||||
sse_servers: (string | MCPSSEServer)[];
|
||||
stdio_servers: MCPStdioServer[];
|
||||
};
|
||||
};
|
||||
|
||||
export type PostSettings = Settings & {
|
||||
user_consents_to_analytics: boolean | null;
|
||||
llm_api_key?: string | null;
|
||||
mcp_config?: MCPConfig;
|
||||
};
|
||||
|
||||
export type PostApiSettings = ApiSettings & {
|
||||
user_consents_to_analytics: boolean | null;
|
||||
mcp_config?: MCPConfig;
|
||||
};
|
||||
|
||||
@ -376,8 +376,8 @@ class ActionExecutionClient(Runtime):
|
||||
)
|
||||
)
|
||||
self.log(
|
||||
'debug',
|
||||
f'Updated MCP config by adding runtime as another server: {updated_mcp_config}',
|
||||
'info',
|
||||
f'Updated MCP config: {updated_mcp_config.sse_servers}',
|
||||
)
|
||||
return updated_mcp_config
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ from logging import LoggerAdapter
|
||||
import socketio
|
||||
|
||||
from openhands.controller.agent import Agent
|
||||
from openhands.core.config import AppConfig
|
||||
from openhands.core.config import AppConfig, MCPConfig
|
||||
from openhands.core.config.condenser_config import (
|
||||
BrowserOutputCondenserConfig,
|
||||
CondenserPipelineConfig,
|
||||
@ -114,6 +114,7 @@ class Session:
|
||||
or settings.sandbox_runtime_container_image
|
||||
else self.config.sandbox.runtime_container_image
|
||||
)
|
||||
self.config.mcp = settings.mcp_config or MCPConfig()
|
||||
max_iterations = settings.max_iterations or self.config.max_iterations
|
||||
|
||||
# This is a shallow copy of the default LLM config, so changes here will
|
||||
|
||||
@ -5,6 +5,7 @@ from pydantic import (
|
||||
SecretStr,
|
||||
)
|
||||
|
||||
from openhands.core.config.mcp_config import MCPConfig
|
||||
from openhands.integrations.provider import ProviderToken
|
||||
from openhands.integrations.service_types import ProviderType
|
||||
from openhands.storage.data_models.settings import Settings
|
||||
@ -15,6 +16,7 @@ class POSTProviderModel(BaseModel):
|
||||
Settings for POST requests
|
||||
"""
|
||||
|
||||
mcp_config: MCPConfig | None = None
|
||||
provider_tokens: dict[ProviderType, ProviderToken] = {}
|
||||
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ from pydantic import (
|
||||
from pydantic.json import pydantic_encoder
|
||||
|
||||
from openhands.core.config.llm_config import LLMConfig
|
||||
from openhands.core.config.mcp_config import MCPConfig
|
||||
from openhands.core.config.utils import load_app_config
|
||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||
|
||||
@ -37,6 +38,7 @@ class Settings(BaseModel):
|
||||
user_consents_to_analytics: bool | None = None
|
||||
sandbox_base_container_image: str | None = None
|
||||
sandbox_runtime_container_image: str | None = None
|
||||
mcp_config: MCPConfig | None = None
|
||||
|
||||
model_config = {
|
||||
'validate_assignment': True,
|
||||
@ -105,6 +107,12 @@ class Settings(BaseModel):
|
||||
# If no api key has been set, we take this to mean that there is no reasonable default
|
||||
return None
|
||||
security = app_config.security
|
||||
|
||||
# Get MCP config if available
|
||||
mcp_config = None
|
||||
if hasattr(app_config, 'mcp'):
|
||||
mcp_config = app_config.mcp
|
||||
|
||||
settings = Settings(
|
||||
language='en',
|
||||
agent=app_config.default_agent,
|
||||
@ -115,5 +123,6 @@ class Settings(BaseModel):
|
||||
llm_api_key=llm_config.api_key,
|
||||
llm_base_url=llm_config.base_url,
|
||||
remote_runtime_resource_factor=app_config.sandbox.remote_runtime_resource_factor,
|
||||
mcp_config=mcp_config,
|
||||
)
|
||||
return settings
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user