fix: Add missing return statement and closing brace in /mcp command handler (#309)
This commit is contained in:
@@ -32,6 +32,11 @@ import {
|
||||
executeTool,
|
||||
savePermissionRule,
|
||||
} from "../tools/manager";
|
||||
import {
|
||||
handleMcpAdd,
|
||||
handleMcpUsage,
|
||||
type McpCommandContext,
|
||||
} from "./commands/mcp";
|
||||
import {
|
||||
addCommandResult,
|
||||
handlePin,
|
||||
@@ -42,11 +47,6 @@ import {
|
||||
type ProfileCommandContext,
|
||||
validateProfileLoad,
|
||||
} from "./commands/profile";
|
||||
import {
|
||||
handleMcpAdd,
|
||||
handleMcpUsage,
|
||||
type McpCommandContext,
|
||||
} from "./commands/mcp";
|
||||
import { AgentSelector } from "./components/AgentSelector";
|
||||
import { ApprovalDialog } from "./components/ApprovalDialogRich";
|
||||
import { AssistantMessage } from "./components/AssistantMessageRich";
|
||||
@@ -1791,6 +1791,8 @@ export default function App({
|
||||
|
||||
// Unknown subcommand
|
||||
handleMcpUsage(mcpCtx, msg);
|
||||
return { submitted: true };
|
||||
}
|
||||
|
||||
// Special handling for /help command - opens help dialog
|
||||
if (trimmed === "/help") {
|
||||
@@ -4658,7 +4660,8 @@ Plan file path: ${planFilePath}`;
|
||||
kind: "command",
|
||||
id: cmdId,
|
||||
input: "/mcp",
|
||||
output: "Use /mcp add --transport <http|sse|stdio> <name> <url|command> [...] to add a new server",
|
||||
output:
|
||||
"Use /mcp add --transport <http|sse|stdio> <name> <url|command> [...] to add a new server",
|
||||
phase: "finished",
|
||||
success: true,
|
||||
});
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
// MCP server command handlers
|
||||
|
||||
import type {
|
||||
CreateStreamableHTTPMcpServer,
|
||||
CreateSseMcpServer,
|
||||
CreateStdioMcpServer,
|
||||
CreateStreamableHTTPMcpServer,
|
||||
} from "@letta-ai/letta-client/resources/mcp-servers/mcp-servers";
|
||||
import { getClient } from "../../agent/client";
|
||||
import type { Buffers, Line } from "../helpers/accumulator";
|
||||
@@ -77,11 +77,11 @@ function parseCommandArgs(commandStr: string): string[] {
|
||||
let current = "";
|
||||
let inQuotes = false;
|
||||
let quoteChar = "";
|
||||
|
||||
|
||||
for (let i = 0; i < commandStr.length; i++) {
|
||||
const char = commandStr[i];
|
||||
if (!char) continue; // Skip if undefined (shouldn't happen but type safety)
|
||||
|
||||
|
||||
if ((char === '"' || char === "'") && !inQuotes) {
|
||||
// Start of quoted string
|
||||
inQuotes = true;
|
||||
@@ -101,12 +101,12 @@ function parseCommandArgs(commandStr: string): string[] {
|
||||
current += char;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add final argument if any
|
||||
if (current) {
|
||||
args.push(current);
|
||||
}
|
||||
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ function parseMcpAddArgs(parts: string[]): McpAddArgs | null {
|
||||
let name: string | null = null;
|
||||
let url: string | null = null;
|
||||
let command: string | null = null;
|
||||
let args: string[] = [];
|
||||
const args: string[] = [];
|
||||
const headers: Record<string, string> = {};
|
||||
let authToken: string | null = null;
|
||||
|
||||
@@ -153,9 +153,9 @@ function parseMcpAddArgs(parts: string[]): McpAddArgs | null {
|
||||
// Parse "key: value" or "key=value"
|
||||
const colonMatch = headerValue.match(/^([^:]+):\s*(.+)$/);
|
||||
const equalsMatch = headerValue.match(/^([^=]+)=(.+)$/);
|
||||
if (colonMatch && colonMatch[1] && colonMatch[2]) {
|
||||
if (colonMatch?.[1] && colonMatch[2]) {
|
||||
headers[colonMatch[1].trim()] = colonMatch[2].trim();
|
||||
} else if (equalsMatch && equalsMatch[1] && equalsMatch[2]) {
|
||||
} else if (equalsMatch?.[1] && equalsMatch[2]) {
|
||||
headers[equalsMatch[1].trim()] = equalsMatch[2].trim();
|
||||
}
|
||||
}
|
||||
@@ -194,14 +194,14 @@ function parseMcpAddArgs(parts: string[]): McpAddArgs | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
transport,
|
||||
name,
|
||||
url: url || null,
|
||||
command: command || null,
|
||||
args,
|
||||
headers,
|
||||
authToken: authToken || null
|
||||
return {
|
||||
transport,
|
||||
name,
|
||||
url: url || null,
|
||||
command: command || null,
|
||||
args,
|
||||
headers,
|
||||
authToken: authToken || null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -220,7 +220,7 @@ export async function handleMcpAdd(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
msg,
|
||||
"Usage: /mcp add --transport <http|sse|stdio> <name> <url|command> [--header \"key: value\"] [--auth token]\n\nExamples:\n /mcp add --transport http notion https://mcp.notion.com/mcp\n /mcp add --transport http secure-api https://api.example.com/mcp --header \"Authorization: Bearer token\"",
|
||||
'Usage: /mcp add --transport <http|sse|stdio> <name> <url|command> [--header "key: value"] [--auth token]\n\nExamples:\n /mcp add --transport http notion https://mcp.notion.com/mcp\n /mcp add --transport http secure-api https://api.example.com/mcp --header "Authorization: Bearer token"',
|
||||
false,
|
||||
);
|
||||
return;
|
||||
@@ -253,7 +253,8 @@ export async function handleMcpAdd(
|
||||
mcp_server_type: "streamable_http",
|
||||
server_url: args.url,
|
||||
auth_token: args.authToken,
|
||||
custom_headers: Object.keys(args.headers).length > 0 ? args.headers : null,
|
||||
custom_headers:
|
||||
Object.keys(args.headers).length > 0 ? args.headers : null,
|
||||
};
|
||||
} else if (args.transport === "sse") {
|
||||
if (!args.url) {
|
||||
@@ -263,7 +264,8 @@ export async function handleMcpAdd(
|
||||
mcp_server_type: "sse",
|
||||
server_url: args.url,
|
||||
auth_token: args.authToken,
|
||||
custom_headers: Object.keys(args.headers).length > 0 ? args.headers : null,
|
||||
custom_headers:
|
||||
Object.keys(args.headers).length > 0 ? args.headers : null,
|
||||
};
|
||||
} else {
|
||||
// stdio
|
||||
@@ -307,10 +309,10 @@ export async function handleMcpAdd(
|
||||
|
||||
try {
|
||||
await client.mcpServers.refresh(server.id);
|
||||
|
||||
|
||||
// Get tool count
|
||||
const tools = await client.mcpServers.tools.list(server.id);
|
||||
|
||||
|
||||
updateCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
@@ -321,7 +323,8 @@ export async function handleMcpAdd(
|
||||
);
|
||||
} catch (refreshErr) {
|
||||
// If refresh fails, still show success but warn about tools
|
||||
const errorMsg = refreshErr instanceof Error ? refreshErr.message : "Unknown error";
|
||||
const errorMsg =
|
||||
refreshErr instanceof Error ? refreshErr.message : "Unknown error";
|
||||
updateCommandResult(
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
@@ -352,7 +355,7 @@ export function handleMcpUsage(ctx: McpCommandContext, msg: string): void {
|
||||
ctx.buffersRef,
|
||||
ctx.refreshDerived,
|
||||
msg,
|
||||
"Usage: /mcp [add ...]\n /mcp - list MCP servers\n /mcp add --transport <http|sse|stdio> <name> <url|command> [...] - add a new server\n\nExamples:\n /mcp add --transport http notion https://mcp.notion.com/mcp\n /mcp add --transport http api https://api.example.com --header \"Authorization: Bearer token\"",
|
||||
'Usage: /mcp [add ...]\n /mcp - list MCP servers\n /mcp add --transport <http|sse|stdio> <name> <url|command> [...] - add a new server\n\nExamples:\n /mcp add --transport http notion https://mcp.notion.com/mcp\n /mcp add --transport http api https://api.example.com --header "Authorization: Bearer token"',
|
||||
false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import type {
|
||||
McpServerListResponse,
|
||||
StreamableHTTPMcpServer,
|
||||
SseMcpServer,
|
||||
StdioMcpServer,
|
||||
StreamableHTTPMcpServer,
|
||||
} from "@letta-ai/letta-client/resources/mcp-servers/mcp-servers";
|
||||
import type { Tool } from "@letta-ai/letta-client/resources/tools";
|
||||
import { Box, Text, useInput } from "ink";
|
||||
@@ -75,11 +74,13 @@ export const McpSelector = memo(function McpSelector({
|
||||
const [mode, setMode] = useState<Mode>("browsing");
|
||||
const [deleteConfirmIndex, setDeleteConfirmIndex] = useState(0);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
|
||||
// Tools viewing state
|
||||
const [viewingServer, setViewingServer] = useState<McpServer | null>(null);
|
||||
const [tools, setTools] = useState<Tool[]>([]);
|
||||
const [attachedToolIds, setAttachedToolIds] = useState<Set<string>>(new Set());
|
||||
const [attachedToolIds, setAttachedToolIds] = useState<Set<string>>(
|
||||
new Set(),
|
||||
);
|
||||
const [toolsLoading, setToolsLoading] = useState(false);
|
||||
const [toolsError, setToolsError] = useState<string | null>(null);
|
||||
const [toolsPage, setToolsPage] = useState(0);
|
||||
@@ -105,81 +106,86 @@ export const McpSelector = memo(function McpSelector({
|
||||
}, []);
|
||||
|
||||
// Load tools for a specific server
|
||||
const loadTools = useCallback(async (server: McpServer) => {
|
||||
if (!server.id) {
|
||||
setToolsError("Server ID not available");
|
||||
return;
|
||||
}
|
||||
|
||||
setToolsLoading(true);
|
||||
setToolsError(null);
|
||||
setViewingServer(server);
|
||||
setMode("viewing-tools");
|
||||
|
||||
try {
|
||||
const client = await getClient();
|
||||
|
||||
// Fetch MCP server tools
|
||||
const toolsList = await client.mcpServers.tools.list(server.id);
|
||||
|
||||
// If no tools found, might need to refresh from server
|
||||
if (toolsList.length === 0) {
|
||||
setToolsError(
|
||||
"No tools found. The server may need to be refreshed. Press R to sync tools from the MCP server."
|
||||
);
|
||||
const loadTools = useCallback(
|
||||
async (server: McpServer) => {
|
||||
if (!server.id) {
|
||||
setToolsError("Server ID not available");
|
||||
return;
|
||||
}
|
||||
|
||||
setTools(toolsList);
|
||||
|
||||
// Fetch agent's current tools to check which are attached
|
||||
const agent = await client.agents.retrieve(agentId);
|
||||
const agentToolIds = new Set(agent.tools?.map(t => t.id) || []);
|
||||
setAttachedToolIds(agentToolIds);
|
||||
|
||||
setToolsPage(0);
|
||||
setToolsSelectedIndex(0);
|
||||
} catch (err) {
|
||||
setToolsError(
|
||||
err instanceof Error ? err.message : "Failed to load tools",
|
||||
);
|
||||
setTools([]);
|
||||
} finally {
|
||||
setToolsLoading(false);
|
||||
}
|
||||
}, [agentId]);
|
||||
|
||||
setToolsLoading(true);
|
||||
setToolsError(null);
|
||||
setViewingServer(server);
|
||||
setMode("viewing-tools");
|
||||
|
||||
try {
|
||||
const client = await getClient();
|
||||
|
||||
// Fetch MCP server tools
|
||||
const toolsList = await client.mcpServers.tools.list(server.id);
|
||||
|
||||
// If no tools found, might need to refresh from server
|
||||
if (toolsList.length === 0) {
|
||||
setToolsError(
|
||||
"No tools found. The server may need to be refreshed. Press R to sync tools from the MCP server.",
|
||||
);
|
||||
}
|
||||
|
||||
setTools(toolsList);
|
||||
|
||||
// Fetch agent's current tools to check which are attached
|
||||
const agent = await client.agents.retrieve(agentId);
|
||||
const agentToolIds = new Set(agent.tools?.map((t) => t.id) || []);
|
||||
setAttachedToolIds(agentToolIds);
|
||||
|
||||
setToolsPage(0);
|
||||
setToolsSelectedIndex(0);
|
||||
} catch (err) {
|
||||
setToolsError(
|
||||
err instanceof Error ? err.message : "Failed to load tools",
|
||||
);
|
||||
setTools([]);
|
||||
} finally {
|
||||
setToolsLoading(false);
|
||||
}
|
||||
},
|
||||
[agentId],
|
||||
);
|
||||
|
||||
// Refresh tools from MCP server
|
||||
const refreshToolsFromServer = useCallback(async () => {
|
||||
if (!viewingServer?.id) return;
|
||||
|
||||
|
||||
setToolsLoading(true);
|
||||
setToolsError(null);
|
||||
|
||||
|
||||
try {
|
||||
const client = await getClient();
|
||||
|
||||
|
||||
// Call refresh endpoint to sync tools from the MCP server
|
||||
await client.mcpServers.refresh(viewingServer.id, { agent_id: agentId });
|
||||
|
||||
|
||||
// Reload tools list
|
||||
const toolsList = await client.mcpServers.tools.list(viewingServer.id);
|
||||
setTools(toolsList);
|
||||
|
||||
|
||||
// Refresh agent's current tools
|
||||
const agent = await client.agents.retrieve(agentId);
|
||||
const agentToolIds = new Set(agent.tools?.map(t => t.id) || []);
|
||||
const agentToolIds = new Set(agent.tools?.map((t) => t.id) || []);
|
||||
setAttachedToolIds(agentToolIds);
|
||||
|
||||
|
||||
setToolsPage(0);
|
||||
setToolsSelectedIndex(0);
|
||||
|
||||
|
||||
// Clear error if successful
|
||||
if (toolsList.length === 0) {
|
||||
setToolsError("Server refreshed but no tools available.");
|
||||
}
|
||||
} catch (err) {
|
||||
setToolsError(
|
||||
err instanceof Error ? `Failed to refresh: ${err.message}` : "Failed to refresh tools",
|
||||
err instanceof Error
|
||||
? `Failed to refresh: ${err.message}`
|
||||
: "Failed to refresh tools",
|
||||
);
|
||||
} finally {
|
||||
setToolsLoading(false);
|
||||
@@ -187,50 +193,55 @@ export const McpSelector = memo(function McpSelector({
|
||||
}, [agentId, viewingServer]);
|
||||
|
||||
// Toggle tool attachment
|
||||
const toggleTool = useCallback(async (tool: Tool) => {
|
||||
setIsTogglingTool(true);
|
||||
try {
|
||||
const client = await getClient();
|
||||
const isAttached = attachedToolIds.has(tool.id);
|
||||
|
||||
if (isAttached) {
|
||||
// Detach tool
|
||||
await client.agents.tools.detach(tool.id, { agent_id: agentId });
|
||||
} else {
|
||||
// Attach tool
|
||||
await client.agents.tools.attach(tool.id, { agent_id: agentId });
|
||||
const toggleTool = useCallback(
|
||||
async (tool: Tool) => {
|
||||
setIsTogglingTool(true);
|
||||
try {
|
||||
const client = await getClient();
|
||||
const isAttached = attachedToolIds.has(tool.id);
|
||||
|
||||
if (isAttached) {
|
||||
// Detach tool
|
||||
await client.agents.tools.detach(tool.id, { agent_id: agentId });
|
||||
} else {
|
||||
// Attach tool
|
||||
await client.agents.tools.attach(tool.id, { agent_id: agentId });
|
||||
}
|
||||
|
||||
// Fetch agent's current tools to get accurate total count
|
||||
const agent = await client.agents.retrieve(agentId);
|
||||
const agentToolIds = new Set(agent.tools?.map((t) => t.id) || []);
|
||||
setAttachedToolIds(agentToolIds);
|
||||
} catch (err) {
|
||||
setToolsError(
|
||||
err instanceof Error
|
||||
? err.message
|
||||
: "Failed to toggle tool attachment",
|
||||
);
|
||||
} finally {
|
||||
setIsTogglingTool(false);
|
||||
}
|
||||
|
||||
// Fetch agent's current tools to get accurate total count
|
||||
const agent = await client.agents.retrieve(agentId);
|
||||
const agentToolIds = new Set(agent.tools?.map(t => t.id) || []);
|
||||
setAttachedToolIds(agentToolIds);
|
||||
} catch (err) {
|
||||
setToolsError(
|
||||
err instanceof Error ? err.message : "Failed to toggle tool attachment",
|
||||
);
|
||||
} finally {
|
||||
setIsTogglingTool(false);
|
||||
}
|
||||
}, [agentId, attachedToolIds]);
|
||||
},
|
||||
[agentId, attachedToolIds],
|
||||
);
|
||||
|
||||
// Attach all tools
|
||||
const attachAllTools = useCallback(async () => {
|
||||
setIsTogglingTool(true);
|
||||
try {
|
||||
const client = await getClient();
|
||||
|
||||
|
||||
// Attach tools that aren't already attached
|
||||
const unattachedTools = tools.filter(t => !attachedToolIds.has(t.id));
|
||||
const unattachedTools = tools.filter((t) => !attachedToolIds.has(t.id));
|
||||
await Promise.all(
|
||||
unattachedTools.map(tool =>
|
||||
client.agents.tools.attach(tool.id, { agent_id: agentId })
|
||||
)
|
||||
unattachedTools.map((tool) =>
|
||||
client.agents.tools.attach(tool.id, { agent_id: agentId }),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
// Fetch agent's current tools to get accurate total count
|
||||
const agent = await client.agents.retrieve(agentId);
|
||||
const agentToolIds = new Set(agent.tools?.map(t => t.id) || []);
|
||||
const agentToolIds = new Set(agent.tools?.map((t) => t.id) || []);
|
||||
setAttachedToolIds(agentToolIds);
|
||||
} catch (err) {
|
||||
setToolsError(
|
||||
@@ -246,18 +257,18 @@ export const McpSelector = memo(function McpSelector({
|
||||
setIsTogglingTool(true);
|
||||
try {
|
||||
const client = await getClient();
|
||||
|
||||
|
||||
// Detach only the tools from this server that are currently attached
|
||||
const attachedTools = tools.filter(t => attachedToolIds.has(t.id));
|
||||
const attachedTools = tools.filter((t) => attachedToolIds.has(t.id));
|
||||
await Promise.all(
|
||||
attachedTools.map(tool =>
|
||||
client.agents.tools.detach(tool.id, { agent_id: agentId })
|
||||
)
|
||||
attachedTools.map((tool) =>
|
||||
client.agents.tools.detach(tool.id, { agent_id: agentId }),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
// Fetch agent's current tools to get accurate total count
|
||||
const agent = await client.agents.retrieve(agentId);
|
||||
const agentToolIds = new Set(agent.tools?.map(t => t.id) || []);
|
||||
const agentToolIds = new Set(agent.tools?.map((t) => t.id) || []);
|
||||
setAttachedToolIds(agentToolIds);
|
||||
} catch (err) {
|
||||
setToolsError(
|
||||
@@ -324,10 +335,13 @@ export const McpSelector = memo(function McpSelector({
|
||||
// Handle viewing tools mode
|
||||
if (mode === "viewing-tools") {
|
||||
if (isTogglingTool) return; // Prevent input during toggle
|
||||
|
||||
|
||||
const toolsTotalPages = Math.ceil(tools.length / TOOLS_DISPLAY_PAGE_SIZE);
|
||||
const toolsStartIndex = toolsPage * TOOLS_DISPLAY_PAGE_SIZE;
|
||||
const pageTools = tools.slice(toolsStartIndex, toolsStartIndex + TOOLS_DISPLAY_PAGE_SIZE);
|
||||
const pageTools = tools.slice(
|
||||
toolsStartIndex,
|
||||
toolsStartIndex + TOOLS_DISPLAY_PAGE_SIZE,
|
||||
);
|
||||
const selectedTool = pageTools[toolsSelectedIndex];
|
||||
|
||||
if (key.upArrow) {
|
||||
@@ -339,12 +353,17 @@ export const McpSelector = memo(function McpSelector({
|
||||
setToolsSelectedIndex((prev) => Math.max(0, prev - 1));
|
||||
}
|
||||
} else if (key.downArrow) {
|
||||
if (toolsSelectedIndex === pageTools.length - 1 && toolsPage < toolsTotalPages - 1) {
|
||||
if (
|
||||
toolsSelectedIndex === pageTools.length - 1 &&
|
||||
toolsPage < toolsTotalPages - 1
|
||||
) {
|
||||
// At bottom of page, go to next page
|
||||
setToolsPage((prev) => prev + 1);
|
||||
setToolsSelectedIndex(0);
|
||||
} else {
|
||||
setToolsSelectedIndex((prev) => Math.min(pageTools.length - 1, prev + 1));
|
||||
setToolsSelectedIndex((prev) =>
|
||||
Math.min(pageTools.length - 1, prev + 1),
|
||||
);
|
||||
}
|
||||
} else if ((key.return || input === " ") && selectedTool) {
|
||||
// Space or Enter to toggle selected tool
|
||||
@@ -378,7 +397,10 @@ export const McpSelector = memo(function McpSelector({
|
||||
setSelectedIndex((prev) => Math.max(0, prev - 1));
|
||||
}
|
||||
} else if (key.downArrow) {
|
||||
if (selectedIndex === pageServers.length - 1 && currentPage < totalPages - 1) {
|
||||
if (
|
||||
selectedIndex === pageServers.length - 1 &&
|
||||
currentPage < totalPages - 1
|
||||
) {
|
||||
// At bottom of page, go to next page
|
||||
setCurrentPage((prev) => prev + 1);
|
||||
setSelectedIndex(0);
|
||||
@@ -410,7 +432,10 @@ export const McpSelector = memo(function McpSelector({
|
||||
if (mode === "viewing-tools" && viewingServer) {
|
||||
const toolsTotalPages = Math.ceil(tools.length / TOOLS_DISPLAY_PAGE_SIZE);
|
||||
const toolsStartIndex = toolsPage * TOOLS_DISPLAY_PAGE_SIZE;
|
||||
const pageTools = tools.slice(toolsStartIndex, toolsStartIndex + TOOLS_DISPLAY_PAGE_SIZE);
|
||||
const pageTools = tools.slice(
|
||||
toolsStartIndex,
|
||||
toolsStartIndex + TOOLS_DISPLAY_PAGE_SIZE,
|
||||
);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column" gap={1}>
|
||||
@@ -466,11 +491,7 @@ export const McpSelector = memo(function McpSelector({
|
||||
const statusIndicator = isAttached ? "✓" : " ";
|
||||
|
||||
return (
|
||||
<Box
|
||||
key={tool.id}
|
||||
flexDirection="column"
|
||||
marginBottom={1}
|
||||
>
|
||||
<Box key={tool.id} flexDirection="column" marginBottom={1}>
|
||||
{/* Row 1: Selection indicator, attachment status, and tool name */}
|
||||
<Box flexDirection="row">
|
||||
<Text
|
||||
@@ -510,24 +531,32 @@ export const McpSelector = memo(function McpSelector({
|
||||
)}
|
||||
|
||||
{/* Footer with pagination and controls */}
|
||||
{!toolsLoading && !toolsError && tools.length > 0 && (() => {
|
||||
const attachedFromThisServer = tools.filter(t => attachedToolIds.has(t.id)).length;
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<Box>
|
||||
<Text dimColor>
|
||||
{toolsTotalPages > 1 && `Page ${toolsPage + 1}/${toolsTotalPages} · `}
|
||||
{attachedFromThisServer}/{tools.length} attached from server · {attachedToolIds.size} total on agent
|
||||
</Text>
|
||||
{!toolsLoading &&
|
||||
!toolsError &&
|
||||
tools.length > 0 &&
|
||||
(() => {
|
||||
const attachedFromThisServer = tools.filter((t) =>
|
||||
attachedToolIds.has(t.id),
|
||||
).length;
|
||||
return (
|
||||
<Box flexDirection="column" marginTop={1}>
|
||||
<Box>
|
||||
<Text dimColor>
|
||||
{toolsTotalPages > 1 &&
|
||||
`Page ${toolsPage + 1}/${toolsTotalPages} · `}
|
||||
{attachedFromThisServer}/{tools.length} attached from server
|
||||
· {attachedToolIds.size} total on agent
|
||||
</Text>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text dimColor>
|
||||
↑↓ navigate · Space/Enter toggle · A attach all · D detach
|
||||
all · R refresh · Esc back
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>
|
||||
<Text dimColor>
|
||||
↑↓ navigate · Space/Enter toggle · A attach all · D detach all · R refresh · Esc back
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
})()}
|
||||
);
|
||||
})()}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -676,7 +705,8 @@ export const McpSelector = memo(function McpSelector({
|
||||
)}
|
||||
<Box>
|
||||
<Text dimColor>
|
||||
↑↓ navigate · Enter view tools · A add · D delete · R refresh · Esc close
|
||||
↑↓ navigate · Enter view tools · A add · D delete · R refresh ·
|
||||
Esc close
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
Reference in New Issue
Block a user