mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for mcp_tools batch 3
This commit is contained in:
parent
c233d094cf
commit
8708a1e614
7 changed files with 394 additions and 113 deletions
|
|
@ -2,6 +2,7 @@ import React from "react";
|
|||
import { Form, Input, InputNumber, Select, Tooltip } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { Button, TextInput } from "@tremor/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { OAUTH_FLOW } from "./types";
|
||||
|
||||
interface OAuthFlowStatus {
|
||||
|
|
@ -38,15 +39,16 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
initialFlowType,
|
||||
docsUrl,
|
||||
}) => {
|
||||
const placeholderSuffix = isEditing ? " (leave blank to keep existing)" : "";
|
||||
const { t } = useTranslation();
|
||||
const placeholderSuffix = isEditing ? ` (${t("mcpTools.oAuthFormFields.leaveBlankToKeep")})` : "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
label={
|
||||
<FieldLabel
|
||||
label="OAuth Flow Type"
|
||||
tooltip="Choose how the proxy authenticates with this MCP server. M2M is for server-to-server communication using client credentials. Interactive (PKCE) is for user-facing flows that require browser-based authorization."
|
||||
label={t("mcpTools.oAuthFormFields.flowTypeLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.flowTypeTooltip")}
|
||||
/>
|
||||
}
|
||||
name="oauth_flow_type"
|
||||
|
|
@ -55,14 +57,14 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
<Select className="rounded-lg" size="large">
|
||||
<Select.Option value={OAUTH_FLOW.M2M}>
|
||||
<div>
|
||||
<span className="font-medium">Machine-to-Machine (M2M)</span>
|
||||
<span className="text-gray-400 text-xs ml-2">server-to-server, no user interaction</span>
|
||||
<span className="font-medium">{t("mcpTools.oAuthFormFields.m2mLabel")}</span>
|
||||
<span className="text-gray-400 text-xs ml-2">{t("mcpTools.oAuthFormFields.m2mDesc")}</span>
|
||||
</div>
|
||||
</Select.Option>
|
||||
<Select.Option value={OAUTH_FLOW.INTERACTIVE}>
|
||||
<div>
|
||||
<span className="font-medium">Interactive (PKCE)</span>
|
||||
<span className="text-gray-400 text-xs ml-2">browser-based user authorization</span>
|
||||
<span className="font-medium">{t("mcpTools.oAuthFormFields.interactiveLabel")}</span>
|
||||
<span className="text-gray-400 text-xs ml-2">{t("mcpTools.oAuthFormFields.interactiveDesc")}</span>
|
||||
</div>
|
||||
</Select.Option>
|
||||
</Select>
|
||||
|
|
@ -71,46 +73,65 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
{isM2M ? (
|
||||
<>
|
||||
<Form.Item
|
||||
label={<FieldLabel label="Client ID" tooltip="OAuth2 client ID for the client_credentials grant." />}
|
||||
label={
|
||||
<FieldLabel
|
||||
label={t("mcpTools.oAuthFormFields.clientIdLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.clientIdM2MTooltip")}
|
||||
/>
|
||||
}
|
||||
name={["credentials", "client_id"]}
|
||||
rules={[{ required: true, message: "Client ID is required for M2M OAuth" }]}
|
||||
rules={[{ required: true, message: t("mcpTools.oAuthFormFields.clientIdM2MRequired") }]}
|
||||
>
|
||||
<TextInput
|
||||
type="password"
|
||||
placeholder={`Enter OAuth client ID${placeholderSuffix}`}
|
||||
placeholder={`${t("mcpTools.oAuthFormFields.clientIdM2MPlaceholder")}${placeholderSuffix}`}
|
||||
className={fieldClassName}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<FieldLabel label="Client Secret" tooltip="OAuth2 client secret for the client_credentials grant." />
|
||||
<FieldLabel
|
||||
label={t("mcpTools.oAuthFormFields.clientSecretLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.clientSecretM2MTooltip")}
|
||||
/>
|
||||
}
|
||||
name={["credentials", "client_secret"]}
|
||||
rules={[{ required: true, message: "Client Secret is required for M2M OAuth" }]}
|
||||
rules={[{ required: true, message: t("mcpTools.oAuthFormFields.clientSecretM2MRequired") }]}
|
||||
>
|
||||
<TextInput
|
||||
type="password"
|
||||
placeholder={`Enter OAuth client secret${placeholderSuffix}`}
|
||||
placeholder={`${t("mcpTools.oAuthFormFields.clientSecretM2MPlaceholder")}${placeholderSuffix}`}
|
||||
className={fieldClassName}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={<FieldLabel label="Token URL" tooltip="Token endpoint URL for the client_credentials grant." />}
|
||||
label={
|
||||
<FieldLabel
|
||||
label={t("mcpTools.oAuthFormFields.tokenUrlLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.tokenUrlM2MTooltip")}
|
||||
/>
|
||||
}
|
||||
name="token_url"
|
||||
rules={[{ required: true, message: "Token URL is required for M2M OAuth" }]}
|
||||
rules={[{ required: true, message: t("mcpTools.oAuthFormFields.tokenUrlM2MRequired") }]}
|
||||
>
|
||||
<TextInput placeholder="https://auth.example.com/oauth/token" className={fieldClassName} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<FieldLabel
|
||||
label="Scopes (optional)"
|
||||
tooltip="Optional scopes to request with the client_credentials grant."
|
||||
label={t("mcpTools.oAuthFormFields.scopesOptionalLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.scopesM2MTooltip")}
|
||||
/>
|
||||
}
|
||||
name={["credentials", "scopes"]}
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[","]} placeholder="Add scopes" className="rounded-lg" size="large" />
|
||||
<Select
|
||||
mode="tags"
|
||||
tokenSeparators={[","]}
|
||||
placeholder={t("mcpTools.oAuthFormFields.scopesPlaceholder")}
|
||||
className="rounded-lg"
|
||||
size="large"
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
) : (
|
||||
|
|
@ -119,8 +140,8 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
label={
|
||||
<span className="flex items-center justify-between w-full">
|
||||
<FieldLabel
|
||||
label="Client ID (optional)"
|
||||
tooltip="Provide only if your MCP server cannot handle dynamic client registration."
|
||||
label={t("mcpTools.oAuthFormFields.clientIdOptionalLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.clientIdInteractiveTooltip")}
|
||||
/>
|
||||
{docsUrl && (
|
||||
<a
|
||||
|
|
@ -130,46 +151,56 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
className="text-xs text-blue-500 hover:text-blue-700 ml-2 font-normal"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
Create OAuth App →
|
||||
{t("mcpTools.oAuthFormFields.createOAuthAppLink")}
|
||||
</a>
|
||||
)}
|
||||
</span>
|
||||
}
|
||||
name={["credentials", "client_id"]}
|
||||
>
|
||||
<TextInput type="password" placeholder={`Enter client ID${placeholderSuffix}`} className={fieldClassName} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<FieldLabel
|
||||
label="Client Secret (optional)"
|
||||
tooltip="Provide only if your MCP server cannot handle dynamic client registration."
|
||||
/>
|
||||
}
|
||||
name={["credentials", "client_secret"]}
|
||||
>
|
||||
<TextInput
|
||||
type="password"
|
||||
placeholder={`Enter client secret${placeholderSuffix}`}
|
||||
placeholder={`${t("mcpTools.oAuthFormFields.clientIdInteractivePlaceholder")}${placeholderSuffix}`}
|
||||
className={fieldClassName}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<FieldLabel
|
||||
label="Scopes (optional)"
|
||||
tooltip="Optional scopes requested during token exchange. Separate multiple scopes with enter or commas."
|
||||
label={t("mcpTools.oAuthFormFields.clientSecretOptionalLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.clientSecretInteractiveTooltip")}
|
||||
/>
|
||||
}
|
||||
name={["credentials", "scopes"]}
|
||||
name={["credentials", "client_secret"]}
|
||||
>
|
||||
<Select mode="tags" tokenSeparators={[","]} placeholder="Add scopes" className="rounded-lg" size="large" />
|
||||
<TextInput
|
||||
type="password"
|
||||
placeholder={`${t("mcpTools.oAuthFormFields.clientSecretInteractivePlaceholder")}${placeholderSuffix}`}
|
||||
className={fieldClassName}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<FieldLabel
|
||||
label="Authorization URL (optional)"
|
||||
tooltip="Optional override for the authorization endpoint."
|
||||
label={t("mcpTools.oAuthFormFields.scopesOptionalLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.scopesInteractiveTooltip")}
|
||||
/>
|
||||
}
|
||||
name={["credentials", "scopes"]}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
tokenSeparators={[","]}
|
||||
placeholder={t("mcpTools.oAuthFormFields.scopesPlaceholder")}
|
||||
className="rounded-lg"
|
||||
size="large"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={
|
||||
<FieldLabel
|
||||
label={t("mcpTools.oAuthFormFields.authorizationUrlOptionalLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.authorizationUrlTooltip")}
|
||||
/>
|
||||
}
|
||||
name="authorization_url"
|
||||
|
|
@ -177,7 +208,12 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
<TextInput placeholder="https://example.com/oauth/authorize" className={fieldClassName} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={<FieldLabel label="Token URL (optional)" tooltip="Optional override for the token endpoint." />}
|
||||
label={
|
||||
<FieldLabel
|
||||
label={t("mcpTools.oAuthFormFields.tokenUrlOptionalLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.tokenUrlInteractiveTooltip")}
|
||||
/>
|
||||
}
|
||||
name="token_url"
|
||||
>
|
||||
<TextInput placeholder="https://example.com/oauth/token" className={fieldClassName} />
|
||||
|
|
@ -185,8 +221,8 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
<Form.Item
|
||||
label={
|
||||
<FieldLabel
|
||||
label="Registration URL (optional)"
|
||||
tooltip="Optional override for the dynamic client registration endpoint."
|
||||
label={t("mcpTools.oAuthFormFields.registrationUrlOptionalLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.registrationUrlTooltip")}
|
||||
/>
|
||||
}
|
||||
name="registration_url"
|
||||
|
|
@ -196,8 +232,8 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
<Form.Item
|
||||
label={
|
||||
<FieldLabel
|
||||
label="Token Validation Rules (optional)"
|
||||
tooltip='JSON object of key-value rules checked against the OAuth token response before storing. Supports dot-notation for nested fields (e.g. {"organization": "my-org", "team.id": "123"}). Tokens that fail validation are rejected with HTTP 403.'
|
||||
label={t("mcpTools.oAuthFormFields.tokenValidationRulesLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.tokenValidationRulesTooltip")}
|
||||
/>
|
||||
}
|
||||
name="token_validation_json"
|
||||
|
|
@ -209,7 +245,7 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
JSON.parse(value);
|
||||
return Promise.resolve();
|
||||
} catch {
|
||||
return Promise.reject(new Error("Must be valid JSON"));
|
||||
return Promise.reject(new Error(t("mcpTools.oAuthFormFields.mustBeValidJson")));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -224,35 +260,37 @@ const OAuthFormFields: React.FC<OAuthFormFieldsProps> = ({
|
|||
<Form.Item
|
||||
label={
|
||||
<FieldLabel
|
||||
label="Token Storage TTL (seconds, optional)"
|
||||
tooltip="How long to cache each user's OAuth access token in Redis before evicting it (regardless of the token's own expires_in). Leave blank to derive the TTL from the token's expires_in, or fall back to the 12-hour default."
|
||||
label={t("mcpTools.oAuthFormFields.tokenStorageTtlLabel")}
|
||||
tooltip={t("mcpTools.oAuthFormFields.tokenStorageTtlTooltip")}
|
||||
/>
|
||||
}
|
||||
name="token_storage_ttl_seconds"
|
||||
>
|
||||
<InputNumber min={1} placeholder="e.g. 3600" className="w-full rounded-lg" style={{ width: "100%" }} />
|
||||
<InputNumber
|
||||
min={1}
|
||||
placeholder={t("mcpTools.oAuthFormFields.tokenStorageTtlPlaceholder")}
|
||||
className="w-full rounded-lg"
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
</Form.Item>
|
||||
{oauthFlow && (
|
||||
<div className="rounded-lg border border-dashed border-gray-300 p-4 space-y-2">
|
||||
<p className="text-sm text-gray-600">
|
||||
Use OAuth to fetch a fresh access token and temporarily save it in the session as the authentication
|
||||
value.
|
||||
</p>
|
||||
<p className="text-sm text-gray-600">{t("mcpTools.oAuthFormFields.oauthFlowDescription")}</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={oauthFlow.startOAuthFlow}
|
||||
disabled={oauthFlow.status === "authorizing" || oauthFlow.status === "exchanging"}
|
||||
>
|
||||
{oauthFlow.status === "authorizing"
|
||||
? "Waiting for authorization..."
|
||||
? t("mcpTools.oAuthFormFields.waitingForAuthorization")
|
||||
: oauthFlow.status === "exchanging"
|
||||
? "Exchanging authorization code..."
|
||||
: "Authorize & Fetch Token"}
|
||||
? t("mcpTools.oAuthFormFields.exchangingCode")
|
||||
: t("mcpTools.oAuthFormFields.authorizeAndFetch")}
|
||||
</Button>
|
||||
{oauthFlow.error && <p className="text-sm text-red-500">{oauthFlow.error}</p>}
|
||||
{oauthFlow.status === "success" && oauthFlow.tokenResponse?.access_token && (
|
||||
<p className="text-sm text-green-600">
|
||||
Token fetched. Expires in {oauthFlow.tokenResponse.expires_in ?? "?"} seconds.
|
||||
{t("mcpTools.oAuthFormFields.tokenFetched", { seconds: oauthFlow.tokenResponse.expires_in ?? "?" })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import React, { useState } from "react";
|
|||
import { Form, Input, Tooltip } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { FormInstance } from "antd/es/form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AUTH_TYPE, OAUTH_FLOW } from "./types";
|
||||
import OpenAPIQuickPicker, { OpenAPIRegistryEntry, OpenAPIKeyTool } from "./OpenAPIQuickPicker";
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ const OpenAPIFormSection: React.FC<OpenAPIFormSectionProps> = ({
|
|||
onLogoUrlChange,
|
||||
onOAuthDocsUrlChange,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedPreset, setSelectedPreset] = useState<string | null>(null);
|
||||
|
||||
const handlePresetSelect = (entry: OpenAPIRegistryEntry) => {
|
||||
|
|
@ -66,14 +68,14 @@ const OpenAPIFormSection: React.FC<OpenAPIFormSectionProps> = ({
|
|||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
OpenAPI Spec URL
|
||||
<Tooltip title="URL to an OpenAPI specification (JSON or YAML). MCP tools will be automatically generated from the API endpoints defined in the spec.">
|
||||
{t("mcpTools.openAPIFormSection.specUrlLabel")}
|
||||
<Tooltip title={t("mcpTools.openAPIFormSection.specUrlTooltip")}>
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="spec_path"
|
||||
rules={[{ required: true, message: "Please enter an OpenAPI spec URL" }]}
|
||||
rules={[{ required: true, message: t("mcpTools.openAPIFormSection.specUrlRequired") }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="https://petstore3.swagger.io/api/v3/openapi.json"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { Spin } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { fetchOpenAPIRegistry } from "../networking";
|
||||
|
||||
export interface OpenAPIKeyTool {
|
||||
|
|
@ -29,6 +30,7 @@ interface OpenAPIQuickPickerProps {
|
|||
}
|
||||
|
||||
const OpenAPIQuickPicker: React.FC<OpenAPIQuickPickerProps> = ({ accessToken, selectedName, onSelect }) => {
|
||||
const { t } = useTranslation();
|
||||
const [apis, setApis] = useState<OpenAPIRegistryEntry[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [imgErrors, setImgErrors] = useState<Set<string>>(new Set());
|
||||
|
|
@ -49,7 +51,7 @@ const OpenAPIQuickPicker: React.FC<OpenAPIQuickPickerProps> = ({ accessToken, se
|
|||
if (loading) {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<span className="text-sm font-medium text-gray-700">Popular APIs</span>
|
||||
<span className="text-sm font-medium text-gray-700">{t("mcpTools.openAPIQuickPicker.popularApis")}</span>
|
||||
<div className="flex justify-center py-6">
|
||||
<Spin size="small" />
|
||||
</div>
|
||||
|
|
@ -61,7 +63,9 @@ const OpenAPIQuickPicker: React.FC<OpenAPIQuickPickerProps> = ({ accessToken, se
|
|||
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<span className="text-sm font-medium text-gray-700 block mb-2">Popular APIs</span>
|
||||
<span className="text-sm font-medium text-gray-700 block mb-2">
|
||||
{t("mcpTools.openAPIQuickPicker.popularApis")}
|
||||
</span>
|
||||
|
||||
<div className="grid grid-cols-5 gap-2">
|
||||
{apis.map((api) => {
|
||||
|
|
@ -98,9 +102,7 @@ const OpenAPIQuickPicker: React.FC<OpenAPIQuickPickerProps> = ({ accessToken, se
|
|||
})}
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-gray-400 mt-2">
|
||||
Select an API to pre-fill the spec URL and OAuth 2.0 settings, or enter your own spec URL below.
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 mt-2">{t("mcpTools.openAPIQuickPicker.selectHint")}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React from "react";
|
||||
import { Form, Input, Tooltip } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface StdioConfigurationProps {
|
||||
isVisible: boolean;
|
||||
|
|
@ -12,21 +13,23 @@ interface StdioConfigurationProps {
|
|||
}
|
||||
|
||||
const StdioConfiguration: React.FC<StdioConfigurationProps> = ({ isVisible, required = true }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
return (
|
||||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700 flex items-center">
|
||||
Stdio Configuration (JSON)
|
||||
<Tooltip title="Paste your stdio MCP server configuration in JSON format. You can use the full mcpServers structure from config.yaml or just the inner server configuration.">
|
||||
{t("mcpTools.stdioConfiguration.label")}
|
||||
<Tooltip title={t("mcpTools.stdioConfiguration.tooltip")}>
|
||||
<InfoCircleOutlined className="ml-2 text-blue-400 hover:text-blue-600 cursor-help" />
|
||||
</Tooltip>
|
||||
</span>
|
||||
}
|
||||
name="stdio_config"
|
||||
rules={[
|
||||
...(required ? [{ required: true, message: "Please enter stdio configuration" }] : []),
|
||||
...(required ? [{ required: true, message: t("mcpTools.stdioConfiguration.required") }] : []),
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!value) return Promise.resolve();
|
||||
|
|
@ -34,7 +37,7 @@ const StdioConfiguration: React.FC<StdioConfigurationProps> = ({ isVisible, requ
|
|||
JSON.parse(value);
|
||||
return Promise.resolve();
|
||||
} catch {
|
||||
return Promise.reject("Please enter valid JSON");
|
||||
return Promise.reject(t("mcpTools.stdioConfiguration.invalidJson"));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Button, TextInput } from "@tremor/react";
|
|||
import { MCPTool, InputSchema, InputSchemaProperty } from "./types";
|
||||
import { Form, Select, Tooltip } from "antd";
|
||||
import { InfoCircleOutlined } from "@ant-design/icons";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import NotificationsManager from "../molecules/notifications_manager";
|
||||
|
||||
const isPlainObject = (value: unknown): value is Record<string, any> =>
|
||||
|
|
@ -113,6 +114,7 @@ export function ToolTestPanel({
|
|||
error: Error | null;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const [viewMode, setViewMode] = React.useState<"formatted" | "json">("formatted");
|
||||
const [startTime, setStartTime] = React.useState<number | null>(null);
|
||||
|
|
@ -278,18 +280,18 @@ export function ToolTestPanel({
|
|||
const handleCopyResult = async () => {
|
||||
const success = await copyToClipboard(JSON.stringify(result, null, 2));
|
||||
if (success) {
|
||||
NotificationsManager.success("Result copied to clipboard");
|
||||
NotificationsManager.success(t("mcpTools.toolTestPanel.resultCopied"));
|
||||
} else {
|
||||
NotificationsManager.fromBackend("Failed to copy result");
|
||||
NotificationsManager.fromBackend(t("mcpTools.toolTestPanel.copyResultFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
const handleCopyToolName = async () => {
|
||||
const success = await copyToClipboard(tool.name);
|
||||
if (success) {
|
||||
NotificationsManager.success("Tool name copied to clipboard");
|
||||
NotificationsManager.success(t("mcpTools.toolTestPanel.toolNameCopied"));
|
||||
} else {
|
||||
NotificationsManager.fromBackend("Failed to copy tool name");
|
||||
NotificationsManager.fromBackend(t("mcpTools.toolTestPanel.copyToolNameFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -308,11 +310,11 @@ export function ToolTestPanel({
|
|||
)}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center space-x-2 mb-1">
|
||||
<h2 className="text-lg font-semibold text-gray-900">Test Tool:</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900">{t("mcpTools.toolTestPanel.testToolTitle")}</h2>
|
||||
<div
|
||||
className="group inline-flex items-center space-x-1 bg-slate-50 hover:bg-slate-100 px-3 py-1 rounded-md cursor-pointer transition-colors border border-slate-200"
|
||||
onClick={handleCopyToolName}
|
||||
title="Click to copy tool name"
|
||||
title={t("mcpTools.toolTestPanel.clickToCopyToolName")}
|
||||
>
|
||||
<span className="font-mono text-slate-700 font-medium text-sm">{tool.name}</span>
|
||||
<svg
|
||||
|
|
@ -331,7 +333,9 @@ export function ToolTestPanel({
|
|||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-600">{tool.description}</p>
|
||||
<p className="text-xs text-gray-500">Provider: {tool.mcp_info.server_name}</p>
|
||||
<p className="text-xs text-gray-500">
|
||||
{t("mcpTools.toolTestPanel.provider", { name: tool.mcp_info.server_name })}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button onClick={onClose} variant="light" size="sm" className="text-gray-500 hover:text-gray-700">
|
||||
|
|
@ -347,8 +351,8 @@ export function ToolTestPanel({
|
|||
<div className="bg-white border border-gray-200 rounded-lg">
|
||||
<div className="border-b border-gray-100 px-4 py-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-semibold text-gray-900">Input Parameters</h3>
|
||||
<Tooltip title="Configure the input parameters for this tool call">
|
||||
<h3 className="text-sm font-semibold text-gray-900">{t("mcpTools.toolTestPanel.inputParameters")}</h3>
|
||||
<Tooltip title={t("mcpTools.toolTestPanel.inputParametersTooltip")}>
|
||||
<InfoCircleOutlined className="text-gray-400 hover:text-gray-600" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
|
@ -361,15 +365,15 @@ export function ToolTestPanel({
|
|||
<Form.Item
|
||||
label={
|
||||
<span className="text-sm font-medium text-gray-700">
|
||||
Input <span className="text-red-500">*</span>
|
||||
{t("mcpTools.toolTestPanel.inputLabel")} <span className="text-red-500">*</span>
|
||||
</span>
|
||||
}
|
||||
name="input"
|
||||
rules={[{ required: true, message: "Please enter input for this tool" }]}
|
||||
rules={[{ required: true, message: t("mcpTools.toolTestPanel.inputRequired") }]}
|
||||
className="mb-3"
|
||||
>
|
||||
<TextInput
|
||||
placeholder="Enter input for this tool"
|
||||
placeholder={t("mcpTools.toolTestPanel.inputPlaceholder")}
|
||||
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
|
@ -377,8 +381,10 @@ export function ToolTestPanel({
|
|||
) : actualSchema.properties === undefined ? (
|
||||
<div className="text-center py-6 bg-gray-50 rounded-lg border border-gray-200">
|
||||
<div className="max-w-sm mx-auto">
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-1">No Parameters Required</h4>
|
||||
<p className="text-xs text-gray-500">This tool can be called without any input parameters.</p>
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-1">
|
||||
{t("mcpTools.toolTestPanel.noParametersTitle")}
|
||||
</h4>
|
||||
<p className="text-xs text-gray-500">{t("mcpTools.toolTestPanel.noParametersDesc")}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -404,7 +410,7 @@ export function ToolTestPanel({
|
|||
rules={[
|
||||
{
|
||||
required: actualSchema.required?.includes(key),
|
||||
message: `Please enter ${key}`,
|
||||
message: t("mcpTools.toolTestPanel.fieldRequired", { key }),
|
||||
},
|
||||
...(prop.type === "object" || prop.type === "array"
|
||||
? [
|
||||
|
|
@ -436,12 +442,12 @@ export function ToolTestPanel({
|
|||
return Promise.reject(
|
||||
new Error(
|
||||
prop.type === "object"
|
||||
? "Please enter a JSON object"
|
||||
: "Please enter a JSON array",
|
||||
? t("mcpTools.toolTestPanel.invalidJsonObject")
|
||||
: t("mcpTools.toolTestPanel.invalidJsonArray"),
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
return Promise.reject(new Error("Invalid JSON"));
|
||||
return Promise.reject(new Error(t("mcpTools.toolTestPanel.invalidJson")));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -455,7 +461,9 @@ export function ToolTestPanel({
|
|||
className="w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-colors"
|
||||
defaultValue={(initialValue as string) ?? ""}
|
||||
>
|
||||
{!actualSchema.required?.includes(key) && <option value="">Select {key}</option>}
|
||||
{!actualSchema.required?.includes(key) && (
|
||||
<option value="">{t("mcpTools.toolTestPanel.selectKey", { key })}</option>
|
||||
)}
|
||||
{prop.enum.map((value) => (
|
||||
<option key={value} value={value}>
|
||||
{value}
|
||||
|
|
@ -466,7 +474,7 @@ export function ToolTestPanel({
|
|||
|
||||
{prop.type === "string" && !prop.enum && (
|
||||
<TextInput
|
||||
placeholder={prop.description || `Enter ${key}`}
|
||||
placeholder={prop.description || t("mcpTools.toolTestPanel.enterKey", { key })}
|
||||
defaultValue={(initialValue as string) ?? ""}
|
||||
className="rounded-lg border-gray-300 focus:border-blue-500 focus:ring-blue-500"
|
||||
/>
|
||||
|
|
@ -476,7 +484,7 @@ export function ToolTestPanel({
|
|||
<input
|
||||
type="number"
|
||||
step={prop.type === "integer" ? 1 : "any"}
|
||||
placeholder={prop.description || `Enter ${key}`}
|
||||
placeholder={prop.description || t("mcpTools.toolTestPanel.enterKey", { key })}
|
||||
defaultValue={initialValue ?? 0}
|
||||
className="w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm transition-colors"
|
||||
/>
|
||||
|
|
@ -484,12 +492,12 @@ export function ToolTestPanel({
|
|||
|
||||
{prop.type === "boolean" && (
|
||||
<Select
|
||||
placeholder={`Select ${key}`}
|
||||
placeholder={t("mcpTools.toolTestPanel.selectKey", { key })}
|
||||
allowClear={!actualSchema.required?.includes(key)}
|
||||
className="w-full"
|
||||
>
|
||||
<Select.Option value={true}>True</Select.Option>
|
||||
<Select.Option value={false}>False</Select.Option>
|
||||
<Select.Option value={true}>{t("mcpTools.toolTestPanel.booleanTrue")}</Select.Option>
|
||||
<Select.Option value={false}>{t("mcpTools.toolTestPanel.booleanFalse")}</Select.Option>
|
||||
</Select>
|
||||
)}
|
||||
|
||||
|
|
@ -500,8 +508,8 @@ export function ToolTestPanel({
|
|||
placeholder={
|
||||
prop.description ||
|
||||
(prop.type === "object"
|
||||
? `Enter JSON object for ${key}`
|
||||
: `Enter JSON array for ${key}`)
|
||||
? t("mcpTools.toolTestPanel.jsonObjectPlaceholder", { key })
|
||||
: t("mcpTools.toolTestPanel.jsonArrayPlaceholder", { key }))
|
||||
}
|
||||
defaultValue={(initialValue as string) ?? (prop.type === "object" ? "{}" : "[]")}
|
||||
spellCheck={false}
|
||||
|
|
@ -509,7 +517,9 @@ export function ToolTestPanel({
|
|||
className="w-full px-3 py-2 border border-gray-300 rounded-lg shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-sm font-mono"
|
||||
/>
|
||||
<p className="text-xs text-gray-500">
|
||||
{prop.type === "object" ? "Provide a valid JSON object." : "Provide a valid JSON array."}
|
||||
{prop.type === "object"
|
||||
? t("mcpTools.toolTestPanel.provideJsonObject")
|
||||
: t("mcpTools.toolTestPanel.provideJsonArray")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -528,7 +538,11 @@ export function ToolTestPanel({
|
|||
className="w-full"
|
||||
loading={isLoading}
|
||||
>
|
||||
{isLoading ? "Calling Tool..." : result || error ? "Call Again" : "Call Tool"}
|
||||
{isLoading
|
||||
? t("mcpTools.toolTestPanel.callingTool")
|
||||
: result || error
|
||||
? t("mcpTools.toolTestPanel.callAgain")
|
||||
: t("mcpTools.toolTestPanel.callTool")}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
@ -538,7 +552,7 @@ export function ToolTestPanel({
|
|||
{/* Right Column - Tool Result */}
|
||||
<div className="bg-white border border-gray-200 rounded-lg">
|
||||
<div className="border-b border-gray-100 px-4 py-2">
|
||||
<h3 className="text-sm font-semibold text-gray-900">Tool Result</h3>
|
||||
<h3 className="text-sm font-semibold text-gray-900">{t("mcpTools.toolTestPanel.toolResult")}</h3>
|
||||
</div>
|
||||
|
||||
<div className="p-4">
|
||||
|
|
@ -561,10 +575,10 @@ export function ToolTestPanel({
|
|||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-1">Ready to Call Tool</h4>
|
||||
<p className="text-xs text-gray-500 leading-relaxed">
|
||||
Configure the input parameters and click "Call Tool" to see the results here.
|
||||
</p>
|
||||
<h4 className="text-sm font-medium text-gray-900 mb-1">
|
||||
{t("mcpTools.toolTestPanel.readyToCallTitle")}
|
||||
</h4>
|
||||
<p className="text-xs text-gray-500 leading-relaxed">{t("mcpTools.toolTestPanel.readyToCallDesc")}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
|
|
@ -582,7 +596,9 @@ export function ToolTestPanel({
|
|||
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
<h4 className="text-xs font-medium text-green-900">Tool executed successfully</h4>
|
||||
<h4 className="text-xs font-medium text-green-900">
|
||||
{t("mcpTools.toolTestPanel.executedSuccessfully")}
|
||||
</h4>
|
||||
{duration !== null && (
|
||||
<span className="text-xs text-green-600 ml-1">• {(duration / 1000).toFixed(2)}s</span>
|
||||
)}
|
||||
|
|
@ -598,7 +614,7 @@ export function ToolTestPanel({
|
|||
: "text-green-600 hover:text-green-800"
|
||||
}`}
|
||||
>
|
||||
Formatted
|
||||
{t("mcpTools.toolTestPanel.viewFormatted")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("json")}
|
||||
|
|
@ -608,14 +624,14 @@ export function ToolTestPanel({
|
|||
: "text-green-600 hover:text-green-800"
|
||||
}`}
|
||||
>
|
||||
JSON
|
||||
{t("mcpTools.toolTestPanel.viewJson")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleCopyResult}
|
||||
className="p-1 hover:bg-green-100 rounded text-green-700"
|
||||
title="Copy response"
|
||||
title={t("mcpTools.toolTestPanel.copyResponse")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -644,8 +660,8 @@ export function ToolTestPanel({
|
|||
<div className="animate-spin rounded-full h-8 w-8 border-2 border-gray-200"></div>
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-2 border-blue-600 border-t-transparent absolute top-0"></div>
|
||||
</div>
|
||||
<p className="text-sm font-medium mt-3">Calling tool...</p>
|
||||
<p className="text-xs text-gray-400 mt-1">Please wait while we process your request</p>
|
||||
<p className="text-sm font-medium mt-3">{t("mcpTools.toolTestPanel.callingToolProgress")}</p>
|
||||
<p className="text-xs text-gray-400 mt-1">{t("mcpTools.toolTestPanel.pleaseWait")}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
@ -664,7 +680,9 @@ export function ToolTestPanel({
|
|||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center space-x-2 mb-1">
|
||||
<h4 className="text-xs font-medium text-red-900">Tool Call Failed</h4>
|
||||
<h4 className="text-xs font-medium text-red-900">
|
||||
{t("mcpTools.toolTestPanel.toolCallFailed")}
|
||||
</h4>
|
||||
{duration !== null && (
|
||||
<span className="text-xs text-red-600">• {(duration / 1000).toFixed(2)}s</span>
|
||||
)}
|
||||
|
|
@ -691,7 +709,7 @@ export function ToolTestPanel({
|
|||
<div>
|
||||
<div className="bg-gray-50 px-3 py-1 border-b border-gray-200">
|
||||
<span className="text-xs font-medium text-gray-700 uppercase tracking-wide">
|
||||
Text Response
|
||||
{t("mcpTools.toolTestPanel.textResponse")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="p-3">
|
||||
|
|
@ -780,7 +798,7 @@ export function ToolTestPanel({
|
|||
<div>
|
||||
<div className="bg-gray-50 px-3 py-1 border-b border-gray-200">
|
||||
<span className="text-xs font-medium text-gray-700 uppercase tracking-wide">
|
||||
Image Response
|
||||
{t("mcpTools.toolTestPanel.imageResponse")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="p-3">
|
||||
|
|
@ -800,7 +818,7 @@ export function ToolTestPanel({
|
|||
<div>
|
||||
<div className="bg-gray-50 px-3 py-1 border-b border-gray-200">
|
||||
<span className="text-xs font-medium text-gray-700 uppercase tracking-wide">
|
||||
Embedded Resource
|
||||
{t("mcpTools.toolTestPanel.embeddedResource")}
|
||||
</span>
|
||||
</div>
|
||||
<div className="p-3">
|
||||
|
|
@ -822,7 +840,7 @@ export function ToolTestPanel({
|
|||
</div>
|
||||
<div className="flex-1">
|
||||
<p className="text-xs font-medium text-blue-900">
|
||||
Resource Type: {content.resource_type}
|
||||
{t("mcpTools.toolTestPanel.resourceType", { type: content.resource_type })}
|
||||
</p>
|
||||
{content.url && (
|
||||
<a
|
||||
|
|
@ -831,7 +849,7 @@ export function ToolTestPanel({
|
|||
rel="noopener noreferrer"
|
||||
className="inline-flex items-center text-xs text-blue-600 hover:text-blue-800 hover:underline mt-1 transition-colors"
|
||||
>
|
||||
View Resource
|
||||
{t("mcpTools.toolTestPanel.viewResource")}
|
||||
<svg className="ml-1 h-3 w-3" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z" />
|
||||
<path d="M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z" />
|
||||
|
|
|
|||
|
|
@ -3262,6 +3262,115 @@
|
|||
"howToolsetsWorkPath": "API Keys → Edit Key → MCP Servers",
|
||||
"howToolsetsWorkDesc2": ", then point your MCP client at the toolset URL. The client only sees the tools you picked.",
|
||||
"configLabel": "Claude Code / Cursor config"
|
||||
},
|
||||
"oAuthFormFields": {
|
||||
"leaveBlankToKeep": "leave blank to keep existing",
|
||||
"flowTypeLabel": "OAuth Flow Type",
|
||||
"flowTypeTooltip": "Choose how the proxy authenticates with this MCP server. M2M is for server-to-server communication using client credentials. Interactive (PKCE) is for user-facing flows that require browser-based authorization.",
|
||||
"m2mLabel": "Machine-to-Machine (M2M)",
|
||||
"m2mDesc": "server-to-server, no user interaction",
|
||||
"interactiveLabel": "Interactive (PKCE)",
|
||||
"interactiveDesc": "browser-based user authorization",
|
||||
"clientIdLabel": "Client ID",
|
||||
"clientIdM2MTooltip": "OAuth2 client ID for the client_credentials grant.",
|
||||
"clientIdM2MRequired": "Client ID is required for M2M OAuth",
|
||||
"clientIdM2MPlaceholder": "Enter OAuth client ID",
|
||||
"clientSecretLabel": "Client Secret",
|
||||
"clientSecretM2MTooltip": "OAuth2 client secret for the client_credentials grant.",
|
||||
"clientSecretM2MRequired": "Client Secret is required for M2M OAuth",
|
||||
"clientSecretM2MPlaceholder": "Enter OAuth client secret",
|
||||
"tokenUrlLabel": "Token URL",
|
||||
"tokenUrlM2MTooltip": "Token endpoint URL for the client_credentials grant.",
|
||||
"tokenUrlM2MRequired": "Token URL is required for M2M OAuth",
|
||||
"scopesOptionalLabel": "Scopes (optional)",
|
||||
"scopesM2MTooltip": "Optional scopes to request with the client_credentials grant.",
|
||||
"scopesInteractiveTooltip": "Optional scopes requested during token exchange. Separate multiple scopes with enter or commas.",
|
||||
"scopesPlaceholder": "Add scopes",
|
||||
"clientIdOptionalLabel": "Client ID (optional)",
|
||||
"clientIdInteractiveTooltip": "Provide only if your MCP server cannot handle dynamic client registration.",
|
||||
"clientIdInteractivePlaceholder": "Enter client ID",
|
||||
"clientSecretOptionalLabel": "Client Secret (optional)",
|
||||
"clientSecretInteractiveTooltip": "Provide only if your MCP server cannot handle dynamic client registration.",
|
||||
"clientSecretInteractivePlaceholder": "Enter client secret",
|
||||
"authorizationUrlOptionalLabel": "Authorization URL (optional)",
|
||||
"authorizationUrlTooltip": "Optional override for the authorization endpoint.",
|
||||
"tokenUrlOptionalLabel": "Token URL (optional)",
|
||||
"tokenUrlInteractiveTooltip": "Optional override for the token endpoint.",
|
||||
"registrationUrlOptionalLabel": "Registration URL (optional)",
|
||||
"registrationUrlTooltip": "Optional override for the dynamic client registration endpoint.",
|
||||
"tokenValidationRulesLabel": "Token Validation Rules (optional)",
|
||||
"tokenValidationRulesTooltip": "JSON object of key-value rules checked against the OAuth token response before storing. Supports dot-notation for nested fields (e.g. {\"organization\": \"my-org\", \"team.id\": \"123\"}). Tokens that fail validation are rejected with HTTP 403.",
|
||||
"mustBeValidJson": "Must be valid JSON",
|
||||
"tokenStorageTtlLabel": "Token Storage TTL (seconds, optional)",
|
||||
"tokenStorageTtlTooltip": "How long to cache each user's OAuth access token in Redis before evicting it (regardless of the token's own expires_in). Leave blank to derive the TTL from the token's expires_in, or fall back to the 12-hour default.",
|
||||
"tokenStorageTtlPlaceholder": "e.g. 3600",
|
||||
"createOAuthAppLink": "Create OAuth App",
|
||||
"oauthFlowDescription": "Use OAuth to fetch a fresh access token and temporarily save it in the session as the authentication value.",
|
||||
"waitingForAuthorization": "Waiting for authorization...",
|
||||
"exchangingCode": "Exchanging authorization code...",
|
||||
"authorizeAndFetch": "Authorize & Fetch Token",
|
||||
"tokenFetched": "Token fetched. Expires in {{seconds}} seconds."
|
||||
},
|
||||
"openAPIFormSection": {
|
||||
"specUrlLabel": "OpenAPI Spec URL",
|
||||
"specUrlTooltip": "URL to an OpenAPI specification (JSON or YAML). MCP tools will be automatically generated from the API endpoints defined in the spec.",
|
||||
"specUrlRequired": "Please enter an OpenAPI spec URL"
|
||||
},
|
||||
"openAPIQuickPicker": {
|
||||
"popularApis": "Popular APIs",
|
||||
"selectHint": "Select an API to pre-fill the spec URL and OAuth 2.0 settings, or enter your own spec URL below."
|
||||
},
|
||||
"stdioConfiguration": {
|
||||
"label": "Stdio Configuration (JSON)",
|
||||
"tooltip": "Paste your stdio MCP server configuration in JSON format. You can use the full mcpServers structure from config.yaml or just the inner server configuration.",
|
||||
"required": "Please enter stdio configuration",
|
||||
"invalidJson": "Please enter valid JSON"
|
||||
},
|
||||
"toolTestPanel": {
|
||||
"testToolTitle": "Test Tool:",
|
||||
"clickToCopyToolName": "Click to copy tool name",
|
||||
"provider": "Provider: {{name}}",
|
||||
"inputParameters": "Input Parameters",
|
||||
"inputParametersTooltip": "Configure the input parameters for this tool call",
|
||||
"inputLabel": "Input",
|
||||
"inputRequired": "Please enter input for this tool",
|
||||
"inputPlaceholder": "Enter input for this tool",
|
||||
"noParametersTitle": "No Parameters Required",
|
||||
"noParametersDesc": "This tool can be called without any input parameters.",
|
||||
"fieldRequired": "Please enter {{key}}",
|
||||
"invalidJsonObject": "Please enter a JSON object",
|
||||
"invalidJsonArray": "Please enter a JSON array",
|
||||
"invalidJson": "Invalid JSON",
|
||||
"selectKey": "Select {{key}}",
|
||||
"enterKey": "Enter {{key}}",
|
||||
"booleanTrue": "True",
|
||||
"booleanFalse": "False",
|
||||
"jsonObjectPlaceholder": "Enter JSON object for {{key}}",
|
||||
"jsonArrayPlaceholder": "Enter JSON array for {{key}}",
|
||||
"provideJsonObject": "Provide a valid JSON object.",
|
||||
"provideJsonArray": "Provide a valid JSON array.",
|
||||
"callingTool": "Calling Tool...",
|
||||
"callAgain": "Call Again",
|
||||
"callTool": "Call Tool",
|
||||
"toolResult": "Tool Result",
|
||||
"readyToCallTitle": "Ready to Call Tool",
|
||||
"readyToCallDesc": "Configure the input parameters and click \"Call Tool\" to see the results here.",
|
||||
"executedSuccessfully": "Tool executed successfully",
|
||||
"viewFormatted": "Formatted",
|
||||
"viewJson": "JSON",
|
||||
"copyResponse": "Copy response",
|
||||
"callingToolProgress": "Calling tool...",
|
||||
"pleaseWait": "Please wait while we process your request",
|
||||
"toolCallFailed": "Tool Call Failed",
|
||||
"textResponse": "Text Response",
|
||||
"imageResponse": "Image Response",
|
||||
"embeddedResource": "Embedded Resource",
|
||||
"resourceType": "Resource Type: {{type}}",
|
||||
"viewResource": "View Resource",
|
||||
"resultCopied": "Result copied to clipboard",
|
||||
"copyResultFailed": "Failed to copy result",
|
||||
"toolNameCopied": "Tool name copied to clipboard",
|
||||
"copyToolNameFailed": "Failed to copy tool name"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3262,6 +3262,115 @@
|
|||
"howToolsetsWorkPath": "API 密钥 → 编辑密钥 → MCP 服务器",
|
||||
"howToolsetsWorkDesc2": "将其分配给密钥,然后将您的 MCP 客户端指向工具集 URL。客户端只会看到您选定的工具。",
|
||||
"configLabel": "Claude Code / Cursor 配置"
|
||||
},
|
||||
"oAuthFormFields": {
|
||||
"leaveBlankToKeep": "留空以保留现有值",
|
||||
"flowTypeLabel": "OAuth 流程类型",
|
||||
"flowTypeTooltip": "选择代理与此 MCP 服务器的认证方式。M2M 使用客户端凭据进行服务器间通信;Interactive (PKCE) 用于需要浏览器授权的用户交互流程。",
|
||||
"m2mLabel": "机器对机器 (M2M)",
|
||||
"m2mDesc": "服务器间通信,无需用户交互",
|
||||
"interactiveLabel": "Interactive (PKCE)",
|
||||
"interactiveDesc": "基于浏览器的用户授权",
|
||||
"clientIdLabel": "Client ID",
|
||||
"clientIdM2MTooltip": "client_credentials 授权方式的 OAuth2 Client ID。",
|
||||
"clientIdM2MRequired": "M2M OAuth 需要提供 Client ID",
|
||||
"clientIdM2MPlaceholder": "输入 OAuth Client ID",
|
||||
"clientSecretLabel": "Client Secret",
|
||||
"clientSecretM2MTooltip": "client_credentials 授权方式的 OAuth2 Client Secret。",
|
||||
"clientSecretM2MRequired": "M2M OAuth 需要提供 Client Secret",
|
||||
"clientSecretM2MPlaceholder": "输入 OAuth Client Secret",
|
||||
"tokenUrlLabel": "Token URL",
|
||||
"tokenUrlM2MTooltip": "client_credentials 授权方式的 Token 端点 URL。",
|
||||
"tokenUrlM2MRequired": "M2M OAuth 需要提供 Token URL",
|
||||
"scopesOptionalLabel": "Scopes(可选)",
|
||||
"scopesM2MTooltip": "client_credentials 授权时可选择请求的 scopes。",
|
||||
"scopesInteractiveTooltip": "Token 交换时请求的可选 scopes,多个 scopes 可用回车或逗号分隔。",
|
||||
"scopesPlaceholder": "添加 scopes",
|
||||
"clientIdOptionalLabel": "Client ID(可选)",
|
||||
"clientIdInteractiveTooltip": "仅在您的 MCP 服务器不支持动态客户端注册时提供。",
|
||||
"clientIdInteractivePlaceholder": "输入 Client ID",
|
||||
"clientSecretOptionalLabel": "Client Secret(可选)",
|
||||
"clientSecretInteractiveTooltip": "仅在您的 MCP 服务器不支持动态客户端注册时提供。",
|
||||
"clientSecretInteractivePlaceholder": "输入 Client Secret",
|
||||
"authorizationUrlOptionalLabel": "Authorization URL(可选)",
|
||||
"authorizationUrlTooltip": "授权端点的可选覆盖地址。",
|
||||
"tokenUrlOptionalLabel": "Token URL(可选)",
|
||||
"tokenUrlInteractiveTooltip": "Token 端点的可选覆盖地址。",
|
||||
"registrationUrlOptionalLabel": "Registration URL(可选)",
|
||||
"registrationUrlTooltip": "动态客户端注册端点的可选覆盖地址。",
|
||||
"tokenValidationRulesLabel": "Token 验证规则(可选)",
|
||||
"tokenValidationRulesTooltip": "存储前对 OAuth Token 响应进行校验的键值规则 JSON 对象。支持点号表示法访问嵌套字段(如 {\"organization\": \"my-org\", \"team.id\": \"123\"})。验证失败的 Token 将以 HTTP 403 拒绝。",
|
||||
"mustBeValidJson": "必须是有效的 JSON",
|
||||
"tokenStorageTtlLabel": "Token 存储 TTL(秒,可选)",
|
||||
"tokenStorageTtlTooltip": "在 Redis 中缓存每位用户 OAuth 访问 Token 的时长(与 Token 自身的 expires_in 无关)。留空则从 Token 的 expires_in 推导 TTL,或回退到默认的 12 小时。",
|
||||
"tokenStorageTtlPlaceholder": "例如:3600",
|
||||
"createOAuthAppLink": "创建 OAuth App",
|
||||
"oauthFlowDescription": "使用 OAuth 获取新的访问 Token,并临时保存在会话中作为认证凭据。",
|
||||
"waitingForAuthorization": "等待授权...",
|
||||
"exchangingCode": "正在交换授权码...",
|
||||
"authorizeAndFetch": "授权并获取 Token",
|
||||
"tokenFetched": "Token 已获取,有效期 {{seconds}} 秒。"
|
||||
},
|
||||
"openAPIFormSection": {
|
||||
"specUrlLabel": "OpenAPI Spec URL",
|
||||
"specUrlTooltip": "OpenAPI 规范文件的 URL(JSON 或 YAML 格式)。MCP 工具将自动根据规范中定义的 API 端点生成。",
|
||||
"specUrlRequired": "请输入 OpenAPI Spec URL"
|
||||
},
|
||||
"openAPIQuickPicker": {
|
||||
"popularApis": "热门 API",
|
||||
"selectHint": "选择 API 可自动填充 Spec URL 和 OAuth 2.0 设置,或在下方输入自定义 Spec URL。"
|
||||
},
|
||||
"stdioConfiguration": {
|
||||
"label": "Stdio 配置(JSON)",
|
||||
"tooltip": "以 JSON 格式粘贴您的 stdio MCP 服务器配置。可使用 config.yaml 中的完整 mcpServers 结构,或仅填写内部服务器配置。",
|
||||
"required": "请输入 stdio 配置",
|
||||
"invalidJson": "请输入有效的 JSON"
|
||||
},
|
||||
"toolTestPanel": {
|
||||
"testToolTitle": "测试工具:",
|
||||
"clickToCopyToolName": "点击复制工具名称",
|
||||
"provider": "提供商:{{name}}",
|
||||
"inputParameters": "输入参数",
|
||||
"inputParametersTooltip": "配置此工具调用的输入参数",
|
||||
"inputLabel": "输入",
|
||||
"inputRequired": "请为此工具输入内容",
|
||||
"inputPlaceholder": "为此工具输入内容",
|
||||
"noParametersTitle": "无需参数",
|
||||
"noParametersDesc": "此工具无需任何输入参数即可调用。",
|
||||
"fieldRequired": "请输入 {{key}}",
|
||||
"invalidJsonObject": "请输入 JSON 对象",
|
||||
"invalidJsonArray": "请输入 JSON 数组",
|
||||
"invalidJson": "无效的 JSON",
|
||||
"selectKey": "选择 {{key}}",
|
||||
"enterKey": "输入 {{key}}",
|
||||
"booleanTrue": "True",
|
||||
"booleanFalse": "False",
|
||||
"jsonObjectPlaceholder": "为 {{key}} 输入 JSON 对象",
|
||||
"jsonArrayPlaceholder": "为 {{key}} 输入 JSON 数组",
|
||||
"provideJsonObject": "请提供有效的 JSON 对象。",
|
||||
"provideJsonArray": "请提供有效的 JSON 数组。",
|
||||
"callingTool": "调用中...",
|
||||
"callAgain": "再次调用",
|
||||
"callTool": "调用工具",
|
||||
"toolResult": "工具结果",
|
||||
"readyToCallTitle": "准备就绪",
|
||||
"readyToCallDesc": "配置输入参数后点击「调用工具」查看结果。",
|
||||
"executedSuccessfully": "工具执行成功",
|
||||
"viewFormatted": "格式化",
|
||||
"viewJson": "JSON",
|
||||
"copyResponse": "复制响应",
|
||||
"callingToolProgress": "正在调用工具...",
|
||||
"pleaseWait": "请稍候,正在处理您的请求",
|
||||
"toolCallFailed": "工具调用失败",
|
||||
"textResponse": "文本响应",
|
||||
"imageResponse": "图片响应",
|
||||
"embeddedResource": "嵌入资源",
|
||||
"resourceType": "资源类型:{{type}}",
|
||||
"viewResource": "查看资源",
|
||||
"resultCopied": "结果已复制到剪贴板",
|
||||
"copyResultFailed": "复制结果失败",
|
||||
"toolNameCopied": "工具名称已复制到剪贴板",
|
||||
"copyToolNameFailed": "复制工具名称失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue