{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "mcp-tool",
  "type": "registry:ui",
  "dependencies": [
    "@base-ui/react",
    "@streamdown/code",
    "@tabler/icons-react",
    "clsx",
    "streamdown",
    "tailwind-merge"
  ],
  "registryDependencies": [
    "https://agent-elements.21st.dev/r/text-shimmer.json"
  ],
  "files": [
    {
      "path": "registry/agent-elements/components/tools/mcp-tool.tsx",
      "content": "import { memo, useMemo } from \"react\";\nimport { Streamdown } from \"streamdown\";\nimport { createCodePlugin } from \"@streamdown/code\";\nimport { getToolStatus, areToolPropsEqual } from \"../utils/format-tool\";\nimport type { McpToolInfo } from \"./tool-registry\";\nimport { ToolRowBase } from \"./tool-row-base\";\n\nexport type McpToolProps = {\n  part: any;\n  mcpInfo: McpToolInfo;\n  chatStatus?: string;\n  defaultOpen?: boolean;\n};\n\nconst PRIORITY_ARGS = [\n  \"query\",\n  \"question\",\n  \"email\",\n  \"name\",\n  \"id\",\n  \"customer\",\n  \"url\",\n  \"issue\",\n  \"body\",\n  \"summary\",\n  \"title\",\n];\n\nconst ACTIVE_VERBS: Record<string, string> = {\n  List: \"Listing\",\n  Get: \"Getting\",\n  Create: \"Creating\",\n  Update: \"Updating\",\n  Delete: \"Deleting\",\n  Search: \"Searching\",\n  Fetch: \"Fetching\",\n  Retrieve: \"Retrieving\",\n  Send: \"Sending\",\n  Generate: \"Generating\",\n  Add: \"Adding\",\n  Remove: \"Removing\",\n  Modify: \"Modifying\",\n  Draft: \"Drafting\",\n  Manage: \"Managing\",\n  Query: \"Querying\",\n  Start: \"Starting\",\n  Set: \"Setting\",\n  Check: \"Checking\",\n  Find: \"Finding\",\n};\n\nconst COMPLETED_VERBS: Record<string, string> = {\n  List: \"Listed\",\n  Get: \"Got\",\n  Create: \"Created\",\n  Update: \"Updated\",\n  Delete: \"Deleted\",\n  Search: \"Searched\",\n  Fetch: \"Fetched\",\n  Retrieve: \"Retrieved\",\n  Send: \"Sent\",\n  Generate: \"Generated\",\n  Add: \"Added\",\n  Remove: \"Removed\",\n  Modify: \"Modified\",\n  Draft: \"Drafted\",\n  Manage: \"Managed\",\n  Query: \"Queried\",\n  Start: \"Started\",\n  Set: \"Set\",\n  Check: \"Checked\",\n  Find: \"Found\",\n};\n\nfunction getActiveTitle(info: McpToolInfo): string {\n  const words = info.displayName.split(\" \");\n  const verb = words[0];\n  const rest = words.slice(1).join(\" \");\n  const active = ACTIVE_VERBS[verb];\n  if (active) return rest ? `${active} ${rest}` : active;\n  return info.displayName;\n}\n\nfunction getCompletedTitle(info: McpToolInfo): string {\n  const words = info.displayName.split(\" \");\n  const verb = words[0];\n  const rest = words.slice(1).join(\" \");\n  const completed = COMPLETED_VERBS[verb];\n  return completed\n    ? rest\n      ? `${completed} ${rest}`\n      : completed\n    : info.displayName;\n}\n\nfunction formatMcpArgs(input: any): string {\n  if (!input || typeof input !== \"object\") return \"\";\n  const entries = Object.entries(input).filter(\n    ([, v]) => v !== undefined && v !== null && v !== \"\",\n  );\n  if (entries.length === 0) return \"\";\n\n  const sorted = [...entries].sort(([a], [b]) => {\n    const ai = PRIORITY_ARGS.indexOf(a);\n    const bi = PRIORITY_ARGS.indexOf(b);\n    if (ai !== -1 && bi !== -1) return ai - bi;\n    if (ai !== -1) return -1;\n    if (bi !== -1) return 1;\n    return 0;\n  });\n\n  const parts: string[] = [];\n  for (const [key, value] of sorted) {\n    if (parts.length >= 2) break;\n    const val = typeof value === \"string\" ? value : JSON.stringify(value);\n    const display = val.length > 30 ? val.slice(0, 27) + \"...\" : val;\n    parts.push(`${key}: ${display}`);\n  }\n  return parts.join(\"  \");\n}\n\nexport function unwrapMcpOutput(output: any): any {\n  if (!output) return output;\n  if (Array.isArray(output)) {\n    const textParts: string[] = [];\n    for (const block of output) {\n      if (block?.type === \"text\" && typeof block?.text === \"string\") {\n        textParts.push(block.text);\n      }\n    }\n    if (textParts.length > 0) {\n      const combined = textParts.join(\"\");\n      try {\n        return JSON.parse(combined);\n      } catch {\n        return combined;\n      }\n    }\n    return output;\n  }\n  if (output?.type === \"text\" && typeof output?.text === \"string\") {\n    try {\n      return JSON.parse(output.text);\n    } catch {\n      return output.text;\n    }\n  }\n  if (typeof output === \"string\") {\n    try {\n      return JSON.parse(output);\n    } catch {\n      return output;\n    }\n  }\n  return output;\n}\n\nfunction formatOutputForDisplay(output: any): string {\n  const unwrapped = unwrapMcpOutput(output);\n  if (typeof unwrapped === \"string\") {\n    return unwrapped.length > 3000\n      ? unwrapped.slice(0, 3000) + \"\\n...\"\n      : unwrapped;\n  }\n  const text = JSON.stringify(unwrapped, null, 2);\n  return text.length > 3000 ? text.slice(0, 3000) + \"\\n...\" : text;\n}\n\nconst code = createCodePlugin({\n  themes: [\"github-light\", \"github-dark\"],\n});\n\nexport const McpTool = memo(function McpTool({\n  part,\n  mcpInfo,\n  chatStatus,\n  defaultOpen,\n}: McpToolProps) {\n  const { isPending, isInterrupted } = getToolStatus(part, chatStatus);\n\n  const title = useMemo(() => {\n    if (part.state === \"input-streaming\")\n      return `Preparing ${mcpInfo.displayName}`;\n    if (isPending) return getActiveTitle(mcpInfo);\n    return getCompletedTitle(mcpInfo);\n  }, [part.state, isPending, mcpInfo]);\n\n  const subtitle = useMemo(() => {\n    if (part.state === \"input-streaming\") return \"\";\n    return formatMcpArgs(part.input);\n  }, [part.input, part.state]);\n\n  const displayOutput = useMemo(() => {\n    if (!part.output) return null;\n    return formatOutputForDisplay(part.output);\n  }, [part.output]);\n\n  const codeBlock = useMemo(() => {\n    if (!displayOutput) return null;\n    const trimmed = displayOutput.trim();\n    if (!trimmed) return null;\n    const language =\n      trimmed.startsWith(\"{\") || trimmed.startsWith(\"[\") ? \"json\" : \"text\";\n    return `\\`\\`\\`${language}\\n${displayOutput}\\n\\`\\`\\``;\n  }, [displayOutput]);\n\n  const hasExpandableContent = !!codeBlock && !isPending;\n\n  if (isInterrupted && !part.output) {\n    return (\n      <span className=\"text-sm text-an-tool-color-muted\">\n        {mcpInfo.displayName} interrupted\n      </span>\n    );\n  }\n\n  return (\n    <div className=\"an-tool-mcp\">\n      <ToolRowBase\n        shimmerLabel={title}\n        completeLabel={title}\n        isAnimating={isPending}\n        detail={subtitle || undefined}\n        trailingContent={undefined}\n        expandable={hasExpandableContent}\n        defaultOpen={defaultOpen}\n      >\n        {codeBlock && (\n          <div className=\"an-markdown text-[12px]\">\n            <Streamdown plugins={{ code }} controls={{ code: false }}>\n              {codeBlock}\n            </Streamdown>\n          </div>\n        )}\n      </ToolRowBase>\n    </div>\n  );\n}, areToolPropsEqual);\n",
      "type": "registry:ui",
      "target": "components/agent-elements/tools/mcp-tool.tsx"
    },
    {
      "path": "registry/agent-elements/utils/format-tool.ts",
      "content": "/**\n * Tool state cache for detecting AI SDK in-place mutations.\n * AI SDK mutates objects in-place during streaming, so we must\n * cache state externally and compare cached values.\n */\n\ntype CachedToolState = {\n  state: string | undefined;\n  inputJson: string;\n  outputJson: string;\n};\n\nconst toolStateCache = new Map<string, CachedToolState>();\n\nfunction getToolStateSnapshot(part: any): CachedToolState {\n  return {\n    state: part.state,\n    inputJson: JSON.stringify(part.input || {}),\n    outputJson: JSON.stringify(part.output || {}),\n  };\n}\n\nfunction hasToolStateChanged(toolCallId: string, part: any): boolean {\n  const cached = toolStateCache.get(toolCallId);\n  const current = getToolStateSnapshot(part);\n\n  if (!cached) {\n    toolStateCache.set(toolCallId, current);\n    return true;\n  }\n\n  const changed =\n    cached.state !== current.state ||\n    cached.inputJson !== current.inputJson ||\n    cached.outputJson !== current.outputJson;\n\n  if (changed) {\n    toolStateCache.set(toolCallId, current);\n  }\n\n  return changed;\n}\n\nfunction arePartsEqual(prev: any, next: any): boolean {\n  if (prev.toolCallId !== next.toolCallId) return false;\n  if (prev.type !== next.type) return false;\n\n  const toolCallId = next.toolCallId;\n  if (!toolCallId) {\n    return prev.state === next.state;\n  }\n\n  const changed = hasToolStateChanged(toolCallId, next);\n  return !changed;\n}\n\nfunction isToolCompleted(part: any): boolean {\n  if (part.output !== undefined && part.output !== null) return true;\n  if (part.state === \"error\") return true;\n  if (part.state === \"result\") return true;\n  return false;\n}\n\n/** Deep compare function for tool part props. Used with React.memo(). */\nexport function areToolPropsEqual(\n  prevProps: { part: any; chatStatus?: string },\n  nextProps: { part: any; chatStatus?: string },\n): boolean {\n  const partsEqual = arePartsEqual(prevProps.part, nextProps.part);\n  if (!partsEqual) return false;\n  if (isToolCompleted(nextProps.part)) return true;\n  if (prevProps.chatStatus !== nextProps.chatStatus) return false;\n  return true;\n}\n\n/** Get tool status from part state */\nexport function getToolStatus(part: any, chatStatus?: string) {\n  const basePending =\n    part.state !== \"output-available\" && part.state !== \"output-error\";\n  const isError =\n    part.state === \"output-error\" ||\n    (part.state === \"output-available\" && part.output?.success === false);\n  const isSuccess = part.state === \"output-available\" && !isError;\n  const isPending = basePending && chatStatus === \"streaming\";\n  const isInterrupted =\n    basePending && chatStatus !== \"streaming\" && chatStatus !== undefined;\n\n  return { isPending, isError, isSuccess, isInterrupted };\n}\n",
      "type": "registry:lib",
      "target": "components/agent-elements/utils/format-tool.ts"
    },
    {
      "path": "registry/agent-elements/components/tools/tool-registry.ts",
      "content": "import type React from \"react\";\nimport {\n  IconSearch as Search,\n  IconEye as Eye,\n  IconFolderSearch as FolderSearch,\n  IconGitBranch as GitBranch,\n  IconTerminal2 as Terminal,\n  IconCircleX as XCircle,\n  IconFileCode as FileCode2,\n  IconSparkles as Sparkles,\n  IconGlobe as Globe,\n  IconFilePlus as FilePlus,\n  IconChecklist as ListTodo,\n  IconLogout as LogOut,\n} from \"@tabler/icons-react\";\n\nexport type ToolVariant = \"simple\" | \"collapsible\";\n\nexport type ToolMeta = {\n  icon: React.ComponentType<{ className?: string }>;\n  title: (part: any) => string;\n  subtitle?: (part: any) => string;\n  variant: ToolVariant;\n};\n\nfunction getDisplayPath(filePath: string): string {\n  if (!filePath) return \"\";\n  const prefixes = [\n    \"/project/sandbox/repo/\",\n    \"/project/sandbox/\",\n    \"/project/\",\n    \"/workspace/\",\n  ];\n  for (const prefix of prefixes) {\n    if (filePath.startsWith(prefix)) return filePath.slice(prefix.length);\n  }\n  const worktreeMatch = filePath.match(\n    /\\.21st\\/worktrees\\/[^/]+\\/[^/]+\\/(.+)$/,\n  );\n  if (worktreeMatch) return worktreeMatch[1]!;\n  if (filePath.startsWith(\"/\")) {\n    const parts = filePath.split(\"/\");\n    const rootIndicators = [\"apps\", \"packages\", \"src\", \"lib\", \"components\"];\n    const rootIndex = parts.findIndex((p) => rootIndicators.includes(p));\n    if (rootIndex > 0) return parts.slice(rootIndex).join(\"/\");\n  }\n  return filePath;\n}\n\nfunction calculateDiffStats(oldString: string, newString: string) {\n  const oldLines = oldString.split(\"\\n\");\n  const newLines = newString.split(\"\\n\");\n  const maxLines = Math.max(oldLines.length, newLines.length);\n  let addedLines = 0;\n  let removedLines = 0;\n  for (let i = 0; i < maxLines; i++) {\n    if (oldLines[i] !== undefined && newLines[i] !== undefined) {\n      if (oldLines[i] !== newLines[i]) {\n        removedLines++;\n        addedLines++;\n      }\n    } else if (oldLines[i] !== undefined) {\n      removedLines++;\n    } else if (newLines[i] !== undefined) {\n      addedLines++;\n    }\n  }\n  return { addedLines, removedLines };\n}\n\nexport const toolRegistry: Record<string, ToolMeta> = {\n  \"tool-Task\": {\n    icon: Sparkles,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      const subagentType = part.input?.subagent_type || \"Agent\";\n      return isPending\n        ? `Running ${subagentType}`\n        : `${subagentType} completed`;\n    },\n    subtitle: (part) => {\n      const desc = part.input?.description || \"\";\n      return desc.length > 50 ? desc.slice(0, 47) + \"...\" : desc;\n    },\n    variant: \"simple\",\n  },\n  // Agent tool — renamed from \"Task\" in claude-agent-sdk 0.2.63+\n  \"tool-Agent\": {\n    icon: Sparkles,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      const subagentType = part.input?.subagent_type || \"Agent\";\n      return isPending\n        ? `Running ${subagentType}`\n        : `${subagentType} completed`;\n    },\n    subtitle: (part) => {\n      const desc = part.input?.description || \"\";\n      return desc.length > 50 ? desc.slice(0, 47) + \"...\" : desc;\n    },\n    variant: \"simple\",\n  },\n  \"tool-Skill\": {\n    icon: Sparkles,\n    title: () => \"Skill\",\n    subtitle: (part) => part.input?.skill || \"\",\n    variant: \"simple\",\n  },\n  \"tool-Grep\": {\n    icon: Search,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      if (isPending) return \"Grepping\";\n      const numFiles = part.output?.numFiles || 0;\n      return numFiles > 0 ? `Grepped ${numFiles} files` : \"No matches\";\n    },\n    subtitle: (part) => {\n      const pattern = part.input?.pattern || \"\";\n      const path = part.input?.path || \"\";\n      if (path) {\n        const combined = `${pattern} in ${getDisplayPath(path)}`;\n        return combined.length > 40 ? combined.slice(0, 37) + \"...\" : combined;\n      }\n      return pattern.length > 40 ? pattern.slice(0, 37) + \"...\" : pattern;\n    },\n    variant: \"simple\",\n  },\n  \"tool-Glob\": {\n    icon: FolderSearch,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      if (isPending) return \"Exploring files\";\n      const numFiles = part.output?.numFiles || 0;\n      return numFiles > 0 ? `Found ${numFiles} files` : \"No files found\";\n    },\n    subtitle: (part) => {\n      const pattern = part.input?.pattern || \"\";\n      return pattern.length > 40 ? pattern.slice(0, 37) + \"...\" : pattern;\n    },\n    variant: \"simple\",\n  },\n  \"tool-Read\": {\n    icon: Eye,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      return isPending ? \"Reading\" : \"Read\";\n    },\n    subtitle: (part) => {\n      const filePath = part.input?.file_path || \"\";\n      if (!filePath) return \"\";\n      return filePath.split(\"/\").pop() || \"\";\n    },\n    variant: \"simple\",\n  },\n  \"tool-Edit\": {\n    icon: FileCode2,\n    title: (part) => {\n      const filePath = part.input?.file_path || \"\";\n      if (!filePath) return \"Edit\";\n      return filePath.split(\"/\").pop() || \"Edit\";\n    },\n    subtitle: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      if (isPending) return \"\";\n      const oldString = part.input?.old_string || \"\";\n      const newString = part.input?.new_string || \"\";\n      if (!oldString && !newString) return \"\";\n      if (oldString !== newString) {\n        const { addedLines, removedLines } = calculateDiffStats(\n          oldString,\n          newString,\n        );\n        return `+${addedLines} -${removedLines}`;\n      }\n      return \"\";\n    },\n    variant: \"simple\",\n  },\n  \"tool-Write\": {\n    icon: FilePlus,\n    title: () => \"Create\",\n    subtitle: (part) => {\n      const filePath = part.input?.file_path || \"\";\n      if (!filePath) return \"\";\n      return filePath.split(\"/\").pop() || \"\";\n    },\n    variant: \"simple\",\n  },\n  \"tool-Bash\": {\n    icon: Terminal,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      return isPending ? \"Running command\" : \"Ran command\";\n    },\n    subtitle: (part) => {\n      const command = part.input?.command || \"\";\n      if (!command) return \"\";\n      let normalized = command.replace(/\\\\\\s*\\n\\s*/g, \" \").trim();\n      normalized = normalized.replace(\n        /\\/(?:Users|home|root)\\/[^\\s\"']+/g,\n        (match: string) => getDisplayPath(match),\n      );\n      return normalized.length > 50\n        ? normalized.slice(0, 47) + \"...\"\n        : normalized;\n    },\n    variant: \"simple\",\n  },\n  \"tool-WebFetch\": {\n    icon: Globe,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      return isPending ? \"Fetching\" : \"Fetched\";\n    },\n    subtitle: (part) => {\n      const url = part.input?.url || \"\";\n      try {\n        return new URL(url).hostname.replace(\"www.\", \"\");\n      } catch {\n        return url.slice(0, 30);\n      }\n    },\n    variant: \"simple\",\n  },\n  \"tool-WebSearch\": {\n    icon: Search,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      return isPending ? \"Searching web\" : \"Searched web\";\n    },\n    subtitle: (part) => {\n      const query = part.input?.query || \"\";\n      return query.length > 40 ? query.slice(0, 37) + \"...\" : query;\n    },\n    variant: \"collapsible\",\n  },\n  \"tool-TodoWrite\": {\n    icon: ListTodo,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      const action = part.input?.action || \"update\";\n      if (isPending) return action === \"add\" ? \"Adding todo\" : \"Updating todos\";\n      return action === \"add\" ? \"Added todo\" : \"Updated todos\";\n    },\n    subtitle: (part) => {\n      const todos = part.input?.todos || [];\n      if (todos.length === 0) return \"\";\n      return `${todos.length} ${todos.length === 1 ? \"item\" : \"items\"}`;\n    },\n    variant: \"simple\",\n  },\n  \"tool-PlanWrite\": {\n    icon: Sparkles,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      const action = part.input?.action || \"create\";\n      if (isPending) {\n        if (action === \"create\") return \"Creating plan\";\n        if (action === \"approve\") return \"Approving plan\";\n        return \"Updating plan\";\n      }\n      const status = part.input?.plan?.status;\n      if (status === \"awaiting_approval\") return \"Plan ready for review\";\n      if (status === \"approved\") return \"Plan approved\";\n      if (status === \"completed\") return \"Plan completed\";\n      return action === \"create\" ? \"Created plan\" : \"Updated plan\";\n    },\n    variant: \"simple\",\n  },\n  \"tool-ExitPlanMode\": {\n    icon: LogOut,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      return isPending ? \"Finishing plan\" : \"Plan complete\";\n    },\n    variant: \"simple\",\n  },\n  \"tool-NotebookEdit\": {\n    icon: FileCode2,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      return isPending ? \"Editing notebook\" : \"Edited notebook\";\n    },\n    subtitle: (part) => {\n      const filePath = part.input?.file_path || \"\";\n      if (!filePath) return \"\";\n      return filePath.split(\"/\").pop() || \"\";\n    },\n    variant: \"simple\",\n  },\n  \"tool-BashOutput\": {\n    icon: Terminal,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      return isPending ? \"Getting output\" : \"Command output\";\n    },\n    subtitle: (part) => {\n      const output = part.output;\n      if (typeof output === \"string\" && output.trim()) return output.trim();\n      const command = part.input?.command || \"\";\n      if (!command) return \"\";\n      let normalized = command.replace(/\\\\\\s*\\n\\s*/g, \" \").trim();\n      normalized = normalized.replace(\n        /\\/(?:Users|home|root)\\/[^\\s\"']+/g,\n        (match: string) => getDisplayPath(match),\n      );\n      return normalized.length > 50\n        ? normalized.slice(0, 47) + \"...\"\n        : normalized;\n    },\n    variant: \"simple\",\n  },\n  \"tool-KillShell\": {\n    icon: XCircle,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      return isPending ? \"Stopping shell\" : \"Shell stopped\";\n    },\n    subtitle: (part) => {\n      const pid = part.input?.pid;\n      return typeof pid === \"number\" ? `pid ${pid}` : \"\";\n    },\n    variant: \"simple\",\n  },\n  \"tool-cloning\": {\n    icon: GitBranch,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      return isPending ? \"Cloning repo\" : \"Repo cloned\";\n    },\n    subtitle: (part) => part.input?.repo ?? \"\",\n    variant: \"simple\",\n  },\n  \"tool-Thinking\": {\n    icon: Sparkles,\n    title: (part) => {\n      const isPending =\n        part.state !== \"output-available\" && part.state !== \"output-error\";\n      return isPending ? \"Thinking...\" : \"Thought\";\n    },\n    variant: \"collapsible\",\n  },\n};\n\n// MCP tool parsing\nconst MCP_TOOL_PREFIX = \"tool-mcp__\";\n\nexport type McpToolInfo = {\n  serverName: string;\n  toolName: string;\n  displayName: string;\n  category: string;\n};\n\nconst BUILTIN_MCP_TOOLS: Record<string, McpToolInfo> = {\n  \"tool-ListMcpResources\": {\n    serverName: \"mcp\",\n    toolName: \"list_resources\",\n    displayName: \"List Resources\",\n    category: \"list\",\n  },\n  \"tool-ListMcpResourcesTool\": {\n    serverName: \"mcp\",\n    toolName: \"list_resources\",\n    displayName: \"List Resources\",\n    category: \"list\",\n  },\n  \"tool-ReadMcpResource\": {\n    serverName: \"mcp\",\n    toolName: \"read_resource\",\n    displayName: \"Read Resource\",\n    category: \"get\",\n  },\n  \"tool-ReadMcpResourceTool\": {\n    serverName: \"mcp\",\n    toolName: \"read_resource\",\n    displayName: \"Read Resource\",\n    category: \"get\",\n  },\n};\n\nexport function parseMcpToolType(partType: string): McpToolInfo | null {\n  const builtin = BUILTIN_MCP_TOOLS[partType];\n  if (builtin) return builtin;\n  if (!partType.startsWith(MCP_TOOL_PREFIX)) return null;\n  const withoutPrefix = partType.slice(MCP_TOOL_PREFIX.length);\n  const separatorIndex = withoutPrefix.indexOf(\"__\");\n  if (separatorIndex === -1) return null;\n  const serverName = withoutPrefix.slice(0, separatorIndex);\n  const toolName = withoutPrefix.slice(separatorIndex + 2);\n  return {\n    serverName,\n    toolName,\n    displayName: toolName\n      .replace(/_/g, \" \")\n      .replace(/\\b\\w/g, (c) => c.toUpperCase())\n      .trim(),\n    category: \"other\",\n  };\n}\n",
      "type": "registry:lib",
      "target": "components/agent-elements/tools/tool-registry.ts"
    },
    {
      "path": "registry/agent-elements/components/tools/tool-row-base.tsx",
      "content": "import type { ReactNode } from \"react\";\nimport { Collapsible } from \"@base-ui/react/collapsible\";\nimport { TextShimmer } from \"../text-shimmer\";\nimport { IconChevronRight } from \"@tabler/icons-react\";\nimport { cn } from \"../utils/cn\";\n\nexport type ToolRowBaseProps = {\n  icon?: ReactNode;\n  shimmerLabel?: string;\n  completeLabel: string;\n  isAnimating: boolean;\n  detail?: string;\n  trailingContent?: ReactNode;\n  expandable?: boolean;\n  expanded?: boolean;\n  defaultOpen?: boolean;\n  onToggleExpand?: () => void;\n  children?: ReactNode;\n};\n\nexport function ToolRowBase({\n  icon,\n  shimmerLabel,\n  completeLabel,\n  isAnimating,\n  detail,\n  trailingContent,\n  expandable = false,\n  expanded,\n  defaultOpen = false,\n  onToggleExpand,\n  children,\n}: ToolRowBaseProps) {\n  const isComplete = !isAnimating;\n  const isExpanded = expanded ?? false;\n  const canToggle = expandable && (isComplete || isExpanded || isAnimating);\n\n  const row = (\n    <div\n      className={cn(\n        \"flex items-center max-w-full select-none gap-1 rounded-an-tool-border-radius\",\n        canToggle ? \"cursor-pointer\" : \"cursor-default\",\n      )}\n    >\n      <div className=\"flex items-center gap-2 min-w-0 text-sm text-muted-foreground\">\n        {icon && (\n          <span className=\"flex items-center justify-center size-3 shrink-0\">\n            {icon}\n          </span>\n        )}\n        <span className=\"font-[450] whitespace-nowrap shrink-0\">\n          {isAnimating && shimmerLabel ? (\n            <TextShimmer\n              as=\"span\"\n              duration={1.2}\n              className=\"inline-flex items-center leading-none h-4 m-0\"\n            >\n              {shimmerLabel}\n            </TextShimmer>\n          ) : (\n            completeLabel\n          )}\n        </span>\n        {detail && (\n          <span className=\"font-normal truncate min-w-0 flex-1 text-an-foreground-muted/60\">\n            {detail}\n          </span>\n        )}\n        {trailingContent}\n      </div>\n      {expandable && (isComplete || isExpanded || isAnimating) && (\n        <div>\n          <IconChevronRight\n            className={cn(\n              \"shrink-0 text-muted-foreground transition-transform duration-150 ease-out\",\n              \"size-3\",\n              \"rotate-0 group-data-panel-open:rotate-90\",\n            )}\n          />\n        </div>\n      )}\n    </div>\n  );\n\n  if (!expandable) {\n    return <div className=\"flex flex-col gap-1\">{row}</div>;\n  }\n\n  const rootProps =\n    expanded === undefined\n      ? { defaultOpen }\n      : { open: expanded, onOpenChange: onToggleExpand };\n\n  return (\n    <Collapsible.Root className=\"flex flex-col gap-2 w-full\" {...rootProps}>\n      <Collapsible.Trigger\n        className=\"group flex\"\n        disabled={!canToggle}\n        aria-disabled={!canToggle}\n      >\n        {row}\n      </Collapsible.Trigger>\n      <Collapsible.Panel\n        className={cn(\n          \"overflow-hidden\",\n          \"h-[var(--collapsible-panel-height)] transition-all duration-150 ease-out\",\n          \"data-ending-style:h-0 data-starting-style:h-0\",\n          \"[&[hidden]:not([hidden='until-found'])]:hidden\",\n        )}\n      >\n        {children}\n      </Collapsible.Panel>\n    </Collapsible.Root>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/agent-elements/tools/tool-row-base.tsx"
    },
    {
      "path": "registry/agent-elements/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n",
      "type": "registry:lib",
      "target": "components/agent-elements/utils/cn.ts"
    },
    {
      "path": "registry/agent-elements/agent-ui.css",
      "content": "/* Agent Elements tokens + utilities */\n\n/*\n * AN Agent Chat - CSS Custom Properties\n * These are the default values. Override by redefining these vars in your app CSS.\n */\n:root {\n  /* Geometry — all radii derive from --an-border-radius */\n  --an-border-radius: 16px;\n  --an-message-border-radius: var(--an-border-radius);\n  --an-input-border-radius: var(--an-border-radius);\n  --an-tool-border-radius: 10px;\n  --an-message-radius-inner-offset: 4px;\n  --an-max-width: 420px;\n\n  /* Colors - Light mode defaults */\n  --an-background: #ffffff;\n  --an-background-secondary: #f0f0f0;\n  --an-background-tertiary: #f8f8f8;\n  --an-foreground: #1a1a1a;\n  --an-foreground-muted: #737373;\n  --an-foreground-subtle: #a3a3a3;\n  --an-border-color: #e4e4e7;\n  --an-primary-color: #3b82f6;\n\n  /* User Messages */\n  --an-user-message-bg: #f5f5f5;\n  --an-user-message-text: #1a1a1a;\n\n  /* Input */\n  --an-input-background: #ffffff;\n  --an-input-border-color: #e4e4e7;\n  --an-input-color: #1a1a1a;\n  --an-input-placeholder-color: #a3a3a3;\n  --an-input-focus-outline: transparent;\n  --an-context-padding: 10px;\n\n  /* Send/Stop Buttons */\n  --an-send-button-bg: #3b82f6;\n  --an-send-button-color: #ffffff;\n\n  /* Tools */\n  --an-tool-background: #f5f5f5;\n  --an-tool-border-color: #e4e4e7;\n  --an-tool-color: #1a1a1a;\n  --an-tool-color-muted: #737373;\n\n  /* Code */\n  --an-code-background: #1e1e1e;\n  --an-code-color: #d4d4d4;\n\n  /* Diff colors */\n  --an-diff-added-bg: rgba(34, 197, 94, 0.1);\n  --an-diff-added-border: rgba(34, 197, 94, 0.5);\n  --an-diff-added-text: #15803d;\n  --an-diff-removed-bg: rgba(239, 68, 68, 0.1);\n  --an-diff-removed-border: rgba(239, 68, 68, 0.5);\n  --an-diff-removed-text: #dc2626;\n}\n\n/* Dark mode defaults */\n.dark {\n  --an-background: #0a0a0a;\n  --an-background-secondary: #242424;\n  --an-background-tertiary: #141414;\n  --an-foreground: #fafafa;\n  --an-foreground-muted: #8c8c8c;\n  --an-foreground-subtle: #71717a;\n  --an-border-color: #2a2a2a;\n  --an-primary-color: #60a5fa;\n\n  --an-user-message-bg: #1a1a1a;\n  --an-user-message-text: #fafafa;\n\n  --an-input-background: #0a0a0a;\n  --an-input-border-color: #2a2a2a;\n  --an-input-color: #fafafa;\n  --an-input-placeholder-color: #71717a;\n  --an-input-focus-outline: transparent;\n  --an-context-padding: 12px;\n\n  --an-send-button-bg: #60a5fa;\n  /* Dark mode uses a lighter primary (#60a5fa) — pair it with black text so\n     labels like \"Approve\" / send-arrow have proper contrast instead of\n     low-contrast white-on-light-blue. */\n  --an-send-button-color: #0a0a0a;\n\n  --an-tool-background: #141414;\n  --an-tool-border-color: #2a2a2a;\n  --an-tool-color: #fafafa;\n  --an-tool-color-muted: #8c8c8c;\n\n  --an-code-background: #0a0a0a;\n  --an-code-color: #d4d4d4;\n\n  --an-diff-added-bg: rgba(34, 197, 94, 0.15);\n  --an-diff-added-border: rgba(34, 197, 94, 0.4);\n  --an-diff-added-text: #4ade80;\n  --an-diff-removed-bg: rgba(239, 68, 68, 0.15);\n  --an-diff-removed-border: rgba(239, 68, 68, 0.4);\n  --an-diff-removed-text: #f87171;\n}\n\n@theme inline {\n  --color-an-background: var(--an-background);\n  --color-an-background-secondary: var(--an-background-secondary);\n  --color-an-background-tertiary: var(--an-background-tertiary);\n  --color-an-foreground: var(--an-foreground);\n  --color-an-foreground-muted: var(--an-foreground-muted);\n  --color-an-foreground-subtle: var(--an-foreground-subtle);\n  --color-an-border-color: var(--an-border-color);\n  --color-an-primary-color: var(--an-primary-color);\n  --color-an-user-message-bg: var(--an-user-message-bg);\n  --color-an-user-message-text: var(--an-user-message-text);\n  --color-an-input-background: var(--an-input-background);\n  --color-an-input-border-color: var(--an-input-border-color);\n  --color-an-input-color: var(--an-input-color);\n  --color-an-input-placeholder-color: var(--an-input-placeholder-color);\n  --color-an-input-focus-outline: var(--an-input-focus-outline);\n  --color-an-send-button-bg: var(--an-send-button-bg);\n  --color-an-send-button-color: var(--an-send-button-color);\n  --color-an-tool-background: var(--an-tool-background);\n  --color-an-tool-border-color: var(--an-tool-border-color);\n  --color-an-tool-color: var(--an-tool-color);\n  --color-an-tool-color-muted: var(--an-tool-color-muted);\n  --color-an-code-background: var(--an-code-background);\n  --color-an-code-color: var(--an-code-color);\n  --color-an-diff-added-bg: var(--an-diff-added-bg);\n  --color-an-diff-added-border: var(--an-diff-added-border);\n  --color-an-diff-added-text: var(--an-diff-added-text);\n  --color-an-diff-removed-bg: var(--an-diff-removed-bg);\n  --color-an-diff-removed-border: var(--an-diff-removed-border);\n  --color-an-diff-removed-text: var(--an-diff-removed-text);\n  --radius-an-message: var(--an-message-border-radius);\n  --radius-an-message-inner: calc(\n    var(--an-message-border-radius) - var(--an-message-radius-inner-offset)\n  );\n  --radius-an-input-border-radius: var(--an-input-border-radius);\n  --radius-an-tool-border-radius: var(--an-tool-border-radius);\n  --spacing-an-context-padding: var(--an-context-padding);\n  --max-width-an: var(--an-max-width);\n  --spacing-an-user-message-x: 14px;\n  --spacing-an-user-message-y: 10px;\n}\n\n/* TextShimmer animation */\n@keyframes an-shimmer {\n  from {\n    background-position: 100% center;\n  }\n  to {\n    background-position: 0% center;\n  }\n}\n\n@keyframes an-blink {\n  50% {\n    opacity: 0;\n  }\n}\n\n@keyframes loading-dots {\n  0%,\n  100% {\n    opacity: 0;\n  }\n  50% {\n    opacity: 1;\n  }\n}\n\n@keyframes an-ellipsis {\n  0% {\n    width: 0;\n  }\n  33% {\n    width: 0.33em;\n  }\n  66% {\n    width: 0.66em;\n  }\n  100% {\n    width: 1em;\n  }\n}\n\n.an-text-shimmer {\n  display: inline-block;\n  background-size: 250% 100%;\n  background-clip: text;\n  -webkit-background-clip: text;\n  color: transparent;\n  background-image: linear-gradient(\n    90deg,\n    var(--an-foreground-subtle, #a3a3a3) 0%,\n    var(--an-foreground-subtle, #a3a3a3) 40%,\n    var(--an-foreground-muted, #737373) 50%,\n    var(--an-foreground-subtle, #a3a3a3) 60%,\n    var(--an-foreground-subtle, #a3a3a3) 100%\n  );\n  background-repeat: no-repeat;\n}\n\n.an-text-shimmer--active {\n  animation: an-shimmer var(--an-shimmer-duration, 2s) linear infinite;\n}\n\n.an-ellipsis {\n  display: inline-block;\n  overflow: hidden;\n  width: 0;\n  vertical-align: bottom;\n  animation: an-ellipsis 1.2s steps(1, end) infinite;\n}\n\n.an-markdown pre code {\n  counter-reset: none !important;\n}\n\n.an-markdown pre code > span::before {\n  content: none !important;\n  display: none !important;\n}\n\n.an-markdown pre code > span {\n  padding-left: 0 !important;\n}\n\n/* Diff */\n.an-edit-diff,\n.an-edit-diff pre,\n.an-edit-diff code {\n  font-size: 12px;\n}\n\n.dark .an-edit-tool-card {\n  background-color: #000;\n}\n\n[data-theme=\"dark\"] .an-edit-tool-card {\n  background-color: #000;\n}\n\n.dark .an-edit-tool-card .an-edit-diff,\n.dark .an-edit-tool-card .an-edit-diff pre,\n.dark .an-edit-tool-card .an-edit-diff code {\n  background-color: #000 !important;\n}\n\n[data-theme=\"dark\"] .an-edit-tool-card .an-edit-diff,\n[data-theme=\"dark\"] .an-edit-tool-card .an-edit-diff pre,\n[data-theme=\"dark\"] .an-edit-tool-card .an-edit-diff code {\n  background-color: #000 !important;\n}\n\n/* Markdown Code Block */\n.an-markdown [data-streamdown=\"code-block\"] {\n  padding: 0;\n  border: 1px solid var(--an-border-color);\n  border-radius: var(--an-tool-border-radius);\n  background: transparent;\n  gap: 0;\n}\n\n.an-markdown [data-streamdown=\"code-block-header\"] {\n  padding: 0px 10px 0px 8px;\n  height: auto;\n  font-size: 12px;\n  height: 28px;\n  background: transparent;\n  border-bottom: 1px solid var(--color-an-tool-border-color);\n  background-color: var(--an-tool-background);\n  position: relative;\n}\n\n.an-markdown div:has(> [data-streamdown=\"code-block-actions\"]) {\n  position: absolute;\n  height: auto;\n  margin: 0;\n  top: 0;\n  right: 0;\n  height: 28px;\n  padding-right: 8px;\n}\n\n.an-markdown [data-streamdown=\"code-block-actions\"] {\n  background: transparent;\n  border: none;\n  backdrop-filter: none;\n  padding: 0;\n}\n\n.an-markdown [data-streamdown=\"code-block-actions\"] button > svg {\n  width: 14px;\n  height: 14px;\n}\n\n.an-markdown [data-streamdown=\"code-block-body\"] {\n  margin: 0;\n  padding-top: 8px;\n  padding-bottom: 8px;\n  background: transparent;\n  border: transparent;\n  overflow-x: auto;\n}\n\n.an-markdown [data-streamdown=\"code-block\"] pre,\n.an-markdown [data-streamdown=\"code-block\"] code {\n  font-size: 12px;\n  background: transparent;\n}\n",
      "type": "registry:style",
      "target": "components/agent-elements/agent-ui.css"
    }
  ],
  "css": {
    "@import \"../components/agent-elements/agent-ui.css\"": ""
  }
}