/ src / tools / MCPTool / MCPTool.tsx
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>&nbsp;&nbsp;⎿ &nbsp;</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>&nbsp;&nbsp;⎿ &nbsp;</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>