mirror of
https://github.com/All-Hands-AI/OpenHands.git
synced 2026-01-09 14:57:59 -05: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:
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.
|
# Only build nikolaik on PRs, otherwise build both nikolaik and ubuntu.
|
||||||
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
|
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
|
||||||
json=$(jq -n -c '[
|
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
|
else
|
||||||
json=$(jq -n -c '[
|
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)}
|
{t(I18nKey.SETTINGS$SEE_ADVANCED_SETTINGS)}
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{aiConfigOptions.isLoading && (
|
{aiConfigOptions.isLoading && (
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<LoadingSpinner size="small" />
|
<LoadingSpinner size="small" />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import posthog from "posthog-js";
|
||||||
import { DEFAULT_SETTINGS } from "#/services/settings";
|
import { DEFAULT_SETTINGS } from "#/services/settings";
|
||||||
import OpenHands from "#/api/open-hands";
|
import OpenHands from "#/api/open-hands";
|
||||||
import { PostSettings, PostApiSettings } from "#/types/settings";
|
import { PostSettings, PostApiSettings } from "#/types/settings";
|
||||||
@@ -20,6 +21,8 @@ const saveSettingsMutationFn = async (settings: Partial<PostSettings>) => {
|
|||||||
enable_default_condenser: settings.ENABLE_DEFAULT_CONDENSER,
|
enable_default_condenser: settings.ENABLE_DEFAULT_CONDENSER,
|
||||||
enable_sound_notifications: settings.ENABLE_SOUND_NOTIFICATIONS,
|
enable_sound_notifications: settings.ENABLE_SOUND_NOTIFICATIONS,
|
||||||
user_consents_to_analytics: settings.user_consents_to_analytics,
|
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:
|
enable_proactive_conversation_starters:
|
||||||
settings.ENABLE_PROACTIVE_CONVERSATION_STARTERS,
|
settings.ENABLE_PROACTIVE_CONVERSATION_STARTERS,
|
||||||
};
|
};
|
||||||
@@ -34,6 +37,25 @@ export const useSaveSettings = () => {
|
|||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async (settings: Partial<PostSettings>) => {
|
mutationFn: async (settings: Partial<PostSettings>) => {
|
||||||
const newSettings = { ...currentSettings, ...settings };
|
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);
|
await saveSettingsMutationFn(newSettings);
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const getSettingsQueryFn = async (): Promise<Settings> => {
|
|||||||
ENABLE_PROACTIVE_CONVERSATION_STARTERS:
|
ENABLE_PROACTIVE_CONVERSATION_STARTERS:
|
||||||
apiSettings.enable_proactive_conversation_starters,
|
apiSettings.enable_proactive_conversation_starters,
|
||||||
USER_CONSENTS_TO_ANALYTICS: apiSettings.user_consents_to_analytics,
|
USER_CONSENTS_TO_ANALYTICS: apiSettings.user_consents_to_analytics,
|
||||||
|
|
||||||
|
MCP_CONFIG: apiSettings.mcp_config,
|
||||||
IS_NEW_USER: false,
|
IS_NEW_USER: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,32 @@
|
|||||||
// this file generate by script, don't modify it manually!!!
|
// this file generate by script, don't modify it manually!!!
|
||||||
export enum I18nKey {
|
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$CONNECT_PROVIDER_MESSAGE = "HOME$CONNECT_PROVIDER_MESSAGE",
|
||||||
HOME$LETS_START_BUILDING = "HOME$LETS_START_BUILDING",
|
HOME$LETS_START_BUILDING = "HOME$LETS_START_BUILDING",
|
||||||
HOME$OPENHANDS_DESCRIPTION = "HOME$OPENHANDS_DESCRIPTION",
|
HOME$OPENHANDS_DESCRIPTION = "HOME$OPENHANDS_DESCRIPTION",
|
||||||
@@ -366,6 +393,7 @@ export enum I18nKey {
|
|||||||
FILE_EXPLORER$UPLOAD = "FILE_EXPLORER$UPLOAD",
|
FILE_EXPLORER$UPLOAD = "FILE_EXPLORER$UPLOAD",
|
||||||
ACTION_MESSAGE$RUN = "ACTION_MESSAGE$RUN",
|
ACTION_MESSAGE$RUN = "ACTION_MESSAGE$RUN",
|
||||||
ACTION_MESSAGE$RUN_IPYTHON = "ACTION_MESSAGE$RUN_IPYTHON",
|
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$READ = "ACTION_MESSAGE$READ",
|
||||||
ACTION_MESSAGE$EDIT = "ACTION_MESSAGE$EDIT",
|
ACTION_MESSAGE$EDIT = "ACTION_MESSAGE$EDIT",
|
||||||
ACTION_MESSAGE$WRITE = "ACTION_MESSAGE$WRITE",
|
ACTION_MESSAGE$WRITE = "ACTION_MESSAGE$WRITE",
|
||||||
@@ -379,6 +407,7 @@ export enum I18nKey {
|
|||||||
OBSERVATION_MESSAGE$EDIT = "OBSERVATION_MESSAGE$EDIT",
|
OBSERVATION_MESSAGE$EDIT = "OBSERVATION_MESSAGE$EDIT",
|
||||||
OBSERVATION_MESSAGE$WRITE = "OBSERVATION_MESSAGE$WRITE",
|
OBSERVATION_MESSAGE$WRITE = "OBSERVATION_MESSAGE$WRITE",
|
||||||
OBSERVATION_MESSAGE$BROWSE = "OBSERVATION_MESSAGE$BROWSE",
|
OBSERVATION_MESSAGE$BROWSE = "OBSERVATION_MESSAGE$BROWSE",
|
||||||
|
OBSERVATION_MESSAGE$MCP = "OBSERVATION_MESSAGE$MCP",
|
||||||
OBSERVATION_MESSAGE$RECALL = "OBSERVATION_MESSAGE$RECALL",
|
OBSERVATION_MESSAGE$RECALL = "OBSERVATION_MESSAGE$RECALL",
|
||||||
EXPANDABLE_MESSAGE$SHOW_DETAILS = "EXPANDABLE_MESSAGE$SHOW_DETAILS",
|
EXPANDABLE_MESSAGE$SHOW_DETAILS = "EXPANDABLE_MESSAGE$SHOW_DETAILS",
|
||||||
EXPANDABLE_MESSAGE$HIDE_DETAILS = "EXPANDABLE_MESSAGE$HIDE_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": {
|
"HOME$CONNECT_PROVIDER_MESSAGE": {
|
||||||
"en": "To get started with suggested tasks, please connect your GitHub or GitLab account.",
|
"en": "To get started with suggested tasks, please connect your GitHub or GitLab account.",
|
||||||
"ja": "提案されたタスクを始めるには、GitHubまたはGitLabアカウントを接続してください。",
|
"ja": "提案されたタスクを始めるには、GitHubまたはGitLabアカウントを接続してください。",
|
||||||
@@ -5220,6 +5625,21 @@
|
|||||||
"es": "Ejecutando un comando Python",
|
"es": "Ejecutando un comando Python",
|
||||||
"tr": "Python komutu çalıştırılıyor"
|
"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": {
|
"ACTION_MESSAGE$READ": {
|
||||||
"en": "Reading <path>{{action.payload.args.path}}</path>",
|
"en": "Reading <path>{{action.payload.args.path}}</path>",
|
||||||
"zh-CN": "读取 <path>{{action.payload.args.path}}</path>",
|
"zh-CN": "读取 <path>{{action.payload.args.path}}</path>",
|
||||||
@@ -5415,6 +5835,21 @@
|
|||||||
"es": "Navegación completada",
|
"es": "Navegación completada",
|
||||||
"tr": "Gezinme tamamlandı"
|
"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": {
|
"OBSERVATION_MESSAGE$RECALL": {
|
||||||
"en": "Microagent Activated",
|
"en": "Microagent Activated",
|
||||||
"ja": "マイクロエージェントが有効化されました",
|
"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" />
|
/// <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("accept-tos", "routes/accept-tos.tsx"),
|
||||||
route("settings", "routes/settings.tsx", [
|
route("settings", "routes/settings.tsx", [
|
||||||
index("routes/llm-settings.tsx"),
|
index("routes/llm-settings.tsx"),
|
||||||
|
route("mcp", "routes/mcp-settings.tsx"),
|
||||||
route("git", "routes/git-settings.tsx"),
|
route("git", "routes/git-settings.tsx"),
|
||||||
route("app", "routes/app-settings.tsx"),
|
route("app", "routes/app-settings.tsx"),
|
||||||
route("billing", "routes/billing.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 = [
|
const ossNavItems = [
|
||||||
{ to: "/settings", text: t("SETTINGS$NAV_LLM") },
|
{ 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/git", text: t("SETTINGS$NAV_GIT") },
|
||||||
{ to: "/settings/app", text: t("SETTINGS$NAV_APPLICATION") },
|
{ to: "/settings/app", text: t("SETTINGS$NAV_APPLICATION") },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export function handleObservationMessage(message: ObservationMessage) {
|
|||||||
case ObservationType.NULL:
|
case ObservationType.NULL:
|
||||||
case ObservationType.RECALL:
|
case ObservationType.RECALL:
|
||||||
case ObservationType.ERROR:
|
case ObservationType.ERROR:
|
||||||
|
case ObservationType.MCP:
|
||||||
break; // We don't display the default message for these observations
|
break; // We don't display the default message for these observations
|
||||||
default:
|
default:
|
||||||
store.dispatch(addAssistantMessage(message.message));
|
store.dispatch(addAssistantMessage(message.message));
|
||||||
@@ -248,6 +249,14 @@ export function handleObservationMessage(message: ObservationMessage) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
case "mcp":
|
||||||
|
store.dispatch(
|
||||||
|
addAssistantObservation({
|
||||||
|
...baseObservation,
|
||||||
|
observation: "mcp" as const,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// For any unhandled observation types, just ignore them
|
// For any unhandled observation types, just ignore them
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ export const DEFAULT_SETTINGS: Settings = {
|
|||||||
USER_CONSENTS_TO_ANALYTICS: false,
|
USER_CONSENTS_TO_ANALYTICS: false,
|
||||||
ENABLE_PROACTIVE_CONVERSATION_STARTERS: false,
|
ENABLE_PROACTIVE_CONVERSATION_STARTERS: false,
|
||||||
IS_NEW_USER: true,
|
IS_NEW_USER: true,
|
||||||
|
MCP_CONFIG: {
|
||||||
|
sse_servers: [],
|
||||||
|
stdio_servers: [],
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ const HANDLED_ACTIONS: OpenHandsEventType[] = [
|
|||||||
"recall",
|
"recall",
|
||||||
"think",
|
"think",
|
||||||
"system",
|
"system",
|
||||||
|
"call_tool_mcp",
|
||||||
|
"mcp",
|
||||||
];
|
];
|
||||||
|
|
||||||
function getRiskText(risk: ActionSecurityRisk) {
|
function getRiskText(risk: ActionSecurityRisk) {
|
||||||
@@ -140,6 +142,16 @@ export const chatSlice = createSlice({
|
|||||||
} else if (actionID === "recall") {
|
} else if (actionID === "recall") {
|
||||||
// skip recall actions
|
// skip recall actions
|
||||||
return;
|
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 (actionID === "run" || actionID === "run_ipython") {
|
||||||
if (
|
if (
|
||||||
@@ -304,6 +316,19 @@ export const chatSlice = createSlice({
|
|||||||
content = `${content.slice(0, MAX_CONTENT_LENGTH)}...(truncated)`;
|
content = `${content.slice(0, MAX_CONTENT_LENGTH)}...(truncated)`;
|
||||||
}
|
}
|
||||||
causeMessage.content = content;
|
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
|
// Changes the state of the agent, e.g. to paused or running
|
||||||
CHANGE_AGENT_STATE = "change_agent_state",
|
CHANGE_AGENT_STATE = "change_agent_state",
|
||||||
|
|
||||||
|
// Interact with the MCP server.
|
||||||
|
MCP = "call_tool_mcp",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ActionType;
|
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 =
|
export type OpenHandsAction =
|
||||||
| UserMessageAction
|
| UserMessageAction
|
||||||
| AssistantMessageAction
|
| AssistantMessageAction
|
||||||
@@ -167,4 +176,5 @@ export type OpenHandsAction =
|
|||||||
| FileEditAction
|
| FileEditAction
|
||||||
| FileWriteAction
|
| FileWriteAction
|
||||||
| RejectAction
|
| RejectAction
|
||||||
| RecallAction;
|
| RecallAction
|
||||||
|
| MCPAction;
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ export type OpenHandsEventType =
|
|||||||
| "think"
|
| "think"
|
||||||
| "finish"
|
| "finish"
|
||||||
| "error"
|
| "error"
|
||||||
| "recall";
|
| "recall"
|
||||||
|
| "mcp"
|
||||||
|
| "call_tool_mcp";
|
||||||
|
|
||||||
interface OpenHandsBaseEvent {
|
interface OpenHandsBaseEvent {
|
||||||
id: number;
|
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 =
|
export type OpenHandsObservation =
|
||||||
| AgentStateChangeObservation
|
| AgentStateChangeObservation
|
||||||
| AgentThinkObservation
|
| AgentThinkObservation
|
||||||
@@ -141,4 +148,5 @@ export type OpenHandsObservation =
|
|||||||
| ReadObservation
|
| ReadObservation
|
||||||
| EditObservation
|
| EditObservation
|
||||||
| ErrorObservation
|
| ErrorObservation
|
||||||
| RecallObservation;
|
| RecallObservation
|
||||||
|
| MCPObservation;
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ enum ObservationType {
|
|||||||
// An observation that shows agent's context extension
|
// An observation that shows agent's context extension
|
||||||
RECALL = "recall",
|
RECALL = "recall",
|
||||||
|
|
||||||
|
// A MCP tool call observation
|
||||||
|
MCP = "mcp",
|
||||||
|
|
||||||
// An error observation
|
// An error observation
|
||||||
ERROR = "error",
|
ERROR = "error",
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,23 @@ export type ProviderToken = {
|
|||||||
token: string;
|
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 = {
|
export type Settings = {
|
||||||
LLM_MODEL: string;
|
LLM_MODEL: string;
|
||||||
LLM_BASE_URL: string;
|
LLM_BASE_URL: string;
|
||||||
@@ -24,6 +41,7 @@ export type Settings = {
|
|||||||
ENABLE_PROACTIVE_CONVERSATION_STARTERS: boolean;
|
ENABLE_PROACTIVE_CONVERSATION_STARTERS: boolean;
|
||||||
USER_CONSENTS_TO_ANALYTICS: boolean | null;
|
USER_CONSENTS_TO_ANALYTICS: boolean | null;
|
||||||
IS_NEW_USER?: boolean;
|
IS_NEW_USER?: boolean;
|
||||||
|
MCP_CONFIG?: MCPConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiSettings = {
|
export type ApiSettings = {
|
||||||
@@ -41,13 +59,19 @@ export type ApiSettings = {
|
|||||||
enable_proactive_conversation_starters: boolean;
|
enable_proactive_conversation_starters: boolean;
|
||||||
user_consents_to_analytics: boolean | null;
|
user_consents_to_analytics: boolean | null;
|
||||||
provider_tokens_set: Partial<Record<Provider, string | null>>;
|
provider_tokens_set: Partial<Record<Provider, string | null>>;
|
||||||
|
mcp_config?: {
|
||||||
|
sse_servers: (string | MCPSSEServer)[];
|
||||||
|
stdio_servers: MCPStdioServer[];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PostSettings = Settings & {
|
export type PostSettings = Settings & {
|
||||||
user_consents_to_analytics: boolean | null;
|
user_consents_to_analytics: boolean | null;
|
||||||
llm_api_key?: string | null;
|
llm_api_key?: string | null;
|
||||||
|
mcp_config?: MCPConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PostApiSettings = ApiSettings & {
|
export type PostApiSettings = ApiSettings & {
|
||||||
user_consents_to_analytics: boolean | null;
|
user_consents_to_analytics: boolean | null;
|
||||||
|
mcp_config?: MCPConfig;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -376,8 +376,8 @@ class ActionExecutionClient(Runtime):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.log(
|
self.log(
|
||||||
'debug',
|
'info',
|
||||||
f'Updated MCP config by adding runtime as another server: {updated_mcp_config}',
|
f'Updated MCP config: {updated_mcp_config.sse_servers}',
|
||||||
)
|
)
|
||||||
return updated_mcp_config
|
return updated_mcp_config
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from logging import LoggerAdapter
|
|||||||
import socketio
|
import socketio
|
||||||
|
|
||||||
from openhands.controller.agent import Agent
|
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 (
|
from openhands.core.config.condenser_config import (
|
||||||
BrowserOutputCondenserConfig,
|
BrowserOutputCondenserConfig,
|
||||||
CondenserPipelineConfig,
|
CondenserPipelineConfig,
|
||||||
@@ -114,6 +114,7 @@ class Session:
|
|||||||
or settings.sandbox_runtime_container_image
|
or settings.sandbox_runtime_container_image
|
||||||
else self.config.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
|
max_iterations = settings.max_iterations or self.config.max_iterations
|
||||||
|
|
||||||
# This is a shallow copy of the default LLM config, so changes here will
|
# This is a shallow copy of the default LLM config, so changes here will
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ from pydantic import (
|
|||||||
SecretStr,
|
SecretStr,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from openhands.core.config.mcp_config import MCPConfig
|
||||||
from openhands.integrations.provider import ProviderToken
|
from openhands.integrations.provider import ProviderToken
|
||||||
from openhands.integrations.service_types import ProviderType
|
from openhands.integrations.service_types import ProviderType
|
||||||
from openhands.storage.data_models.settings import Settings
|
from openhands.storage.data_models.settings import Settings
|
||||||
@@ -15,6 +16,7 @@ class POSTProviderModel(BaseModel):
|
|||||||
Settings for POST requests
|
Settings for POST requests
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
mcp_config: MCPConfig | None = None
|
||||||
provider_tokens: dict[ProviderType, ProviderToken] = {}
|
provider_tokens: dict[ProviderType, ProviderToken] = {}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from pydantic import (
|
|||||||
from pydantic.json import pydantic_encoder
|
from pydantic.json import pydantic_encoder
|
||||||
|
|
||||||
from openhands.core.config.llm_config import LLMConfig
|
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.core.config.utils import load_app_config
|
||||||
from openhands.storage.data_models.user_secrets import UserSecrets
|
from openhands.storage.data_models.user_secrets import UserSecrets
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ class Settings(BaseModel):
|
|||||||
user_consents_to_analytics: bool | None = None
|
user_consents_to_analytics: bool | None = None
|
||||||
sandbox_base_container_image: str | None = None
|
sandbox_base_container_image: str | None = None
|
||||||
sandbox_runtime_container_image: str | None = None
|
sandbox_runtime_container_image: str | None = None
|
||||||
|
mcp_config: MCPConfig | None = None
|
||||||
|
|
||||||
model_config = {
|
model_config = {
|
||||||
'validate_assignment': True,
|
'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
|
# If no api key has been set, we take this to mean that there is no reasonable default
|
||||||
return None
|
return None
|
||||||
security = app_config.security
|
security = app_config.security
|
||||||
|
|
||||||
|
# Get MCP config if available
|
||||||
|
mcp_config = None
|
||||||
|
if hasattr(app_config, 'mcp'):
|
||||||
|
mcp_config = app_config.mcp
|
||||||
|
|
||||||
settings = Settings(
|
settings = Settings(
|
||||||
language='en',
|
language='en',
|
||||||
agent=app_config.default_agent,
|
agent=app_config.default_agent,
|
||||||
@@ -115,5 +123,6 @@ class Settings(BaseModel):
|
|||||||
llm_api_key=llm_config.api_key,
|
llm_api_key=llm_config.api_key,
|
||||||
llm_base_url=llm_config.base_url,
|
llm_base_url=llm_config.base_url,
|
||||||
remote_runtime_resource_factor=app_config.sandbox.remote_runtime_resource_factor,
|
remote_runtime_resource_factor=app_config.sandbox.remote_runtime_resource_factor,
|
||||||
|
mcp_config=mcp_config,
|
||||||
)
|
)
|
||||||
return settings
|
return settings
|
||||||
|
|||||||
Reference in New Issue
Block a user