MCPTool.tsx
1 import { Box, Text } from 'ink' 2 import * as React from 'react' 3 import { z } from 'zod' 4 import { FallbackToolUseRejectedMessage } from '../../components/FallbackToolUseRejectedMessage.js' 5 import { type Tool } from '../../Tool.js' 6 import { getTheme } from '../../utils/theme.js' 7 import { DESCRIPTION, PROMPT } from './prompt.js' 8 import { OutputLine } from '../BashTool/OutputLine.js' 9 10 // Allow any input object since MCP tools define their own schemas 11 const inputSchema = z.object({}).passthrough() 12 13 export const MCPTool = { 14 async isEnabled() { 15 return true 16 }, 17 isReadOnly() { 18 return false 19 }, 20 // Overridden in mcpClient.ts 21 name: 'mcp', 22 // Overridden in mcpClient.ts 23 async description() { 24 return DESCRIPTION 25 }, 26 // Overridden in mcpClient.ts 27 async prompt() { 28 return PROMPT 29 }, 30 inputSchema, 31 // Overridden in mcpClient.ts 32 async *call() { 33 yield { 34 type: 'result', 35 data: '', 36 resultForAssistant: '', 37 } 38 }, 39 needsPermissions() { 40 return true 41 }, 42 renderToolUseMessage(input) { 43 return Object.entries(input) 44 .map(([key, value]) => `${key}: ${JSON.stringify(value)}`) 45 .join(', ') 46 }, 47 // Overridden in mcpClient.ts 48 userFacingName: () => 'mcp', 49 renderToolUseRejectedMessage() { 50 return <FallbackToolUseRejectedMessage /> 51 }, 52 renderToolResultMessage(output, { verbose }) { 53 if (Array.isArray(output)) { 54 return ( 55 <Box flexDirection="column"> 56 {output.map((item, i) => { 57 if (item.type === 'image') { 58 return ( 59 <Box 60 key={i} 61 justifyContent="space-between" 62 overflowX="hidden" 63 width="100%" 64 > 65 <Box flexDirection="row"> 66 <Text> ⎿ </Text> 67 <Text>[Image]</Text> 68 </Box> 69 </Box> 70 ) 71 } 72 const lines = item.text.split('\n').length 73 return ( 74 <OutputLine 75 key={i} 76 content={item.text} 77 lines={lines} 78 verbose={verbose} 79 /> 80 ) 81 })} 82 </Box> 83 ) 84 } 85 86 if (!output) { 87 return ( 88 <Box justifyContent="space-between" overflowX="hidden" width="100%"> 89 <Box flexDirection="row"> 90 <Text> ⎿ </Text> 91 <Text color={getTheme().secondaryText}>(No content)</Text> 92 </Box> 93 </Box> 94 ) 95 } 96 97 const lines = output.split('\n').length 98 return <OutputLine content={output} lines={lines} verbose={verbose} /> 99 }, 100 renderResultForAssistant(content) { 101 return content 102 }, 103 } satisfies Tool<typeof inputSchema, string>