码桶

发现社区成员的开源项目

奶狗 /

ng-webot

公开
main
ng-webot/web/src/components/McpConfig.tsx
McpConfig.tsx5.4 KB
import { useState, useEffect } from 'react';
import type { Bot } from '../lib/api';

interface McpServer {
  id: string;
  name: string;
  url: string;
  apiKey: string;
}

interface McpConfigProps {
  bot: { id: number };
  settings: Record<string, string>;
  onSave: (key: string, value: string) => Promise<boolean>;
  saving: boolean;
}

export default function McpConfig({ settings, onSave, saving }: McpConfigProps) {
  const [servers, setServers] = useState<McpServer[]>([]);
  const [dirty, setDirty] = useState(false);

  useEffect(() => {
    try {
      const raw = settings['servers'];
      if (raw) {
        const parsed = JSON.parse(raw);
        setServers(Array.isArray(parsed) ? parsed : []);
      } else {
        setServers([]);
      }
    } catch {
      setServers([]);
    }
  }, [settings]);

  const updateServers = (newServers: McpServer[]) => {
    setServers(newServers);
    setDirty(true);
  };

  const handleSave = async () => {
    const success = await onSave('servers', JSON.stringify(servers));
    if (success) setDirty(false);
  };

  const addServer = () => {
    const id = 'svr_' + Date.now();
    updateServers([...servers, { id, name: '', url: '', apiKey: '' }]);
  };

  const removeServer = (id: string) => {
    updateServers(servers.filter(s => s.id !== id));
  };

  const updateServer = (id: string, field: keyof McpServer, value: string) => {
    updateServers(servers.map(s => s.id === id ? { ...s, [field]: value } : s));
  };

  return (
    <div className="space-y-6">
      <div className="rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800/50 p-4">
        <h4 className="font-medium text-sm mb-2">关于 MCP(Model Context Protocol)</h4>
        <p className="text-sm text-gray-600 dark:text-gray-400">
          MCP 是一种开放协议,允许外部服务器向 AI 暴露工具能力。
          添加 MCP 服务器后,智能助手将自动获取其全部工具,无需额外配置。
        </p>
        <p className="text-sm text-gray-500 dark:text-gray-500 mt-2">
          常见用途:接入数据库查询、API 调用、文件搜索、代码执行等第三方能力。
          配置完成后可在微信中对机器人说「mcp 工具」查看可用工具列表。
        </p>
      </div>

      {servers.length === 0 && (
        <div className="text-center py-8 text-gray-400 dark:text-gray-500 text-sm">
          暂无 MCP 服务器,点击下方按钮添加
        </div>
      )}

      <div className="space-y-4">
        {servers.map((server) => (
          <div key={server.id} className="rounded-lg border border-gray-200 dark:border-gray-700 p-4 space-y-3">
            <div className="flex items-center justify-between">
              <span className="text-sm font-medium text-gray-500">MCP 服务器</span>
              <button
                onClick={() => removeServer(server.id)}
                className="text-xs text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300"
              >
                移除
              </button>
            </div>

            <div>
              <label className="text-xs text-gray-500 mb-1 block">名称(自定义标识)</label>
              <input
                type="text"
                value={server.name}
                onChange={e => updateServer(server.id, 'name', e.target.value)}
                placeholder="如:我的文件服务器"
                className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm"
              />
            </div>

            <div>
              <label className="text-xs text-gray-500 mb-1 block">服务器 URL</label>
              <input
                type="text"
                value={server.url}
                onChange={e => updateServer(server.id, 'url', e.target.value)}
                placeholder="https://your-mcp-server.com/mcp"
                className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm font-mono"
              />
            </div>

            <div>
              <label className="text-xs text-gray-500 mb-1 block">API Key(可选)</label>
              <input
                type="password"
                value={server.apiKey}
                onChange={e => updateServer(server.id, 'apiKey', e.target.value)}
                placeholder="Bearer Token 或 API Key"
                className="w-full px-3 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 text-sm"
              />
            </div>
          </div>
        ))}
      </div>

      <div className="flex gap-3">
        <button
          onClick={addServer}
          className="px-4 py-2 text-sm rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
        >
          添加服务器
        </button>

        <button
          onClick={handleSave}
          disabled={saving || !dirty}
          className="px-4 py-2 text-sm rounded-lg bg-zinc-900 text-white hover:bg-zinc-800 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
        >
          {saving ? '保存中...' : '保存配置'}
        </button>
      </div>
    </div>
  );
}