mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-05 16:13:08 +08:00
Add comprehensive documentation templates and schemas for project documentation workflow
- Introduced agent instruction template for generating documentation tasks. - Created CSV schema for tasks including definitions and example entries. - Defined documentation dimensions across five waves with specific focus areas. - Established quality standards for documentation, including completeness, accuracy, clarity, and context integration.
This commit is contained in:
@@ -11,11 +11,13 @@ import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Lock,
|
||||
Link,
|
||||
} from 'lucide-react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { McpServer } from '@/lib/api';
|
||||
import { isHttpMcpServer, isStdioMcpServer } from '@/lib/api';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
@@ -40,6 +42,20 @@ export function CodexMcpCard({
|
||||
}: CodexMcpCardProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
|
||||
const isHttp = isHttpMcpServer(server);
|
||||
const isStdio = isStdioMcpServer(server);
|
||||
|
||||
// Get display text for server summary line
|
||||
const getServerSummary = () => {
|
||||
if (isHttp) {
|
||||
return server.url;
|
||||
}
|
||||
if (isStdio) {
|
||||
return `${server.command || ''} ${server.args?.join(' ') || ''}`.trim();
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={cn('overflow-hidden', !enabled && 'opacity-60')}>
|
||||
{/* Header */}
|
||||
@@ -63,6 +79,13 @@ export function CodexMcpCard({
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{server.name}
|
||||
</span>
|
||||
{/* Transport type badge */}
|
||||
{isHttp && (
|
||||
<Badge variant="outline" className="text-xs text-blue-600 border-blue-300">
|
||||
<Link className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'mcp.transport.http' })}
|
||||
</Badge>
|
||||
)}
|
||||
{/* Read-only badge */}
|
||||
<Badge variant="secondary" className="text-xs flex items-center gap-1">
|
||||
<Lock className="w-3 h-3" />
|
||||
@@ -75,8 +98,8 @@ export function CodexMcpCard({
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1 font-mono">
|
||||
{server.command} {server.args?.join(' ') || ''}
|
||||
<p className="text-sm text-muted-foreground mt-1 font-mono truncate max-w-md" title={getServerSummary()}>
|
||||
{getServerSummary()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -100,44 +123,101 @@ export function CodexMcpCard({
|
||||
{/* Expanded Content */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-border p-4 space-y-3 bg-muted/30">
|
||||
{/* Command details */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.command' })}</p>
|
||||
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto">
|
||||
{server.command}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
{/* Args */}
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.args' })}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{server.args.map((arg, idx) => (
|
||||
<Badge key={idx} variant="outline" className="font-mono text-xs">
|
||||
{arg}
|
||||
</Badge>
|
||||
))}
|
||||
{/* HTTP Server Details */}
|
||||
{isHttp && (
|
||||
<>
|
||||
{/* URL */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.http.url' })}</p>
|
||||
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto break-all">
|
||||
{server.url}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* HTTP Headers - show count only for read-only card */}
|
||||
{(server.headers || server.httpHeaders || server.bearerTokenEnvVar) && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.http.headers' })}</p>
|
||||
<div className="space-y-1">
|
||||
{server.headers && Object.entries(server.headers).map(([name]) => (
|
||||
<div key={name} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{name}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
****
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
{server.httpHeaders && Object.entries(server.httpHeaders).map(([name]) => (
|
||||
<div key={name} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{name}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
****
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
{server.bearerTokenEnvVar && (
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">Authorization</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
Bearer $${server.bearerTokenEnvVar}
|
||||
</code>
|
||||
<Badge variant="outline" className="text-xs text-blue-500">
|
||||
{formatMessage({ id: 'mcp.http.envVar' })}
|
||||
</Badge>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Environment variables */}
|
||||
{server.env && Object.keys(server.env).length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.env' })}</p>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{key}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
{value as string}
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
{/* STDIO Server Details */}
|
||||
{isStdio && (
|
||||
<>
|
||||
{/* Command details */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.command' })}</p>
|
||||
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto">
|
||||
{server.command}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Args */}
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.args' })}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{server.args.map((arg: string, idx: number) => (
|
||||
<Badge key={idx} variant="outline" className="font-mono text-xs">
|
||||
{arg}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Environment variables */}
|
||||
{server.env && Object.keys(server.env).length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.env' })}</p>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{key}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
{value as string}
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Read-only notice */}
|
||||
|
||||
@@ -15,6 +15,9 @@ import {
|
||||
Edit3,
|
||||
Trash2,
|
||||
Lock,
|
||||
Link,
|
||||
Eye,
|
||||
EyeOff,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
AlertDialog,
|
||||
@@ -32,6 +35,7 @@ import { Card } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { McpServer } from '@/lib/api';
|
||||
import { isHttpMcpServer, isStdioMcpServer } from '@/lib/api';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
@@ -54,6 +58,14 @@ export interface CodexMcpEditableCardProps {
|
||||
|
||||
// ========== Component ==========
|
||||
|
||||
/**
|
||||
* Mask a header value for display (show first 4 chars + ***)
|
||||
*/
|
||||
function maskHeaderValue(value: string): string {
|
||||
if (!value || value.length <= 4) return '****';
|
||||
return value.substring(0, 4) + '****';
|
||||
}
|
||||
|
||||
export function CodexMcpEditableCard({
|
||||
server,
|
||||
enabled,
|
||||
@@ -67,6 +79,58 @@ export function CodexMcpEditableCard({
|
||||
}: CodexMcpEditableCardProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [isConfirmDeleteOpen, setIsConfirmDeleteOpen] = useState(false);
|
||||
const [showHeaderValues, setShowHeaderValues] = useState<Record<string, boolean>>({});
|
||||
|
||||
const isHttp = isHttpMcpServer(server);
|
||||
const isStdio = isStdioMcpServer(server);
|
||||
|
||||
// Get display text for server summary line
|
||||
const getServerSummary = () => {
|
||||
if (isHttp) {
|
||||
return server.url;
|
||||
}
|
||||
return `${server.command || ''} ${server.args?.join(' ') || ''}`.trim();
|
||||
};
|
||||
|
||||
// Get all headers for HTTP server (Codex format: http_headers, env_http_headers, bearerTokenEnvVar)
|
||||
const getHttpHeaders = (): Array<{ name: string; value: string; isEnvVar?: boolean }> => {
|
||||
if (!isHttp) return [];
|
||||
|
||||
const headers: Array<{ name: string; value: string; isEnvVar?: boolean }> = [];
|
||||
|
||||
// Codex format: httpHeaders object
|
||||
if (server.httpHeaders) {
|
||||
Object.entries(server.httpHeaders).forEach(([name, value]) => {
|
||||
headers.push({ name, value });
|
||||
});
|
||||
}
|
||||
|
||||
// Codex format: bearerTokenEnvVar (adds Authorization header)
|
||||
if (server.bearerTokenEnvVar) {
|
||||
headers.push({
|
||||
name: 'Authorization',
|
||||
value: `Bearer $${server.bearerTokenEnvVar}`,
|
||||
isEnvVar: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Claude format: headers object (for compatibility)
|
||||
if (server.headers) {
|
||||
Object.entries(server.headers).forEach(([name, value]) => {
|
||||
headers.push({ name, value });
|
||||
});
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
// Toggle header value visibility
|
||||
const toggleHeaderValue = (headerName: string) => {
|
||||
setShowHeaderValues(prev => ({
|
||||
...prev,
|
||||
[headerName]: !prev[headerName]
|
||||
}));
|
||||
};
|
||||
|
||||
// Handle toggle with optimistic update
|
||||
const handleToggle = async () => {
|
||||
@@ -111,6 +175,13 @@ export function CodexMcpEditableCard({
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{server.name}
|
||||
</span>
|
||||
{/* Transport type badge */}
|
||||
{isHttp && (
|
||||
<Badge variant="outline" className="text-xs text-blue-600 border-blue-300">
|
||||
<Link className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'mcp.transport.http' })}
|
||||
</Badge>
|
||||
)}
|
||||
{isEditable ? (
|
||||
<>
|
||||
{/* Editable badge with actions */}
|
||||
@@ -141,8 +212,8 @@ export function CodexMcpEditableCard({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1 font-mono">
|
||||
{server.command} {server.args?.join(' ') || ''}
|
||||
<p className="text-sm text-muted-foreground mt-1 font-mono truncate max-w-md" title={getServerSummary()}>
|
||||
{getServerSummary()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -234,44 +305,100 @@ export function CodexMcpEditableCard({
|
||||
{/* Expanded Content */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-border p-4 space-y-3 bg-muted/30">
|
||||
{/* Command details */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.command' })}</p>
|
||||
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto">
|
||||
{server.command}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
{/* Args */}
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.args' })}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{server.args.map((arg, idx) => (
|
||||
<Badge key={idx} variant="outline" className="font-mono text-xs">
|
||||
{arg}
|
||||
</Badge>
|
||||
))}
|
||||
{/* HTTP Server Details */}
|
||||
{isHttp && (
|
||||
<>
|
||||
{/* URL */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.http.url' })}</p>
|
||||
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto break-all">
|
||||
{server.url}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* HTTP Headers */}
|
||||
{getHttpHeaders().length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.http.headers' })}</p>
|
||||
<div className="space-y-1">
|
||||
{getHttpHeaders().map((header) => (
|
||||
<div key={header.name} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{header.name}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
{showHeaderValues[header.name] ? header.value : maskHeaderValue(header.value)}
|
||||
</code>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={() => toggleHeaderValue(header.name)}
|
||||
title={showHeaderValues[header.name]
|
||||
? formatMessage({ id: 'mcp.http.hideValue' })
|
||||
: formatMessage({ id: 'mcp.http.showValue' })}
|
||||
>
|
||||
{showHeaderValues[header.name] ? (
|
||||
<EyeOff className="w-3 h-3 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="w-3 h-3 text-muted-foreground" />
|
||||
)}
|
||||
</Button>
|
||||
{header.isEnvVar && (
|
||||
<Badge variant="outline" className="text-xs text-blue-500">
|
||||
{formatMessage({ id: 'mcp.http.envVar' })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Environment variables */}
|
||||
{server.env && Object.keys(server.env).length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.env' })}</p>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{key}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
{value as string}
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
{/* STDIO Server Details */}
|
||||
{isStdio && (
|
||||
<>
|
||||
{/* Command details */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.command' })}</p>
|
||||
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto">
|
||||
{server.command}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Args */}
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.args' })}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{server.args.map((arg, idx) => (
|
||||
<Badge key={idx} variant="outline" className="font-mono text-xs">
|
||||
{arg}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Environment variables */}
|
||||
{server.env && Object.keys(server.env).length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.env' })}</p>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{key}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
{value as string}
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Notice based on editable state */}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { Checkbox } from '@/components/ui/Checkbox';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { useMcpServers } from '@/hooks';
|
||||
import { crossCliCopy, fetchCodexMcpServers } from '@/lib/api';
|
||||
import { crossCliCopy, fetchCodexMcpServers, isHttpMcpServer, isStdioMcpServer } from '@/lib/api';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
|
||||
@@ -41,7 +41,8 @@ export interface CrossCliCopyButtonProps {
|
||||
|
||||
interface ServerCheckboxItem {
|
||||
name: string;
|
||||
command: string;
|
||||
/** Display text - command for STDIO, URL for HTTP */
|
||||
displayText: string;
|
||||
enabled: boolean;
|
||||
selected: boolean;
|
||||
}
|
||||
@@ -78,7 +79,7 @@ export function CrossCliCopyButton({
|
||||
setServerItems(
|
||||
servers.map((s) => ({
|
||||
name: s.name,
|
||||
command: s.command,
|
||||
displayText: isHttpMcpServer(s) ? s.url : (isStdioMcpServer(s) ? s.command : ''),
|
||||
enabled: s.enabled,
|
||||
selected: false,
|
||||
}))
|
||||
@@ -91,7 +92,7 @@ export function CrossCliCopyButton({
|
||||
setServerItems(
|
||||
(codex.servers ?? []).map((s) => ({
|
||||
name: s.name,
|
||||
command: s.command,
|
||||
displayText: isHttpMcpServer(s) ? s.url : (isStdioMcpServer(s) ? s.command : ''),
|
||||
enabled: s.enabled,
|
||||
selected: false,
|
||||
}))
|
||||
@@ -281,7 +282,7 @@ export function CrossCliCopyButton({
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-mono truncate">
|
||||
{server.command}
|
||||
{server.displayText}
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { Checkbox } from '@/components/ui/Checkbox';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { useMcpServers } from '@/hooks';
|
||||
import { crossCliCopy, fetchCodexMcpServers } from '@/lib/api';
|
||||
import { crossCliCopy, fetchCodexMcpServers, isHttpMcpServer, isStdioMcpServer } from '@/lib/api';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
|
||||
@@ -25,7 +25,8 @@ export interface CrossCliSyncPanelProps {
|
||||
|
||||
interface ServerCheckboxItem {
|
||||
name: string;
|
||||
command: string;
|
||||
/** Display text - command for STDIO, URL for HTTP */
|
||||
displayText: string;
|
||||
enabled: boolean;
|
||||
selected: boolean;
|
||||
}
|
||||
@@ -64,7 +65,7 @@ export function CrossCliSyncPanel({ onSuccess, className }: CrossCliSyncPanelPro
|
||||
setCodexServers(
|
||||
(codex.servers ?? []).map((s) => ({
|
||||
name: s.name,
|
||||
command: s.command,
|
||||
displayText: isHttpMcpServer(s) ? s.url : (isStdioMcpServer(s) ? s.command : ''),
|
||||
enabled: s.enabled,
|
||||
selected: false,
|
||||
}))
|
||||
@@ -323,7 +324,7 @@ export function CrossCliSyncPanel({ onSuccess, className }: CrossCliSyncPanelPro
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-mono truncate">
|
||||
{server.command}
|
||||
{server.displayText}
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
@@ -421,7 +422,7 @@ export function CrossCliSyncPanel({ onSuccess, className }: CrossCliSyncPanelPro
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-mono truncate">
|
||||
{server.command}
|
||||
{server.displayText}
|
||||
</p>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -32,6 +32,8 @@ import {
|
||||
saveMcpTemplate,
|
||||
type McpServer,
|
||||
type McpProjectConfigType,
|
||||
isStdioMcpServer,
|
||||
isHttpMcpServer,
|
||||
} from '@/lib/api';
|
||||
import { mcpServersKeys, useMcpTemplates } from '@/hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
@@ -122,11 +124,6 @@ function HttpHeadersInput({ headers, onChange, disabled }: HttpHeadersInputProps
|
||||
});
|
||||
};
|
||||
|
||||
const maskValue = (value: string) => {
|
||||
if (!value) return '';
|
||||
return '*'.repeat(Math.min(value.length, 8));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{headers.map((header) => (
|
||||
@@ -246,9 +243,8 @@ export function McpServerDialog({
|
||||
// Helper to detect transport type from server data
|
||||
const detectTransportType = useCallback((serverData: McpServer | undefined): McpTransportType => {
|
||||
if (!serverData) return 'stdio';
|
||||
// If server has url field (from extended McpServer type), it's HTTP
|
||||
const extendedServer = serverData as McpServer & { url?: string };
|
||||
if (extendedServer.url) return 'http';
|
||||
// Use type guard to check for HTTP server
|
||||
if (isHttpMcpServer(serverData)) return 'http';
|
||||
return 'stdio';
|
||||
}, []);
|
||||
|
||||
@@ -258,50 +254,56 @@ export function McpServerDialog({
|
||||
const detectedType = detectTransportType(server);
|
||||
setTransportType(detectedType);
|
||||
|
||||
const extendedServer = server as McpServer & {
|
||||
url?: string;
|
||||
http_headers?: Record<string, string>;
|
||||
env_http_headers?: Record<string, string>;
|
||||
bearer_token_env_var?: string;
|
||||
};
|
||||
|
||||
// Parse HTTP headers if present
|
||||
// Parse HTTP headers if present (for HTTP servers)
|
||||
const httpHeaders: HttpHeader[] = [];
|
||||
if (extendedServer.http_headers) {
|
||||
Object.entries(extendedServer.http_headers).forEach(([name, value], idx) => {
|
||||
httpHeaders.push({
|
||||
id: `header-http-${idx}`,
|
||||
name,
|
||||
value,
|
||||
isEnvVar: false,
|
||||
if (isHttpMcpServer(server)) {
|
||||
// HTTP server - extract headers
|
||||
if (server.httpHeaders) {
|
||||
Object.entries(server.httpHeaders).forEach(([name, value], idx) => {
|
||||
httpHeaders.push({
|
||||
id: `header-http-${idx}`,
|
||||
name,
|
||||
value,
|
||||
isEnvVar: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
if (extendedServer.env_http_headers) {
|
||||
Object.entries(extendedServer.env_http_headers).forEach(([name, envVar], idx) => {
|
||||
httpHeaders.push({
|
||||
id: `header-env-${idx}`,
|
||||
name,
|
||||
value: envVar,
|
||||
isEnvVar: true,
|
||||
}
|
||||
if (server.envHttpHeaders) {
|
||||
// envHttpHeaders is an array of header names that get values from env vars
|
||||
server.envHttpHeaders.forEach((headerName, idx) => {
|
||||
httpHeaders.push({
|
||||
id: `header-env-${idx}`,
|
||||
name: headerName,
|
||||
value: '', // Env var name is not stored in value
|
||||
isEnvVar: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Get STDIO fields safely using type guard
|
||||
const stdioCommand = isStdioMcpServer(server) ? server.command : '';
|
||||
const stdioArgs = isStdioMcpServer(server) ? (server.args || []) : [];
|
||||
const stdioEnv = isStdioMcpServer(server) ? (server.env || {}) : {};
|
||||
|
||||
// Get HTTP fields safely using type guard
|
||||
const httpUrl = isHttpMcpServer(server) ? server.url : '';
|
||||
const httpBearerToken = isHttpMcpServer(server) ? (server.bearerTokenEnvVar || '') : '';
|
||||
|
||||
setFormData({
|
||||
name: server.name,
|
||||
command: server.command || '',
|
||||
args: server.args || [],
|
||||
env: server.env || {},
|
||||
url: extendedServer.url || '',
|
||||
command: stdioCommand,
|
||||
args: stdioArgs,
|
||||
env: stdioEnv,
|
||||
url: httpUrl,
|
||||
headers: httpHeaders,
|
||||
bearerTokenEnvVar: extendedServer.bearer_token_env_var || '',
|
||||
bearerTokenEnvVar: httpBearerToken,
|
||||
scope: server.scope,
|
||||
enabled: server.enabled,
|
||||
});
|
||||
setArgsInput((server.args || []).join(', '));
|
||||
setArgsInput(stdioArgs.join(', '));
|
||||
setEnvInput(
|
||||
Object.entries(server.env || {})
|
||||
Object.entries(stdioEnv)
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.join('\n')
|
||||
);
|
||||
@@ -488,50 +490,46 @@ export function McpServerDialog({
|
||||
return;
|
||||
}
|
||||
|
||||
// Build server config based on transport type
|
||||
const serverConfig: McpServer & {
|
||||
url?: string;
|
||||
http_headers?: Record<string, string>;
|
||||
env_http_headers?: Record<string, string>;
|
||||
bearer_token_env_var?: string;
|
||||
} = {
|
||||
name: formData.name,
|
||||
scope: formData.scope,
|
||||
enabled: formData.enabled,
|
||||
};
|
||||
// Build server config based on transport type using discriminated union
|
||||
let serverConfig: McpServer;
|
||||
|
||||
if (transportType === 'stdio') {
|
||||
serverConfig.command = formData.command;
|
||||
serverConfig.args = formData.args;
|
||||
serverConfig.env = formData.env;
|
||||
serverConfig = {
|
||||
name: formData.name,
|
||||
transport: 'stdio',
|
||||
command: formData.command,
|
||||
args: formData.args.length > 0 ? formData.args : undefined,
|
||||
env: Object.keys(formData.env).length > 0 ? formData.env : undefined,
|
||||
scope: formData.scope,
|
||||
enabled: formData.enabled,
|
||||
};
|
||||
} else {
|
||||
// HTTP transport
|
||||
serverConfig.url = formData.url;
|
||||
serverConfig.command = ''; // Empty command for HTTP servers
|
||||
|
||||
// Separate headers into static and env-based
|
||||
// HTTP transport - separate headers into static and env-based
|
||||
const httpHeaders: Record<string, string> = {};
|
||||
const envHttpHeaders: Record<string, string> = {};
|
||||
const envHttpHeaders: string[] = [];
|
||||
|
||||
formData.headers.forEach((h) => {
|
||||
if (h.name.trim()) {
|
||||
if (h.isEnvVar) {
|
||||
envHttpHeaders[h.name.trim()] = h.value.trim();
|
||||
// For env-based headers, store the header name that will be populated from env var
|
||||
envHttpHeaders.push(h.name.trim());
|
||||
} else {
|
||||
httpHeaders[h.name.trim()] = h.value.trim();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(httpHeaders).length > 0) {
|
||||
serverConfig.http_headers = httpHeaders;
|
||||
}
|
||||
if (Object.keys(envHttpHeaders).length > 0) {
|
||||
serverConfig.env_http_headers = envHttpHeaders;
|
||||
}
|
||||
if (formData.bearerTokenEnvVar.trim()) {
|
||||
serverConfig.bearer_token_env_var = formData.bearerTokenEnvVar.trim();
|
||||
}
|
||||
serverConfig = {
|
||||
name: formData.name,
|
||||
transport: 'http',
|
||||
url: formData.url,
|
||||
headers: Object.keys(httpHeaders).length > 0 ? httpHeaders : undefined,
|
||||
httpHeaders: Object.keys(httpHeaders).length > 0 ? httpHeaders : undefined,
|
||||
envHttpHeaders: envHttpHeaders.length > 0 ? envHttpHeaders : undefined,
|
||||
bearerTokenEnvVar: formData.bearerTokenEnvVar.trim() || undefined,
|
||||
scope: formData.scope,
|
||||
enabled: formData.enabled,
|
||||
};
|
||||
}
|
||||
|
||||
// Save as template if checked (only for STDIO)
|
||||
|
||||
@@ -19,12 +19,27 @@ import {
|
||||
import { useProjectOperations, useMcpServerMutations } from '@/hooks';
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { McpServer } from '@/lib/api';
|
||||
import { isHttpMcpServer, isStdioMcpServer } from '@/lib/api';
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
export interface OtherProjectServer extends McpServer {
|
||||
/**
|
||||
* Server from another project
|
||||
* Contains base server fields plus project metadata
|
||||
*/
|
||||
export interface OtherProjectServer {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
/** Display text - command for STDIO, URL for HTTP */
|
||||
displayText: string;
|
||||
/** Transport type */
|
||||
transport: 'stdio' | 'http';
|
||||
/** Project path */
|
||||
projectPath: string;
|
||||
/** Project name */
|
||||
projectName: string;
|
||||
/** Original server data */
|
||||
originalServer: McpServer;
|
||||
}
|
||||
|
||||
export interface OtherProjectsSectionProps {
|
||||
@@ -63,12 +78,17 @@ export function OtherProjectsSection({
|
||||
|
||||
for (const [path, serverList] of Object.entries(response.servers)) {
|
||||
const projectName = path.split(/[/\\]/).filter(Boolean).pop() || path;
|
||||
for (const server of (serverList as Omit<McpServer, 'scope'>[])) {
|
||||
for (const server of (serverList as McpServer[])) {
|
||||
servers.push({
|
||||
...server,
|
||||
scope: 'project',
|
||||
name: server.name,
|
||||
enabled: server.enabled,
|
||||
displayText: isHttpMcpServer(server)
|
||||
? server.url
|
||||
: (isStdioMcpServer(server) ? server.command : ''),
|
||||
transport: server.transport,
|
||||
projectPath: path,
|
||||
projectName,
|
||||
originalServer: server,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -88,14 +108,14 @@ export function OtherProjectsSection({
|
||||
// Generate a unique name by combining project name and server name
|
||||
const uniqueName = `${server.projectName}-${server.name}`.toLowerCase().replace(/\s+/g, '-');
|
||||
|
||||
await createServer({
|
||||
// Create server based on transport type
|
||||
const serverToCreate: McpServer = {
|
||||
...server.originalServer,
|
||||
name: uniqueName,
|
||||
command: server.command,
|
||||
args: server.args,
|
||||
env: server.env,
|
||||
scope: 'project',
|
||||
enabled: server.enabled,
|
||||
});
|
||||
};
|
||||
|
||||
await createServer(serverToCreate);
|
||||
|
||||
onImportSuccess?.(uniqueName, server.projectPath);
|
||||
} catch (error) {
|
||||
@@ -203,7 +223,7 @@ export function OtherProjectsSection({
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground font-mono truncate">
|
||||
{server.command} {(server.args || []).join(' ')}
|
||||
{server.displayText}
|
||||
</p>
|
||||
<p className="text-xs text-muted-foreground truncate">
|
||||
<span className="font-medium">{server.projectName}</span>
|
||||
|
||||
@@ -27,6 +27,17 @@
|
||||
"command": "Command",
|
||||
"args": "Arguments",
|
||||
"env": "Environment Variables",
|
||||
"transport": {
|
||||
"http": "HTTP",
|
||||
"stdio": "STDIO"
|
||||
},
|
||||
"http": {
|
||||
"url": "Server URL",
|
||||
"headers": "HTTP Headers",
|
||||
"showValue": "Show value",
|
||||
"hideValue": "Hide value",
|
||||
"envVar": "Env Var"
|
||||
},
|
||||
"codex": {
|
||||
"configPath": "Config Path",
|
||||
"readOnly": "Read-only",
|
||||
|
||||
@@ -22,6 +22,9 @@ import {
|
||||
ChevronUp,
|
||||
BookmarkPlus,
|
||||
AlertTriangle,
|
||||
Link,
|
||||
Eye,
|
||||
EyeOff,
|
||||
} from 'lucide-react';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
@@ -54,6 +57,9 @@ import {
|
||||
type McpServer,
|
||||
type McpServerConflict,
|
||||
type CcwMcpConfig,
|
||||
type HttpMcpServer,
|
||||
isHttpMcpServer,
|
||||
isStdioMcpServer,
|
||||
} from '@/lib/api';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
@@ -70,8 +76,68 @@ interface McpServerCardProps {
|
||||
conflictInfo?: McpServerConflict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mask a header value for display (show first 4 chars + ***)
|
||||
*/
|
||||
function maskHeaderValue(value: string): string {
|
||||
if (!value || value.length <= 4) return '****';
|
||||
return value.substring(0, 4) + '****';
|
||||
}
|
||||
|
||||
function McpServerCard({ server, isExpanded, onToggleExpand, onToggle, onEdit, onDelete, onSaveAsTemplate, conflictInfo }: McpServerCardProps) {
|
||||
const { formatMessage } = useIntl();
|
||||
const [showHeaderValues, setShowHeaderValues] = useState<Record<string, boolean>>({});
|
||||
|
||||
const isHttp = isHttpMcpServer(server);
|
||||
const isStdio = isStdioMcpServer(server);
|
||||
|
||||
// Get display text for server summary line
|
||||
const getServerSummary = () => {
|
||||
if (isHttp) {
|
||||
return server.url;
|
||||
}
|
||||
return `${server.command} ${server.args?.join(' ') || ''}`.trim();
|
||||
};
|
||||
|
||||
// Get all headers for HTTP server (combine Claude + Codex formats)
|
||||
const getHttpHeaders = (): Array<{ name: string; value: string; isEnvVar?: boolean }> => {
|
||||
if (!isHttp) return [];
|
||||
|
||||
const headers: Array<{ name: string; value: string; isEnvVar?: boolean }> = [];
|
||||
|
||||
// Claude format: headers object
|
||||
if (server.headers) {
|
||||
Object.entries(server.headers).forEach(([name, value]) => {
|
||||
headers.push({ name, value });
|
||||
});
|
||||
}
|
||||
|
||||
// Codex format: httpHeaders object
|
||||
if (server.httpHeaders) {
|
||||
Object.entries(server.httpHeaders).forEach(([name, value]) => {
|
||||
headers.push({ name, value });
|
||||
});
|
||||
}
|
||||
|
||||
// Codex format: bearerTokenEnvVar (adds Authorization header)
|
||||
if (server.bearerTokenEnvVar) {
|
||||
headers.push({
|
||||
name: 'Authorization',
|
||||
value: `Bearer $${server.bearerTokenEnvVar}`,
|
||||
isEnvVar: true,
|
||||
});
|
||||
}
|
||||
|
||||
return headers;
|
||||
};
|
||||
|
||||
// Toggle header value visibility
|
||||
const toggleHeaderValue = (headerName: string) => {
|
||||
setShowHeaderValues(prev => ({
|
||||
...prev,
|
||||
[headerName]: !prev[headerName]
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={cn('overflow-hidden', !server.enabled && 'opacity-60')}>
|
||||
@@ -96,6 +162,13 @@ function McpServerCard({ server, isExpanded, onToggleExpand, onToggle, onEdit, o
|
||||
<span className="text-sm font-medium text-foreground">
|
||||
{server.name}
|
||||
</span>
|
||||
{/* Transport type badge */}
|
||||
{isHttp && (
|
||||
<Badge variant="outline" className="text-xs text-blue-600 border-blue-300">
|
||||
<Link className="w-3 h-3 mr-1" />
|
||||
{formatMessage({ id: 'mcp.transport.http' })}
|
||||
</Badge>
|
||||
)}
|
||||
<Badge variant={server.scope === 'global' ? 'default' : 'secondary'} className="text-xs">
|
||||
{server.scope === 'global' ? (
|
||||
<><Globe className="w-3 h-3 mr-1" />{formatMessage({ id: 'mcp.scope.global' })}</>
|
||||
@@ -115,8 +188,8 @@ function McpServerCard({ server, isExpanded, onToggleExpand, onToggle, onEdit, o
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mt-1 font-mono">
|
||||
{server.command} {server.args?.join(' ') || ''}
|
||||
<p className="text-sm text-muted-foreground mt-1 font-mono truncate max-w-md" title={getServerSummary()}>
|
||||
{getServerSummary()}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -178,44 +251,100 @@ function McpServerCard({ server, isExpanded, onToggleExpand, onToggle, onEdit, o
|
||||
{/* Expanded Content */}
|
||||
{isExpanded && (
|
||||
<div className="border-t border-border p-4 space-y-3 bg-muted/30">
|
||||
{/* Command details */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.command' })}</p>
|
||||
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto">
|
||||
{server.command}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
{/* Args */}
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.args' })}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{server.args.map((arg, idx) => (
|
||||
<Badge key={idx} variant="outline" className="font-mono text-xs">
|
||||
{arg}
|
||||
</Badge>
|
||||
))}
|
||||
{/* HTTP Server Details */}
|
||||
{isHttp && (
|
||||
<>
|
||||
{/* URL */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.http.url' })}</p>
|
||||
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto break-all">
|
||||
{server.url}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* HTTP Headers */}
|
||||
{getHttpHeaders().length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.http.headers' })}</p>
|
||||
<div className="space-y-1">
|
||||
{getHttpHeaders().map((header) => (
|
||||
<div key={header.name} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{header.name}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
{showHeaderValues[header.name] ? header.value : maskHeaderValue(header.value)}
|
||||
</code>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={() => toggleHeaderValue(header.name)}
|
||||
title={showHeaderValues[header.name]
|
||||
? formatMessage({ id: 'mcp.http.hideValue' })
|
||||
: formatMessage({ id: 'mcp.http.showValue' })}
|
||||
>
|
||||
{showHeaderValues[header.name] ? (
|
||||
<EyeOff className="w-3 h-3 text-muted-foreground" />
|
||||
) : (
|
||||
<Eye className="w-3 h-3 text-muted-foreground" />
|
||||
)}
|
||||
</Button>
|
||||
{header.isEnvVar && (
|
||||
<Badge variant="outline" className="text-xs text-blue-500">
|
||||
{formatMessage({ id: 'mcp.http.envVar' })}
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Environment variables */}
|
||||
{server.env && Object.keys(server.env).length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.env' })}</p>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{key}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
{value as string}
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
{/* STDIO Server Details */}
|
||||
{isStdio && (
|
||||
<>
|
||||
{/* Command details */}
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.command' })}</p>
|
||||
<code className="text-sm bg-background px-2 py-1 rounded block overflow-x-auto">
|
||||
{server.command}
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Args */}
|
||||
{server.args && server.args.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.args' })}</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{server.args.map((arg, idx) => (
|
||||
<Badge key={idx} variant="outline" className="font-mono text-xs">
|
||||
{arg}
|
||||
</Badge>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Environment variables */}
|
||||
{server.env && Object.keys(server.env).length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs text-muted-foreground mb-1">{formatMessage({ id: 'mcp.env' })}</p>
|
||||
<div className="space-y-1">
|
||||
{Object.entries(server.env).map(([key, value]) => (
|
||||
<div key={key} className="flex items-center gap-2 text-sm">
|
||||
<Badge variant="secondary" className="font-mono">{key}</Badge>
|
||||
<span className="text-muted-foreground">=</span>
|
||||
<code className="text-xs bg-background px-2 py-1 rounded flex-1 overflow-x-auto">
|
||||
{value as string}
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Conflict warning panel */}
|
||||
@@ -609,18 +738,28 @@ export function McpManagerPage() {
|
||||
};
|
||||
|
||||
// Filter servers by search query
|
||||
const filteredServers = servers.filter((s) =>
|
||||
s.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
s.command.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
const filteredServers = servers.filter((s) => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
const nameMatch = s.name.toLowerCase().includes(query);
|
||||
// For STDIO servers, search in command; for HTTP servers, search in url
|
||||
const transportMatch = isHttpMcpServer(s)
|
||||
? s.url?.toLowerCase().includes(query)
|
||||
: s.command?.toLowerCase().includes(query);
|
||||
return nameMatch || transportMatch;
|
||||
});
|
||||
|
||||
// Filter Codex servers by search query
|
||||
const codexServers = codexQuery.data?.servers ?? [];
|
||||
const codexConfigPath = codexQuery.data?.configPath ?? '';
|
||||
const filteredCodexServers = codexServers.filter((s) =>
|
||||
s.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
s.command.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
const filteredCodexServers = codexServers.filter((s) => {
|
||||
const query = searchQuery.toLowerCase();
|
||||
const nameMatch = s.name.toLowerCase().includes(query);
|
||||
// For STDIO servers, search in command; for HTTP servers, search in url
|
||||
const transportMatch = isHttpMcpServer(s)
|
||||
? s.url?.toLowerCase().includes(query)
|
||||
: s.command?.toLowerCase().includes(query);
|
||||
return nameMatch || transportMatch;
|
||||
});
|
||||
|
||||
const currentServers = cliMode === 'codex' ? filteredCodexServers : filteredServers;
|
||||
const currentExpanded = cliMode === 'codex' ? codexExpandedServers : expandedServers;
|
||||
|
||||
Reference in New Issue
Block a user