diff --git a/app/docs/active-connection/page.mdx b/app/docs/active-connection/page.mdx new file mode 100644 index 0000000..97c3d6e --- /dev/null +++ b/app/docs/active-connection/page.mdx @@ -0,0 +1,93 @@ +# Active Connection + +A component for viewing active connection details, handling OAuth authentication flow, and displaying available tools with execution capabilities. + + + +## Installation + + + +## Usage + +```tsx +import { ActiveConnection } from "@/components/smithery/active-connection" + +export function MyApp() { + const [connectionId, setConnectionId] = useState("conn_123") + + return ( + + ) +} +``` + +## Props + +- `token` (required): Your Smithery API token for authentication +- `namespace` (optional): The namespace the connection belongs to. Defaults to the first available namespace +- `connectionId` (required): The ID of the connection to display + +## Features + +### Connection Details +- Server avatar and name display +- Website link if available +- Server description +- Connection creation timestamp +- Connection ID display +- Metadata viewer for custom connection data + +### Authentication Flow +- Automatic detection of `auth_required` state +- 5-second countdown before auto-redirect +- Opens authentication in new window +- Polls connection status every 2 seconds during auth +- Manual authentication link fallback + +### Tools Panel Integration +- Creates MCP client with SmitheryTransport +- Fetches available tools from the connection +- Integrates ToolsPanel for tool browsing and execution +- Provides ConnectionConfigContext for code generation + +## Authentication States + +The component handles three connection states: + +1. **Loading**: Initial connection fetch +2. **Auth Required**: Displays countdown and authorization link +3. **Connected**: Shows tools panel with full functionality + +## MCP Client Setup + +The component automatically: +- Creates SmitheryTransport instance +- Initializes AI SDK MCP client +- Fetches tools list +- Handles tool execution with proper options + +## Context Provider + +Wraps ToolsPanel in ConnectionConfigContext with: +- `mcpUrl`: The MCP server URL +- `apiKey`: Your Smithery API token +- `namespace`: The connection namespace +- `connectionId`: The connection ID + +## Use Cases + +- Right panel in split-pane connection manager +- Standalone tool browser for a specific connection +- OAuth authentication handler for MCP servers +- Tool execution interface with context + +## Related Components + +- Pairs with ConnectionsList for full management UI +- Uses ToolsPanel for tool display and execution +- Part of the full Connections component diff --git a/app/docs/connection-card/page.mdx b/app/docs/connection-card/page.mdx new file mode 100644 index 0000000..5ab3028 --- /dev/null +++ b/app/docs/connection-card/page.mdx @@ -0,0 +1,71 @@ +# Connection Card + +A reusable card component for displaying individual MCP server connection details with delete functionality. + + + +## Installation + + + +## Usage + +```tsx +import { ConnectionCard } from "@/components/smithery/connection-card" + +export function MyComponent() { + return ( + handleClick(connection.connectionId)} + /> + ) +} +``` + +## Props + +- `connection` (required): The connection object from Smithery API +- `token` (required): Your Smithery API token for authentication +- `namespace` (required): The namespace the connection belongs to +- `className` (optional): Additional CSS classes for styling +- `onClick` (optional): Click handler for the card +- All standard HTML div attributes are also supported + +## Features + +- Displays connection avatar with icon or fallback initials +- Shows connection name, ID, MCP URL, and creation date +- Metadata display if available +- Built-in delete button with mutation handling +- Fully clickable card for selection +- Query invalidation on successful deletion + +## Connection Object + +The `connection` prop should be a Smithery Connection object: + +```typescript +interface Connection { + connectionId: string + name: string + iconUrl?: string + mcpUrl: string + createdAt?: string + metadata?: Record + serverInfo?: { + name?: string + title?: string + } +} +``` + +## Use Cases + +- Display connections in a list view +- Allow users to select active connections +- Quick access to delete unwanted connections +- Part of the ConnectionsList component diff --git a/app/docs/connections-list/page.mdx b/app/docs/connections-list/page.mdx new file mode 100644 index 0000000..efa4f3b --- /dev/null +++ b/app/docs/connections-list/page.mdx @@ -0,0 +1,69 @@ +# Connections List + +A list component for browsing and managing MCP server connections with integrated server search functionality. + + + +## Installation + + + +## Usage + +```tsx +import { ConnectionsList } from "@/components/smithery/connections-list" + +export function MyApp() { + const [activeConnectionId, setActiveConnectionId] = useState(null) + + return ( + + ) +} +``` + +## Props + +- `token` (required): Your Smithery API token for authentication +- `namespace` (optional): The namespace to manage connections in. Defaults to the first available namespace +- `onActiveConnectionIdChange` (required): Callback when active connection changes +- `defaultActiveConnectionId` (optional): Initial active connection ID +- `defaultShowSearchServers` (optional): Whether to show server search by default. Defaults to `true` + +## Features + +- List all connections in a scrollable panel +- Search and add new connections via ServerSearch toggle +- Refresh connections with loading state +- Click to select active connection with visual feedback +- Auto-selects first connection if none selected +- Delete connections directly from the list +- Query-based data fetching with automatic updates + +## Layout + +The component renders in a vertical layout with: +- Header with "Connections" title and action buttons +- Toggle button to show/hide server search +- Refresh button with loading spinner +- Scrollable list of ConnectionCard components +- Visual selection state for active connection + +## Use Cases + +- Standalone connection browser +- Left sidebar in split-pane layouts +- Part of the full Connections component +- Custom connection management interfaces + +## Related Components + +- Works with ConnectionCard for individual items +- Integrates ServerSearch for adding connections +- Pairs with ActiveConnection for full management UI diff --git a/app/docs/connections/page.mdx b/app/docs/connections/page.mdx index 6379adf..95653ad 100644 --- a/app/docs/connections/page.mdx +++ b/app/docs/connections/page.mdx @@ -37,32 +37,32 @@ export function MyApp() { - Browse and execute tools from the selected connection - Real-time refresh of connection status -## Sub-components +## Architecture + +The Connections component is built from three modular sub-components that can be used independently: ### ConnectionCard -Individual connection card with delete functionality: +A reusable card for displaying individual connection details. [See full documentation →](/docs/connection-card) ```tsx -import { ConnectionCard } from "@/components/smithery/connections" - - +import { ConnectionCard } from "@/components/smithery/connection-card" ``` ### ConnectionsList -Just the connections list without the tools panel: +The connections list panel with search and management features. [See full documentation →](/docs/connections-list) ```tsx -import { ConnectionsList } from "@/components/smithery/connections" +import { ConnectionsList } from "@/components/smithery/connections-list" +``` + +### ActiveConnection - console.log(id)} -/> +The active connection viewer with auth flow and tools panel. [See full documentation →](/docs/active-connection) + +```tsx +import { ActiveConnection } from "@/components/smithery/active-connection" ``` + +Each of these components can be installed and used independently, or together via the `smithery-connections` package. diff --git a/app/docs/page.tsx b/app/docs/page.tsx index dab137e..5a3c76c 100644 --- a/app/docs/page.tsx +++ b/app/docs/page.tsx @@ -1,8 +1,11 @@ import { + Activity, + CreditCard, FileJson, Key, LayoutGrid, Link2, + List, Search, Settings2, Square, @@ -35,6 +38,25 @@ const components = [ description: "Manage MCP server connections with tool browsing.", icon: Link2, }, + { + title: "Connection Card", + slug: "connection-card", + description: + "Display individual connection details with delete functionality.", + icon: CreditCard, + }, + { + title: "Connections List", + slug: "connections-list", + description: "Browse and manage connections with search integration.", + icon: List, + }, + { + title: "Active Connection", + slug: "active-connection", + description: "View connection details with auth flow and tools panel.", + icon: Activity, + }, { title: "Tools Panel", slug: "tools-panel", diff --git a/components/docs/component-preview.tsx b/components/docs/component-preview.tsx index 607b8f8..be94785 100644 --- a/components/docs/component-preview.tsx +++ b/components/docs/component-preview.tsx @@ -2,7 +2,7 @@ import { createMCPClient } from "@ai-sdk/mcp"; import Smithery from "@smithery/api"; -import { createConnection } from "@smithery/api/mcp"; +import { SmitheryTransport } from "@smithery/api/mcp"; import type { Connection } from "@smithery/api/resources/beta/connect/connections.mjs"; import { useQuery } from "@tanstack/react-query"; import type { Tool, ToolExecutionOptions } from "ai"; @@ -65,7 +65,7 @@ function useConnectionTools( throw new Error("Token and connection required"); const namespaceToUse = namespace || (await getDefaultNamespace(getSmitheryClient(token))); - const { transport } = await createConnection({ + const transport = new SmitheryTransport({ client: getSmitheryClient(token), connectionId, namespace: namespaceToUse, diff --git a/components/docs/previews.tsx b/components/docs/previews.tsx index 272e715..7f2d01d 100644 --- a/components/docs/previews.tsx +++ b/components/docs/previews.tsx @@ -2,7 +2,7 @@ import { createMCPClient } from "@ai-sdk/mcp"; import Smithery from "@smithery/api"; -import { createConnection } from "@smithery/api/mcp"; +import { SmitheryTransport } from "@smithery/api/mcp"; import type { Connection } from "@smithery/api/resources/beta/connect/connections.mjs"; import { useQuery } from "@tanstack/react-query"; import type { ToolExecutionOptions } from "ai"; @@ -18,8 +18,11 @@ import { SelectValue, } from "@/components/ui/select"; import { Spinner } from "@/components/ui/spinner"; +import { ActiveConnection } from "@/registry/new-york/smithery/active-connection"; +import { ConnectionCard } from "@/registry/new-york/smithery/connection-card"; import { ConnectionConfigContext } from "@/registry/new-york/smithery/connection-context"; import { Connections } from "@/registry/new-york/smithery/connections"; +import { ConnectionsList } from "@/registry/new-york/smithery/connections-list"; import { SchemaForm } from "@/registry/new-york/smithery/schema-form"; import { ServerSearch } from "@/registry/new-york/smithery/server-search"; import { selectedTokenAtom } from "@/registry/new-york/smithery/tokens"; @@ -85,7 +88,7 @@ function useConnectionTools( throw new Error("Token and connection required"); const namespaceToUse = namespace || (await getDefaultNamespace(getSmitheryClient(token))); - const { transport } = await createConnection({ + const transport = new SmitheryTransport({ client: getSmitheryClient(token), connectionId, namespace: namespaceToUse, @@ -662,3 +665,136 @@ function SchemaFormInner({ ); } + +// Connection Card Preview +export function ConnectionCardPreview() { + const apiKey = useAtomValue(selectedTokenAtom); + const { data, isLoading, error } = useConnections(apiKey?.token); + + if (!apiKey) { + return ( + + + + ); + } + + if (isLoading) { + return ( + +
+ Loading connections... +
+
+ ); + } + + if (error) { + return ( + +
Error: {error.message}
+
+ ); + } + + if (!data?.connections.length) { + return ( + +
+ + No connections. Connect to{" "} + + {DEFAULT_MCP_URL} + {" "} + first. +
+
+ ); + } + + return ( + +
+ {data.connections.slice(0, 2).map((connection: Connection) => ( + + ))} +
+
+ ); +} + +// Connections List Preview +export function ConnectionsListPreview() { + const apiKey = useAtomValue(selectedTokenAtom); + const [_activeConnectionId, setActiveConnectionId] = useState( + null, + ); + + if (!apiKey) { + return ( + + + + ); + } + + return ( + +
+ +
+
+ ); +} + +// Active Connection Preview +export function ActiveConnectionPreview() { + const apiKey = useAtomValue(selectedTokenAtom); + const [selectedConnectionId, setSelectedConnectionId] = useState< + string | null + >(null); + + if (!apiKey) { + return ( + + + + ); + } + + return ( +
+
+ Connection: + +
+ + {selectedConnectionId ? ( +
+ +
+ ) : ( +
+ Select a connection above. +
+ )} +
+
+ ); +} diff --git a/components/shared-sidebar.tsx b/components/shared-sidebar.tsx index 769cff2..a462696 100644 --- a/components/shared-sidebar.tsx +++ b/components/shared-sidebar.tsx @@ -2,11 +2,14 @@ import type { CreateTokenResponse } from "@smithery/api/resources/tokens.mjs"; import { + Activity, Blocks, + CreditCard, FileJson, Key, LayoutGrid, Link2, + List, Search, Settings2, Square, @@ -40,6 +43,9 @@ export const componentItems = [ { title: "Tokens", slug: "tokens", icon: Key }, { title: "Server Search", slug: "server-search", icon: Search }, { title: "Connections", slug: "connections", icon: Link2 }, + { title: "Connection Card", slug: "connection-card", icon: CreditCard }, + { title: "Connections List", slug: "connections-list", icon: List }, + { title: "Active Connection", slug: "active-connection", icon: Activity }, { title: "Tools Panel", slug: "tools-panel", icon: LayoutGrid }, { title: "Tool Card", slug: "tool-card", icon: Square }, { diff --git a/components/smithery/actions.ts b/components/smithery/actions.ts index 91d4cde..b22d103 100644 --- a/components/smithery/actions.ts +++ b/components/smithery/actions.ts @@ -109,7 +109,7 @@ export const enableServer = async ( // Verify auth by calling tools/list try { - await client.beta.connect.mcp.call(connectionId, { + await client.beta.connect.rpc.call(connectionId, { namespace, jsonrpc: "2.0", method: "tools/list", @@ -190,7 +190,7 @@ export const checkConnection = async ( // Check auth by calling tools/list try { - await client.beta.connect.mcp.call(connectionId, { + await client.beta.connect.rpc.call(connectionId, { namespace, jsonrpc: "2.0", method: "tools/list", @@ -211,7 +211,7 @@ export const testConnection = async ( ) => { try { const client = getSmitheryClient(apiKey); - const response = await client.beta.connect.mcp.call(connectionId, { + const response = await client.beta.connect.rpc.call(connectionId, { namespace: namespace, jsonrpc: "2.0", method: "tools/list", @@ -259,7 +259,7 @@ export const planAction = async ( // Get tools from all connections const toolsWithConfigs = await Promise.all( serverConfigs.map(async (config) => { - const response = await client.beta.connect.mcp.call(config.configId, { + const response = await client.beta.connect.rpc.call(config.configId, { namespace, jsonrpc: "2.0", method: "tools/list", @@ -327,7 +327,7 @@ export const runTool = async ( apiKey?: string | null, ) => { const client = getSmitheryClient(apiKey); - const response = await client.beta.connect.mcp.call(configId, { + const response = await client.beta.connect.rpc.call(configId, { namespace: namespace, jsonrpc: "2.0", method: "tools/call", diff --git a/lib/actions.ts b/lib/actions.ts index aa38b07..a882f01 100644 --- a/lib/actions.ts +++ b/lib/actions.ts @@ -31,7 +31,7 @@ export async function createToken({ userId, }, }, - mcp: { + rpc: { actions: ["read", "write"], namespaces: [namespace], metadata: { diff --git a/mdx-components.tsx b/mdx-components.tsx index cdac96b..8d2327e 100644 --- a/mdx-components.tsx +++ b/mdx-components.tsx @@ -3,6 +3,9 @@ import { ComponentPreview } from "@/components/docs/component-preview"; import { InstallCommand } from "@/components/docs/install-command"; import { PreviewFrame } from "@/components/docs/preview-frame"; import { + ActiveConnectionPreview, + ConnectionCardPreview, + ConnectionsListPreview, ConnectionsPreview, SchemaFormPreview, ServerSearchPreview, @@ -19,6 +22,9 @@ export function useMDXComponents(components: MDXComponents): MDXComponents { PreviewFrame, ServerSearchPreview, ConnectionsPreview, + ConnectionCardPreview, + ConnectionsListPreview, + ActiveConnectionPreview, ToolsPanelPreview, ToolCardPreview, ToolDetailDialogPreview, diff --git a/public/r/registry.json b/public/r/registry.json index 8a36088..23a19df 100644 --- a/public/r/registry.json +++ b/public/r/registry.json @@ -234,6 +234,11 @@ "type": "registry:component", "target": "components/smithery/query-client-wrapper.tsx" }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, { "path": "registry/new-york/smithery/connection-context.tsx", "type": "registry:component", @@ -259,6 +264,21 @@ "type": "registry:component", "target": "components/smithery/tools-panel.tsx" }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + }, + { + "path": "registry/new-york/smithery/connections-list.tsx", + "type": "registry:component", + "target": "components/smithery/connections-list.tsx" + }, + { + "path": "registry/new-york/smithery/active-connection.tsx", + "type": "registry:component", + "target": "components/smithery/active-connection.tsx" + }, { "path": "registry/new-york/smithery/server-search.tsx", "type": "registry:component", @@ -300,6 +320,159 @@ } ] }, + { + "name": "smithery-utils", + "type": "registry:block", + "title": "Smithery Utils", + "description": "Utility functions for Smithery API client instantiation and namespace management.", + "dependencies": ["@smithery/api"], + "files": [ + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + } + ] + }, + { + "name": "smithery-connection-card", + "type": "registry:block", + "title": "Smithery Connection Card", + "description": "A reusable card component for displaying individual MCP server connection details.", + "dependencies": [ + "@smithery/api", + "@tanstack/react-query", + "lucide-react" + ], + "registryDependencies": ["avatar", "button", "card"], + "files": [ + { + "path": "registry/new-york/smithery/query-client-wrapper.tsx", + "type": "registry:component", + "target": "components/smithery/query-client-wrapper.tsx" + }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + } + ] + }, + { + "name": "smithery-connections-list", + "type": "registry:block", + "title": "Smithery Connections List", + "description": "A list component for browsing and managing MCP server connections with search functionality.", + "dependencies": [ + "@smithery/api", + "@tanstack/react-query", + "lucide-react" + ], + "registryDependencies": ["button", "separator", "toggle"], + "files": [ + { + "path": "registry/new-york/smithery/query-client-wrapper.tsx", + "type": "registry:component", + "target": "components/smithery/query-client-wrapper.tsx" + }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + }, + { + "path": "registry/new-york/smithery/server-search.tsx", + "type": "registry:component", + "target": "components/smithery/server-search.tsx" + }, + { + "path": "registry/new-york/smithery/connections-list.tsx", + "type": "registry:component", + "target": "components/smithery/connections-list.tsx" + } + ] + }, + { + "name": "smithery-active-connection", + "type": "registry:block", + "title": "Smithery Active Connection", + "description": "A component for viewing active connection details, handling authentication flow, and displaying available tools.", + "dependencies": [ + "@ai-sdk/mcp", + "@smithery/api", + "@tanstack/react-query", + "ai", + "lucide-react", + "react-hook-form", + "tokenx", + "shiki" + ], + "registryDependencies": [ + "avatar", + "badge", + "button", + "card", + "dialog", + "field", + "input", + "select", + "switch", + "tabs", + "textarea" + ], + "files": [ + { + "path": "registry/new-york/smithery/query-client-wrapper.tsx", + "type": "registry:component", + "target": "components/smithery/query-client-wrapper.tsx" + }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, + { + "path": "registry/new-york/smithery/connection-context.tsx", + "type": "registry:component", + "target": "components/smithery/connection-context.tsx" + }, + { + "path": "registry/new-york/smithery/code-block.tsx", + "type": "registry:component", + "target": "components/smithery/code-block.tsx" + }, + { + "path": "registry/new-york/smithery/tool-detail-dialog.tsx", + "type": "registry:component", + "target": "components/smithery/tool-detail-dialog.tsx" + }, + { + "path": "registry/new-york/smithery/tool-card.tsx", + "type": "registry:component", + "target": "components/smithery/tool-card.tsx" + }, + { + "path": "registry/new-york/smithery/tools-panel.tsx", + "type": "registry:component", + "target": "components/smithery/tools-panel.tsx" + }, + { + "path": "registry/new-york/smithery/active-connection.tsx", + "type": "registry:component", + "target": "components/smithery/active-connection.tsx" + } + ] + }, { "name": "smithery-tokens", "type": "registry:block", @@ -356,6 +529,11 @@ "type": "registry:component", "target": "components/smithery/query-client-wrapper.tsx" }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, { "path": "registry/new-york/smithery/connection-context.tsx", "type": "registry:component", @@ -386,6 +564,21 @@ "type": "registry:component", "target": "components/smithery/tools-panel.tsx" }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + }, + { + "path": "registry/new-york/smithery/connections-list.tsx", + "type": "registry:component", + "target": "components/smithery/connections-list.tsx" + }, + { + "path": "registry/new-york/smithery/active-connection.tsx", + "type": "registry:component", + "target": "components/smithery/active-connection.tsx" + }, { "path": "registry/new-york/smithery/connections.tsx", "type": "registry:component", diff --git a/public/r/smithery-active-connection.json b/public/r/smithery-active-connection.json new file mode 100644 index 0000000..e9a6050 --- /dev/null +++ b/public/r/smithery-active-connection.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "smithery-active-connection", + "title": "Smithery Active Connection", + "description": "A component for viewing active connection details, handling authentication flow, and displaying available tools.", + "dependencies": [ + "@ai-sdk/mcp", + "@smithery/api", + "@tanstack/react-query", + "ai", + "lucide-react", + "react-hook-form", + "tokenx", + "shiki" + ], + "registryDependencies": [ + "avatar", + "badge", + "button", + "card", + "dialog", + "field", + "input", + "select", + "switch", + "tabs", + "textarea" + ], + "files": [ + { + "path": "registry/new-york/smithery/query-client-wrapper.tsx", + "content": "\"use client\";\n\nimport {\n\tQueryClient,\n\tQueryClientContext,\n\tQueryClientProvider,\n} from \"@tanstack/react-query\";\nimport { type ReactNode, useContext, useState } from \"react\";\n\n// Shared default QueryClient for all Smithery components\nlet sharedQueryClient: QueryClient | null = null;\n\nfunction getSharedQueryClient() {\n\tif (!sharedQueryClient) {\n\t\tsharedQueryClient = new QueryClient({\n\t\t\tdefaultOptions: {\n\t\t\t\tqueries: { staleTime: 60 * 1000 },\n\t\t\t},\n\t\t});\n\t}\n\treturn sharedQueryClient;\n}\n\n/**\n * Wrapper that auto-provides QueryClient if none exists.\n * - If a QueryClientProvider already exists, uses that client (user's takes precedence)\n * - If no provider exists, creates a shared client for Smithery components\n */\nexport function WithQueryClient({ children }: { children: ReactNode }) {\n\t// Check if we're already inside a QueryClientProvider\n\tconst existingClient = useContext(QueryClientContext);\n\n\t// Create our own client only if needed (lazy init, stable across renders)\n\tconst [fallbackClient] = useState(() => getSharedQueryClient());\n\n\t// If there's already a client, just render children\n\tif (existingClient) {\n\t\treturn <>{children};\n\t}\n\n\t// Otherwise, provide our own client\n\treturn (\n\t\t\n\t\t\t{children}\n\t\t\n\t);\n}\n", + "type": "registry:component", + "target": "components/smithery/query-client-wrapper.tsx" + }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "content": "import Smithery from \"@smithery/api\";\n\nexport async function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nexport const getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, + { + "path": "registry/new-york/smithery/connection-context.tsx", + "content": "\"use client\";\n\nimport { createContext, useContext } from \"react\";\n\n// Context for connection config - consumed by ToolDetailDialog for code generation\nexport interface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nexport const ConnectionConfigContext = createContext(\n\tnull,\n);\n\nexport function useConnectionConfig() {\n\treturn useContext(ConnectionConfigContext);\n}\n", + "type": "registry:component", + "target": "components/smithery/connection-context.tsx" + }, + { + "path": "registry/new-york/smithery/code-block.tsx", + "content": "\"use client\";\n\nimport { CheckIcon, CopyIcon } from \"lucide-react\";\nimport {\n\ttype ComponentProps,\n\tcreateContext,\n\ttype HTMLAttributes,\n\tuseContext,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n} from \"react\";\nimport { type BundledLanguage, codeToHtml, type ShikiTransformer } from \"shiki\";\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype CodeBlockProps = HTMLAttributes & {\n\tcode: string;\n\tlanguage: BundledLanguage;\n\tshowLineNumbers?: boolean;\n};\n\ntype CodeBlockContextType = {\n\tcode: string;\n};\n\nconst CodeBlockContext = createContext({\n\tcode: \"\",\n});\n\nconst lineNumberTransformer: ShikiTransformer = {\n\tname: \"line-numbers\",\n\tline(node, line) {\n\t\tnode.children.unshift({\n\t\t\ttype: \"element\",\n\t\t\ttagName: \"span\",\n\t\t\tproperties: {\n\t\t\t\tclassName: [\n\t\t\t\t\t\"inline-block\",\n\t\t\t\t\t\"min-w-10\",\n\t\t\t\t\t\"mr-4\",\n\t\t\t\t\t\"text-right\",\n\t\t\t\t\t\"select-none\",\n\t\t\t\t\t\"text-muted-foreground\",\n\t\t\t\t],\n\t\t\t},\n\t\t\tchildren: [{ type: \"text\", value: String(line) }],\n\t\t});\n\t},\n};\n\nexport async function highlightCode(\n\tcode: string,\n\tlanguage: BundledLanguage,\n\tshowLineNumbers = false,\n) {\n\tconst transformers: ShikiTransformer[] = showLineNumbers\n\t\t? [lineNumberTransformer]\n\t\t: [];\n\n\treturn await Promise.all([\n\t\tcodeToHtml(code, {\n\t\t\tlang: language,\n\t\t\ttheme: \"one-light\",\n\t\t\ttransformers,\n\t\t}),\n\t\tcodeToHtml(code, {\n\t\t\tlang: language,\n\t\t\ttheme: \"one-dark-pro\",\n\t\t\ttransformers,\n\t\t}),\n\t]);\n}\n\nexport const CodeBlock = ({\n\tcode,\n\tlanguage,\n\tshowLineNumbers = false,\n\tclassName,\n\tchildren,\n\t...props\n}: CodeBlockProps) => {\n\tconst [html, setHtml] = useState(\"\");\n\tconst [darkHtml, setDarkHtml] = useState(\"\");\n\tconst mounted = useRef(false);\n\n\tuseEffect(() => {\n\t\thighlightCode(code, language, showLineNumbers).then(([light, dark]) => {\n\t\t\tif (!mounted.current) {\n\t\t\t\tsetHtml(light);\n\t\t\t\tsetDarkHtml(dark);\n\t\t\t\tmounted.current = true;\n\t\t\t}\n\t\t});\n\n\t\treturn () => {\n\t\t\tmounted.current = false;\n\t\t};\n\t}, [code, language, showLineNumbers]);\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\tpre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm\"\n\t\t\t\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: \"this is needed.\"\n\t\t\t\t\t\tdangerouslySetInnerHTML={{ __html: html }}\n\t\t\t\t\t/>\n\t\t\t\t\tpre]:m-0 [&>pre]:bg-background! [&>pre]:p-4 [&>pre]:text-foreground! [&>pre]:text-sm [&_code]:font-mono [&_code]:text-sm\"\n\t\t\t\t\t\t// biome-ignore lint/security/noDangerouslySetInnerHtml: \"this is needed.\"\n\t\t\t\t\t\tdangerouslySetInnerHTML={{ __html: darkHtml }}\n\t\t\t\t\t/>\n\t\t\t\t\t{children && (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{children}\n\t\t\t\t\t\t
\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n};\n\nexport type CodeBlockCopyButtonProps = ComponentProps & {\n\tonCopy?: () => void;\n\tonError?: (error: Error) => void;\n\ttimeout?: number;\n};\n\nexport const CodeBlockCopyButton = ({\n\tonCopy,\n\tonError,\n\ttimeout = 2000,\n\tchildren,\n\tclassName,\n\t...props\n}: CodeBlockCopyButtonProps) => {\n\tconst [isCopied, setIsCopied] = useState(false);\n\tconst { code } = useContext(CodeBlockContext);\n\n\tconst copyToClipboard = async () => {\n\t\tif (typeof window === \"undefined\" || !navigator?.clipboard?.writeText) {\n\t\t\tonError?.(new Error(\"Clipboard API not available\"));\n\t\t\treturn;\n\t\t}\n\n\t\ttry {\n\t\t\tawait navigator.clipboard.writeText(code);\n\t\t\tsetIsCopied(true);\n\t\t\tonCopy?.();\n\t\t\tsetTimeout(() => setIsCopied(false), timeout);\n\t\t} catch (error) {\n\t\t\tonError?.(error as Error);\n\t\t}\n\t};\n\n\tconst Icon = isCopied ? CheckIcon : CopyIcon;\n\n\treturn (\n\t\t\n\t\t\t{children ?? }\n\t\t\n\t);\n};\n", + "type": "registry:component", + "target": "components/smithery/code-block.tsx" + }, + { + "path": "registry/new-york/smithery/tool-detail-dialog.tsx", + "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst _namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", + "type": "registry:component", + "target": "components/smithery/tool-detail-dialog.tsx" + }, + { + "path": "registry/new-york/smithery/tool-card.tsx", + "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { ChevronRight } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Card, CardHeader } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport { ToolDetailDialog } from \"@/registry/new-york/smithery/tool-detail-dialog\";\n\ninterface ToolCardProps {\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolCard({ name, tool, onExecute }: ToolCardProps) {\n\tconst [dialogOpen, setDialogOpen] = useState(false);\n\n\treturn (\n\t\t<>\n\t\t\t setDialogOpen(true)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

{name}

\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\n\t\t\t\n\t\t\n\t);\n}\n", + "type": "registry:component", + "target": "components/smithery/tool-card.tsx" + }, + { + "path": "registry/new-york/smithery/tools-panel.tsx", + "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { Search } from \"lucide-react\";\nimport { useMemo, useState } from \"react\";\nimport { Field, FieldLabel } from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport { ToolCard } from \"@/registry/new-york/smithery/tool-card\";\n\ninterface ToolsPanelProps {\n\ttools: Record;\n\tonExecute?: (\n\t\ttoolName: string,\n\t\tparams: Record,\n\t) => Promise;\n}\n\nexport function ToolsPanel({ tools, onExecute }: ToolsPanelProps) {\n\tconst [searchQuery, setSearchQuery] = useState(\"\");\n\n\tconst filteredTools = useMemo(() => {\n\t\tif (!searchQuery.trim()) {\n\t\t\treturn Object.entries(tools);\n\t\t}\n\n\t\tconst query = searchQuery.toLowerCase();\n\t\treturn Object.entries(tools).filter(([name, tool]) => {\n\t\t\treturn (\n\t\t\t\tname.toLowerCase().includes(query) ||\n\t\t\t\ttool.description?.toLowerCase().includes(query) ||\n\t\t\t\ttool.type?.toLowerCase().includes(query)\n\t\t\t);\n\t\t});\n\t}, [tools, searchQuery]);\n\n\tconst handleExecute = async (\n\t\ttoolName: string,\n\t\tparams: Record,\n\t) => {\n\t\tif (!onExecute) {\n\t\t\tthrow new Error(\"No execute handler provided\");\n\t\t}\n\t\treturn await onExecute(toolName, params);\n\t};\n\n\tconst toolCount = Object.keys(tools).length;\n\tconst filteredCount = filteredTools.length;\n\n\tif (toolCount === 0) {\n\t\treturn (\n\t\t\t
\n\t\t\t\t

No tools available

\n\t\t\t
\n\t\t);\n\t}\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\t

Tools

\n\t\t\t\t\t\n\t\t\t\t\t\t{filteredCount === toolCount\n\t\t\t\t\t\t\t? `${toolCount} tool${toolCount === 1 ? \"\" : \"s\"}`\n\t\t\t\t\t\t\t: `${filteredCount} of ${toolCount} tools`}\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\tSearch tools\n\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t setSearchQuery(e.target.value)}\n\t\t\t\t\t\t\tclassName=\"pl-9\"\n\t\t\t\t\t\t/>\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t{filteredCount === 0 ? (\n\t\t\t\t\t
\n\t\t\t\t\t\t

No tools match your search

\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\tTry a different search term\n\t\t\t\t\t\t

\n\t\t\t\t\t
\n\t\t\t\t) : (\n\t\t\t\t\t
\n\t\t\t\t\t\t{filteredTools.map(([name, tool]) => (\n\t\t\t\t\t\t\t handleExecute(name, params)}\n\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t))}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n}\n", + "type": "registry:component", + "target": "components/smithery/tools-panel.tsx" + }, + { + "path": "registry/new-york/smithery/active-connection.tsx", + "content": "\"use client\";\n\nimport { createMCPClient } from \"@ai-sdk/mcp\";\nimport { SmitheryTransport } from \"@smithery/api/mcp\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport type { ToolExecutionOptions } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport {\n\tConnectionConfigContext,\n\tuseConnectionConfig,\n} from \"@/registry/new-york/smithery/connection-context\";\nimport {\n\tgetDefaultNamespace,\n\tgetSmitheryClient,\n} from \"@/registry/new-york/smithery/smithery-utils\";\nimport { ToolsPanel } from \"@/registry/new-york/smithery/tools-panel\";\n\n// Re-export useConnectionConfig for backward compatibility\nexport { useConnectionConfig };\n\nexport const ActiveConnection = ({\n\ttoken,\n\tnamespace,\n\tconnectionId,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tconnectionId: string;\n}) => {\n\tconst [countdown, setCountdown] = useState(null);\n\tconst [hasRedirected, setHasRedirected] = useState(false);\n\n\tconst { data, isLoading, error } = useQuery({\n\t\tqueryKey: [\"connection\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst data = await client.beta.connect.connections.get(connectionId, {\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\treturn { namespace: namespaceToUse, ...data };\n\t\t},\n\t\t// Poll every 2 seconds when auth_required, stop when connected or error\n\t\trefetchInterval: (query) => {\n\t\t\tconst state = query.state.data?.status?.state;\n\t\t\tif (state === \"auth_required\") {\n\t\t\t\treturn 2000;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t});\n\n\t// Start countdown when auth_required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tcountdown === null &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetCountdown(5);\n\t\t}\n\t\t// Reset state when connectionId changes or status is no longer auth_required\n\t\tif (data?.status?.state !== \"auth_required\") {\n\t\t\tsetCountdown(null);\n\t\t\tsetHasRedirected(false);\n\t\t}\n\t}, [data?.status?.state, countdown, hasRedirected]);\n\n\t// Countdown timer\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when countdown reaches 0\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tdata?.status?.authorizationUrl &&\n\t\t\tcountdown === 0 &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetHasRedirected(true);\n\t\t\twindow.open(data.status.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [data?.status, countdown, hasRedirected]);\n\n\tconst clientQuery = useQuery({\n\t\tqueryKey: [\"mcp-client\", token, connectionId, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst namespaceToUse =\n\t\t\t\tnamespace || (await getDefaultNamespace(getSmitheryClient(token)));\n\t\t\tconst transport = new SmitheryTransport({\n\t\t\t\tclient: getSmitheryClient(token),\n\t\t\t\tconnectionId: connectionId,\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\tconst mcpClient = await createMCPClient({ transport });\n\t\t\treturn mcpClient;\n\t\t},\n\t\tenabled: !!connectionId && data?.status?.state === \"connected\",\n\t});\n\n\tconst toolsQuery = useQuery({\n\t\tqueryKey: [\"tools\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tif (!clientQuery.data) {\n\t\t\t\tthrow new Error(\"Client not available\");\n\t\t\t}\n\t\t\tconst client = clientQuery.data;\n\t\t\treturn await client.tools();\n\t\t},\n\t\tenabled: !!clientQuery.data && data?.status?.state === \"connected\",\n\t});\n\tconst handleExecute = async (\n\t\ttoolName: string,\n\t\tparams: Record,\n\t) => {\n\t\tif (!toolsQuery.data) {\n\t\t\tthrow new Error(\"Tools not available\");\n\t\t}\n\t\tconst tool = toolsQuery.data[toolName];\n\t\tif (!tool) {\n\t\t\tthrow new Error(`Tool ${toolName} not found`);\n\t\t}\n\t\t// The execute method from AI SDK tools expects (params, options)\n\t\t// We're executing tools directly, so provide minimal required options\n\t\tconst options: ToolExecutionOptions = {\n\t\t\ttoolCallId: `manual-${Date.now()}`,\n\t\t\tmessages: [],\n\t\t};\n\t\treturn await tool.execute(params, options);\n\t};\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading connection...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{data.iconUrl && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{(\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.title ??\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.name ??\n\t\t\t\t\t\t\t\t\t\t\tdata.name\n\t\t\t\t\t\t\t\t\t\t)?.charAt(0)}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\t{data.serverInfo?.title ?? data.serverInfo?.name ?? data.name}\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.websiteUrl && (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\tView website\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.serverInfo?.description && (\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.description}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\tConnection created:{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t

·

\n\t\t\t\t\t\t\t

Connection ID: {data.connectionId}

\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.metadata && (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tMetadata\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t{JSON.stringify(data.metadata, null, 2)}\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data?.status && data.status.state === \"auth_required\" && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Auth required

\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t\t\t\t: \"You should be automatically redirected to authenticate. If not, click the link below:\"}\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tOpen authentication window\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading tools...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\tError: {toolsQuery.error.message}\n\t\t\t\t\t\t

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.data && data && (\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n", + "type": "registry:component", + "target": "components/smithery/active-connection.tsx" + } + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/public/r/smithery-connection-card.json b/public/r/smithery-connection-card.json new file mode 100644 index 0000000..9c290b7 --- /dev/null +++ b/public/r/smithery-connection-card.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "smithery-connection-card", + "title": "Smithery Connection Card", + "description": "A reusable card component for displaying individual MCP server connection details.", + "dependencies": [ + "@smithery/api", + "@tanstack/react-query", + "lucide-react" + ], + "registryDependencies": [ + "avatar", + "button", + "card" + ], + "files": [ + { + "path": "registry/new-york/smithery/query-client-wrapper.tsx", + "content": "\"use client\";\n\nimport {\n\tQueryClient,\n\tQueryClientContext,\n\tQueryClientProvider,\n} from \"@tanstack/react-query\";\nimport { type ReactNode, useContext, useState } from \"react\";\n\n// Shared default QueryClient for all Smithery components\nlet sharedQueryClient: QueryClient | null = null;\n\nfunction getSharedQueryClient() {\n\tif (!sharedQueryClient) {\n\t\tsharedQueryClient = new QueryClient({\n\t\t\tdefaultOptions: {\n\t\t\t\tqueries: { staleTime: 60 * 1000 },\n\t\t\t},\n\t\t});\n\t}\n\treturn sharedQueryClient;\n}\n\n/**\n * Wrapper that auto-provides QueryClient if none exists.\n * - If a QueryClientProvider already exists, uses that client (user's takes precedence)\n * - If no provider exists, creates a shared client for Smithery components\n */\nexport function WithQueryClient({ children }: { children: ReactNode }) {\n\t// Check if we're already inside a QueryClientProvider\n\tconst existingClient = useContext(QueryClientContext);\n\n\t// Create our own client only if needed (lazy init, stable across renders)\n\tconst [fallbackClient] = useState(() => getSharedQueryClient());\n\n\t// If there's already a client, just render children\n\tif (existingClient) {\n\t\treturn <>{children};\n\t}\n\n\t// Otherwise, provide our own client\n\treturn (\n\t\t\n\t\t\t{children}\n\t\t\n\t);\n}\n", + "type": "registry:component", + "target": "components/smithery/query-client-wrapper.tsx" + }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "content": "import Smithery from \"@smithery/api\";\n\nexport async function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nexport const getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "content": "\"use client\";\n\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { Trash2 } from \"lucide-react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\nimport { getSmitheryClient } from \"@/registry/new-york/smithery/smithery-utils\";\n\nconst ConnectionCardInner = ({\n\tconnection,\n\ttoken,\n\tnamespace,\n\tclassName,\n\t...rest\n}: {\n\tconnection: Connection;\n\ttoken: string;\n\tnamespace: string;\n\tclassName?: string;\n} & React.HTMLAttributes) => {\n\tconst queryClient = useQueryClient();\n\tconst deleteMutation = useMutation({\n\t\tmutationFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tawait client.beta.connect.connections.delete(connection.connectionId, {\n\t\t\t\tnamespace: namespace,\n\t\t\t});\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t});\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{connection.name.charAt(0)}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.serverInfo?.title ??\n\t\t\t\t\t\t\tconnection.serverInfo?.name ??\n\t\t\t\t\t\t\tconnection.name}\n\t\t\t\t\t\t{connection.connectionId && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{\"•\".repeat(Math.min(connection.connectionId.length - 10, 4))}\n\t\t\t\t\t\t\t\t{connection.connectionId.slice(-10)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.mcpUrl}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.metadata && JSON.stringify(connection.metadata)}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t\t deleteMutation.mutate()}\n\t\t\t\t\tdisabled={deleteMutation.isPending}\n\t\t\t\t>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionCard = (\n\tprops: {\n\t\tconnection: Connection;\n\t\ttoken: string;\n\t\tnamespace: string;\n\t\tclassName?: string;\n\t} & React.HTMLAttributes,\n) => (\n\t\n\t\t\n\t\n);\n", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + } + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/public/r/smithery-connections-list.json b/public/r/smithery-connections-list.json new file mode 100644 index 0000000..2542f63 --- /dev/null +++ b/public/r/smithery-connections-list.json @@ -0,0 +1,49 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "smithery-connections-list", + "title": "Smithery Connections List", + "description": "A list component for browsing and managing MCP server connections with search functionality.", + "dependencies": [ + "@smithery/api", + "@tanstack/react-query", + "lucide-react" + ], + "registryDependencies": [ + "button", + "separator", + "toggle" + ], + "files": [ + { + "path": "registry/new-york/smithery/query-client-wrapper.tsx", + "content": "\"use client\";\n\nimport {\n\tQueryClient,\n\tQueryClientContext,\n\tQueryClientProvider,\n} from \"@tanstack/react-query\";\nimport { type ReactNode, useContext, useState } from \"react\";\n\n// Shared default QueryClient for all Smithery components\nlet sharedQueryClient: QueryClient | null = null;\n\nfunction getSharedQueryClient() {\n\tif (!sharedQueryClient) {\n\t\tsharedQueryClient = new QueryClient({\n\t\t\tdefaultOptions: {\n\t\t\t\tqueries: { staleTime: 60 * 1000 },\n\t\t\t},\n\t\t});\n\t}\n\treturn sharedQueryClient;\n}\n\n/**\n * Wrapper that auto-provides QueryClient if none exists.\n * - If a QueryClientProvider already exists, uses that client (user's takes precedence)\n * - If no provider exists, creates a shared client for Smithery components\n */\nexport function WithQueryClient({ children }: { children: ReactNode }) {\n\t// Check if we're already inside a QueryClientProvider\n\tconst existingClient = useContext(QueryClientContext);\n\n\t// Create our own client only if needed (lazy init, stable across renders)\n\tconst [fallbackClient] = useState(() => getSharedQueryClient());\n\n\t// If there's already a client, just render children\n\tif (existingClient) {\n\t\treturn <>{children};\n\t}\n\n\t// Otherwise, provide our own client\n\treturn (\n\t\t\n\t\t\t{children}\n\t\t\n\t);\n}\n", + "type": "registry:component", + "target": "components/smithery/query-client-wrapper.tsx" + }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "content": "import Smithery from \"@smithery/api\";\n\nexport async function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nexport const getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "content": "\"use client\";\n\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { Trash2 } from \"lucide-react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\nimport { getSmitheryClient } from \"@/registry/new-york/smithery/smithery-utils\";\n\nconst ConnectionCardInner = ({\n\tconnection,\n\ttoken,\n\tnamespace,\n\tclassName,\n\t...rest\n}: {\n\tconnection: Connection;\n\ttoken: string;\n\tnamespace: string;\n\tclassName?: string;\n} & React.HTMLAttributes) => {\n\tconst queryClient = useQueryClient();\n\tconst deleteMutation = useMutation({\n\t\tmutationFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tawait client.beta.connect.connections.delete(connection.connectionId, {\n\t\t\t\tnamespace: namespace,\n\t\t\t});\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t});\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{connection.name.charAt(0)}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.serverInfo?.title ??\n\t\t\t\t\t\t\tconnection.serverInfo?.name ??\n\t\t\t\t\t\t\tconnection.name}\n\t\t\t\t\t\t{connection.connectionId && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{\"•\".repeat(Math.min(connection.connectionId.length - 10, 4))}\n\t\t\t\t\t\t\t\t{connection.connectionId.slice(-10)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.mcpUrl}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.metadata && JSON.stringify(connection.metadata)}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t\t deleteMutation.mutate()}\n\t\t\t\t\tdisabled={deleteMutation.isPending}\n\t\t\t\t>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionCard = (\n\tprops: {\n\t\tconnection: Connection;\n\t\ttoken: string;\n\t\tnamespace: string;\n\t\tclassName?: string;\n\t} & React.HTMLAttributes,\n) => (\n\t\n\t\t\n\t\n);\n", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + }, + { + "path": "registry/new-york/smithery/server-search.tsx", + "content": "\"use client\";\n\nimport Smithery, { AuthenticationError } from \"@smithery/api\";\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport type { ServerListResponse } from \"@smithery/api/resources/servers/servers.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n\tAlertTriangle,\n\tArrowRight,\n\tCheckCircle,\n\tLink,\n\tLoader2,\n\tLock,\n} from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tCombobox,\n\tComboboxContent,\n\tComboboxEmpty,\n\tComboboxInput,\n\tComboboxItem,\n\tComboboxList,\n} from \"@/components/ui/combobox\";\nimport {\n\tItem,\n\tItemContent,\n\tItemDescription,\n\tItemMedia,\n\tItemTitle,\n} from \"@/components/ui/item\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\n\ninterface AuthRequiredBannerProps {\n\tserverName: string;\n\tauthorizationUrl?: string;\n\tcountdown: number | null;\n}\n\nconst AuthRequiredBanner = ({\n\tserverName,\n\tauthorizationUrl,\n\tcountdown,\n}: AuthRequiredBannerProps) => (\n\t
\n\t\t
\n\t\t\t

\n\t\t\t\t{\" \"}\n\t\t\t\tAuthorization required\n\t\t\t

\n\t\t\t

\n\t\t\t\tThis server requires you to authorize access. You should be\n\t\t\t\tautomatically redirected to complete authentication.{\" \"}\n\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t: \"If not, click the link below.\"}\n\t\t\t

\n\t\t\t{authorizationUrl && (\n\t\t\t\t\n\t\t\t\t\tSign in to {serverName}{\" \"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t
\n);\n\ninterface ExistingConnectionWarningBannerProps {\n\tserverName: string;\n\tonUseExisting: () => void;\n\tonCreateNew: () => void;\n\tisConnecting: boolean;\n}\n\nconst ExistingConnectionWarningBanner = ({\n\tserverName,\n\tonUseExisting,\n\tonCreateNew,\n\tisConnecting,\n}: ExistingConnectionWarningBannerProps) => (\n\t
\n\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t\t

\n\t\t\t\t\tConnection already exists\n\t\t\t\t

\n\t\t\t\t

\n\t\t\t\t\tA connection to {serverName} already exists. Would you like to use the\n\t\t\t\t\texisting connection or create a new one?\n\t\t\t\t

\n\t\t\t
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Use existing\"\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Create new\"\n\t\t\t\t)}\n\t\t\t\n\t\t
\n\t
\n);\n\ninterface ConnectionButtonProps {\n\tconnectionStatus?:\n\t\t| \"connected\"\n\t\t| \"auth_required\"\n\t\t| \"existing_connection_warning\"\n\t\t| \"error\";\n\tisConnecting: boolean;\n\tonConnect: () => void;\n}\n\nconst ConnectionButton = ({\n\tconnectionStatus,\n\tisConnecting,\n\tonConnect,\n}: ConnectionButtonProps) => {\n\tswitch (connectionStatus) {\n\t\tcase \"connected\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"auth_required\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"existing_connection_warning\":\n\t\t\t// Buttons are rendered separately in ExistingConnectionWarningBanner\n\t\t\treturn null;\n\t\tdefault:\n\t\t\treturn (\n\t\t\t\t\n\t\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tConnecting...\n\t\t\t\t\t\t\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tConnect \n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t);\n\t}\n};\n\ntype OnExistingConnectionMode = \"warn\" | \"error\" | \"use\" | \"create-new\";\n\ninterface ServerDisplayProps {\n\tserver: ServerListResponse;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ServerDisplay = ({\n\tserver,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ServerDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: activeNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (namespace) {\n\t\t\t\treturn namespace;\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst serverUrl =\n\t\tserver.qualifiedName.startsWith(\"http://\") ||\n\t\tserver.qualifiedName.startsWith(\"https://\")\n\t\t\t? server.qualifiedName\n\t\t\t: `https://server.smithery.ai/${server.qualifiedName}/mcp`;\n\tconst serverName = server.displayName || server.qualifiedName;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\tserverUrl,\n\t\t\t\tserverName,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\n\t\t\t\t\"error connecting to server\",\n\t\t\t\t`server: ${server.qualifiedName}, namespace: ${activeNamespace}`,\n\t\t\t\terror,\n\t\t\t);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t{server.verified && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.qualifiedName}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t{server.description &&

{server.description}

}\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t {\n\t\t\t\t\t\twindow.open(server.homepage, \"_blank\");\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tView Details\n\t\t\t\t\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\ntype ConnectionStatus =\n\t| { status: \"connected\"; connection: Connection }\n\t| { status: \"auth_required\"; connectionId: string; authorizationUrl?: string }\n\t| {\n\t\t\tstatus: \"existing_connection_warning\";\n\t\t\texistingConnectionId: string;\n\t\t\tname: string;\n\t }\n\t| { status: \"error\"; error: unknown };\n\nasync function checkConnectionStatus(\n\tclient: Smithery,\n\tconnectionId: string,\n\tnamespace: string,\n): Promise {\n\ttry {\n\t\tconst jsonRpcResponse = await client.beta.connect.rpc.call(connectionId, {\n\t\t\tnamespace,\n\t\t\tjsonrpc: \"2.0\",\n\t\t\tmethod: \"tools/list\",\n\t\t});\n\t\tconsole.log(\"jsonRpcResponse\", jsonRpcResponse);\n\n\t\tconst connection = await client.beta.connect.connections.get(connectionId, {\n\t\t\tnamespace: namespace,\n\t\t});\n\n\t\treturn {\n\t\t\tstatus: \"connected\",\n\t\t\tconnection,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof AuthenticationError) {\n\t\t\tconst errorData = error.error as unknown as {\n\t\t\t\terror?: { data?: { authorizationUrl?: string } };\n\t\t\t\tdata?: { authorizationUrl?: string };\n\t\t\t};\n\t\t\tconst authorizationUrl =\n\t\t\t\terrorData?.error?.data?.authorizationUrl ||\n\t\t\t\terrorData?.data?.authorizationUrl;\n\t\t\treturn {\n\t\t\t\tstatus: \"auth_required\",\n\t\t\t\tconnectionId,\n\t\t\t\tauthorizationUrl,\n\t\t\t};\n\t\t}\n\t\tconsole.error(\n\t\t\t\"error connecting to server\",\n\t\t\t`connectionId: ${connectionId}, namespace: ${namespace}`,\n\t\t\terror,\n\t\t);\n\t\treturn {\n\t\t\tstatus: \"error\",\n\t\t\terror,\n\t\t};\n\t}\n}\n\nasync function createConnection(\n\tclient: Smithery,\n\tnamespace: string,\n\tmcpUrl: string,\n\tname: string,\n\tonExistingConnection: OnExistingConnectionMode,\n): Promise {\n\t// Check for existing connection by name\n\tconst existingList = await client.beta.connect.connections.list(namespace, {\n\t\tname,\n\t});\n\tconst existing = existingList.connections[0];\n\n\tif (existing) {\n\t\tswitch (onExistingConnection) {\n\t\t\tcase \"error\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: new Error(`Connection \"${name}\" already exists`),\n\t\t\t\t};\n\t\t\tcase \"warn\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"existing_connection_warning\",\n\t\t\t\t\texistingConnectionId: existing.connectionId,\n\t\t\t\t\tname,\n\t\t\t\t};\n\t\t\tcase \"use\":\n\t\t\t\treturn checkConnectionStatus(client, existing.connectionId, namespace);\n\t\t\tcase \"create-new\":\n\t\t\t\t// Continue to create new connection\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Create new connection (API auto-generates unique ID)\n\tconsole.log(\"creating connection\", name);\n\tconst connection = await client.beta.connect.connections.create(namespace, {\n\t\tmcpUrl,\n\t\tname,\n\t});\n\tconsole.log(\"connection\", connection);\n\n\tif (connection.status?.state === \"auth_required\") {\n\t\treturn {\n\t\t\tstatus: \"auth_required\",\n\t\t\tconnectionId: connection.connectionId,\n\t\t\tauthorizationUrl: connection.status?.authorizationUrl,\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: \"connected\",\n\t\tconnection,\n\t};\n}\n\n// Helper function to detect if input is a URL\nconst isValidUrl = (str: string): boolean => {\n\tif (!str.trim()) return false;\n\ttry {\n\t\tconst url = new URL(str.trim());\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n};\n\ninterface ExternalURLDisplayProps {\n\turl: string;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ExternalURLDisplay = ({\n\turl,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ExternalURLDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: defaultNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst activeNamespace = namespace || defaultNamespace;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\turl,\n\t\t\t\turl,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\"error connecting to external URL\", error);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when auth is required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tconnectionData?.status === \"auth_required\" &&\n\t\t\tconnectionData.authorizationUrl &&\n\t\t\tcountdown === 0\n\t\t) {\n\t\t\twindow.open(connectionData.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [connectionData, countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

External MCP Server

\n\t\t\t\t\t

{url}

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nconst ServerSearchInner = ({\n\ttoken,\n\tnamespace,\n\tonExistingConnection = \"warn\",\n}: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => {\n\tconst [query, setQuery] = useState(\"\");\n\tconst [selectedServer, setSelectedServer] =\n\t\tuseState(null);\n\tconst [selectedExternalUrl, setSelectedExternalUrl] = useState(\n\t\tnull,\n\t);\n\tconst debouncedQuery = useDebounce(query, 300);\n\tconst isUrl = isValidUrl(query);\n\n\tconst { data, isLoading } = useQuery({\n\t\tqueryKey: [\"servers\", token, debouncedQuery],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconsole.log(\"searching\", debouncedQuery);\n\t\t\tconst servers = debouncedQuery\n\t\t\t\t? await client.servers.list({ q: debouncedQuery, pageSize: 3 })\n\t\t\t\t: await client.servers.list();\n\t\t\tconsole.log(`servers for ${debouncedQuery}`, servers);\n\t\t\treturn servers;\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst servers = data?.servers ?? [];\n\n\tconst handleKeyDown = (e: React.KeyboardEvent) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\te.preventDefault();\n\t\t\tif (isUrl) {\n\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\tsetSelectedServer(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t} else if (servers.length > 0) {\n\t\t\t\tsetSelectedServer(servers[0]);\n\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\tvalue={selectedServer}\n\t\t\t\tonValueChange={(server) => {\n\t\t\t\t\tsetSelectedServer(server);\n\t\t\t\t\t// Only clear external URL when a server is actually selected\n\t\t\t\t\t// (not when combobox fires null from closing without selection)\n\t\t\t\t\tif (server) {\n\t\t\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tonInputValueChange={(value) => setQuery(value)}\n\t\t\t\titemToStringLabel={(server) =>\n\t\t\t\t\tserver.displayName || server.qualifiedName\n\t\t\t\t}\n\t\t\t\tdefaultOpen={true}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{isUrl ? (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\t\t\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\t\t\t\t\t\tsetSelectedServer(null);\n\t\t\t\t\t\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tConnect to external MCP URL\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{query.trim()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{servers.length === 0 && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{isLoading ? \"Loading...\" : \"No servers found.\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{servers.map((server) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.description || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\n\n\t\t\t{selectedServer && token && namespace && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{selectedExternalUrl && token && (\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t);\n};\n\nexport const ServerSearch = (props: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => (\n\t\n\t\t\n\t\n);\n", + "type": "registry:component", + "target": "components/smithery/server-search.tsx" + }, + { + "path": "registry/new-york/smithery/connections-list.tsx", + "content": "\"use client\";\n\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { Plus, RefreshCw, X } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Toggle } from \"@/components/ui/toggle\";\nimport { cn } from \"@/lib/utils\";\nimport { ConnectionCard } from \"@/registry/new-york/smithery/connection-card\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\nimport { ServerSearch } from \"@/registry/new-york/smithery/server-search\";\nimport {\n\tgetDefaultNamespace,\n\tgetSmitheryClient,\n} from \"@/registry/new-york/smithery/smithery-utils\";\n\nconst ConnectionsListInner = ({\n\ttoken,\n\tnamespace,\n\tdefaultActiveConnectionId,\n\tonActiveConnectionIdChange,\n\tdefaultShowSearchServers = true,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tdefaultActiveConnectionId || null,\n\t);\n\tconst [showSearchServers, setShowSearchServers] = useState(\n\t\tdefaultShowSearchServers || false,\n\t);\n\tconst { data, isLoading, error, refetch, isFetching } = useQuery({\n\t\tqueryKey: [\"connections\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst { connections } =\n\t\t\t\tawait client.beta.connect.connections.list(namespaceToUse);\n\t\t\treturn { connections, namespace: namespaceToUse };\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tuseEffect(() => {\n\t\tif (data?.connections && !defaultActiveConnectionId) {\n\t\t\tsetActiveConnectionId(data?.connections[0]?.connectionId || null);\n\t\t}\n\t}, [data?.connections, defaultActiveConnectionId]);\n\n\tuseEffect(() => {\n\t\tif (activeConnectionId) {\n\t\t\tonActiveConnectionIdChange(activeConnectionId);\n\t\t}\n\t}, [activeConnectionId, onActiveConnectionIdChange]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

Connections

\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\t{showSearchServers ? (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t\n\t\t\t\t\t refetch()}\n\t\t\t\t\t\tdisabled={isFetching}\n\t\t\t\t\t\ttitle=\"Refresh connections\"\n\t\t\t\t\t>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t{showSearchServers && (\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{isLoading &&

Loading...

}\n\t\t\t\t{error && (\n\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t{data.connections.length === 0 && (\n\t\t\t\t\t\t\t

No connections found

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{data.connections.map((connection: Connection) => (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t setActiveConnectionId(connection.connectionId)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t))}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionsList = (props: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => (\n\t\n\t\t\n\t\n);\n", + "type": "registry:component", + "target": "components/smithery/connections-list.tsx" + } + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/public/r/smithery-connections.json b/public/r/smithery-connections.json index 44e21af..bccd0a1 100644 --- a/public/r/smithery-connections.json +++ b/public/r/smithery-connections.json @@ -43,6 +43,12 @@ "type": "registry:component", "target": "components/smithery/query-client-wrapper.tsx" }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "content": "import Smithery from \"@smithery/api\";\n\nexport async function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nexport const getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, { "path": "registry/new-york/smithery/connection-context.tsx", "content": "\"use client\";\n\nimport { createContext, useContext } from \"react\";\n\n// Context for connection config - consumed by ToolDetailDialog for code generation\nexport interface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nexport const ConnectionConfigContext = createContext(\n\tnull,\n);\n\nexport function useConnectionConfig() {\n\treturn useContext(ConnectionConfigContext);\n}\n", @@ -57,7 +63,7 @@ }, { "path": "registry/new-york/smithery/tool-detail-dialog.tsx", - "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", + "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst _namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", "type": "registry:component", "target": "components/smithery/tool-detail-dialog.tsx" }, @@ -73,15 +79,33 @@ "type": "registry:component", "target": "components/smithery/tools-panel.tsx" }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "content": "\"use client\";\n\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { Trash2 } from \"lucide-react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\nimport { getSmitheryClient } from \"@/registry/new-york/smithery/smithery-utils\";\n\nconst ConnectionCardInner = ({\n\tconnection,\n\ttoken,\n\tnamespace,\n\tclassName,\n\t...rest\n}: {\n\tconnection: Connection;\n\ttoken: string;\n\tnamespace: string;\n\tclassName?: string;\n} & React.HTMLAttributes) => {\n\tconst queryClient = useQueryClient();\n\tconst deleteMutation = useMutation({\n\t\tmutationFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tawait client.beta.connect.connections.delete(connection.connectionId, {\n\t\t\t\tnamespace: namespace,\n\t\t\t});\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t});\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{connection.name.charAt(0)}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.serverInfo?.title ??\n\t\t\t\t\t\t\tconnection.serverInfo?.name ??\n\t\t\t\t\t\t\tconnection.name}\n\t\t\t\t\t\t{connection.connectionId && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{\"•\".repeat(Math.min(connection.connectionId.length - 10, 4))}\n\t\t\t\t\t\t\t\t{connection.connectionId.slice(-10)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.mcpUrl}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.metadata && JSON.stringify(connection.metadata)}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t\t deleteMutation.mutate()}\n\t\t\t\t\tdisabled={deleteMutation.isPending}\n\t\t\t\t>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionCard = (\n\tprops: {\n\t\tconnection: Connection;\n\t\ttoken: string;\n\t\tnamespace: string;\n\t\tclassName?: string;\n\t} & React.HTMLAttributes,\n) => (\n\t\n\t\t\n\t\n);\n", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + }, + { + "path": "registry/new-york/smithery/connections-list.tsx", + "content": "\"use client\";\n\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { Plus, RefreshCw, X } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Toggle } from \"@/components/ui/toggle\";\nimport { cn } from \"@/lib/utils\";\nimport { ConnectionCard } from \"@/registry/new-york/smithery/connection-card\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\nimport { ServerSearch } from \"@/registry/new-york/smithery/server-search\";\nimport {\n\tgetDefaultNamespace,\n\tgetSmitheryClient,\n} from \"@/registry/new-york/smithery/smithery-utils\";\n\nconst ConnectionsListInner = ({\n\ttoken,\n\tnamespace,\n\tdefaultActiveConnectionId,\n\tonActiveConnectionIdChange,\n\tdefaultShowSearchServers = true,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tdefaultActiveConnectionId || null,\n\t);\n\tconst [showSearchServers, setShowSearchServers] = useState(\n\t\tdefaultShowSearchServers || false,\n\t);\n\tconst { data, isLoading, error, refetch, isFetching } = useQuery({\n\t\tqueryKey: [\"connections\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst { connections } =\n\t\t\t\tawait client.beta.connect.connections.list(namespaceToUse);\n\t\t\treturn { connections, namespace: namespaceToUse };\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tuseEffect(() => {\n\t\tif (data?.connections && !defaultActiveConnectionId) {\n\t\t\tsetActiveConnectionId(data?.connections[0]?.connectionId || null);\n\t\t}\n\t}, [data?.connections, defaultActiveConnectionId]);\n\n\tuseEffect(() => {\n\t\tif (activeConnectionId) {\n\t\t\tonActiveConnectionIdChange(activeConnectionId);\n\t\t}\n\t}, [activeConnectionId, onActiveConnectionIdChange]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

Connections

\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\t{showSearchServers ? (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t\n\t\t\t\t\t refetch()}\n\t\t\t\t\t\tdisabled={isFetching}\n\t\t\t\t\t\ttitle=\"Refresh connections\"\n\t\t\t\t\t>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t{showSearchServers && (\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{isLoading &&

Loading...

}\n\t\t\t\t{error && (\n\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t{data.connections.length === 0 && (\n\t\t\t\t\t\t\t

No connections found

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{data.connections.map((connection: Connection) => (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t setActiveConnectionId(connection.connectionId)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t))}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionsList = (props: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => (\n\t\n\t\t\n\t\n);\n", + "type": "registry:component", + "target": "components/smithery/connections-list.tsx" + }, + { + "path": "registry/new-york/smithery/active-connection.tsx", + "content": "\"use client\";\n\nimport { createMCPClient } from \"@ai-sdk/mcp\";\nimport { SmitheryTransport } from \"@smithery/api/mcp\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport type { ToolExecutionOptions } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport {\n\tConnectionConfigContext,\n\tuseConnectionConfig,\n} from \"@/registry/new-york/smithery/connection-context\";\nimport {\n\tgetDefaultNamespace,\n\tgetSmitheryClient,\n} from \"@/registry/new-york/smithery/smithery-utils\";\nimport { ToolsPanel } from \"@/registry/new-york/smithery/tools-panel\";\n\n// Re-export useConnectionConfig for backward compatibility\nexport { useConnectionConfig };\n\nexport const ActiveConnection = ({\n\ttoken,\n\tnamespace,\n\tconnectionId,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tconnectionId: string;\n}) => {\n\tconst [countdown, setCountdown] = useState(null);\n\tconst [hasRedirected, setHasRedirected] = useState(false);\n\n\tconst { data, isLoading, error } = useQuery({\n\t\tqueryKey: [\"connection\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst data = await client.beta.connect.connections.get(connectionId, {\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\treturn { namespace: namespaceToUse, ...data };\n\t\t},\n\t\t// Poll every 2 seconds when auth_required, stop when connected or error\n\t\trefetchInterval: (query) => {\n\t\t\tconst state = query.state.data?.status?.state;\n\t\t\tif (state === \"auth_required\") {\n\t\t\t\treturn 2000;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t});\n\n\t// Start countdown when auth_required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tcountdown === null &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetCountdown(5);\n\t\t}\n\t\t// Reset state when connectionId changes or status is no longer auth_required\n\t\tif (data?.status?.state !== \"auth_required\") {\n\t\t\tsetCountdown(null);\n\t\t\tsetHasRedirected(false);\n\t\t}\n\t}, [data?.status?.state, countdown, hasRedirected]);\n\n\t// Countdown timer\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when countdown reaches 0\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tdata?.status?.authorizationUrl &&\n\t\t\tcountdown === 0 &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetHasRedirected(true);\n\t\t\twindow.open(data.status.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [data?.status, countdown, hasRedirected]);\n\n\tconst clientQuery = useQuery({\n\t\tqueryKey: [\"mcp-client\", token, connectionId, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst namespaceToUse =\n\t\t\t\tnamespace || (await getDefaultNamespace(getSmitheryClient(token)));\n\t\t\tconst transport = new SmitheryTransport({\n\t\t\t\tclient: getSmitheryClient(token),\n\t\t\t\tconnectionId: connectionId,\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\tconst mcpClient = await createMCPClient({ transport });\n\t\t\treturn mcpClient;\n\t\t},\n\t\tenabled: !!connectionId && data?.status?.state === \"connected\",\n\t});\n\n\tconst toolsQuery = useQuery({\n\t\tqueryKey: [\"tools\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tif (!clientQuery.data) {\n\t\t\t\tthrow new Error(\"Client not available\");\n\t\t\t}\n\t\t\tconst client = clientQuery.data;\n\t\t\treturn await client.tools();\n\t\t},\n\t\tenabled: !!clientQuery.data && data?.status?.state === \"connected\",\n\t});\n\tconst handleExecute = async (\n\t\ttoolName: string,\n\t\tparams: Record,\n\t) => {\n\t\tif (!toolsQuery.data) {\n\t\t\tthrow new Error(\"Tools not available\");\n\t\t}\n\t\tconst tool = toolsQuery.data[toolName];\n\t\tif (!tool) {\n\t\t\tthrow new Error(`Tool ${toolName} not found`);\n\t\t}\n\t\t// The execute method from AI SDK tools expects (params, options)\n\t\t// We're executing tools directly, so provide minimal required options\n\t\tconst options: ToolExecutionOptions = {\n\t\t\ttoolCallId: `manual-${Date.now()}`,\n\t\t\tmessages: [],\n\t\t};\n\t\treturn await tool.execute(params, options);\n\t};\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading connection...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{data.iconUrl && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{(\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.title ??\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.name ??\n\t\t\t\t\t\t\t\t\t\t\tdata.name\n\t\t\t\t\t\t\t\t\t\t)?.charAt(0)}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\t{data.serverInfo?.title ?? data.serverInfo?.name ?? data.name}\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.websiteUrl && (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\tView website\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.serverInfo?.description && (\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.description}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\tConnection created:{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t

·

\n\t\t\t\t\t\t\t

Connection ID: {data.connectionId}

\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.metadata && (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tMetadata\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t{JSON.stringify(data.metadata, null, 2)}\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data?.status && data.status.state === \"auth_required\" && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Auth required

\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t\t\t\t: \"You should be automatically redirected to authenticate. If not, click the link below:\"}\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tOpen authentication window\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading tools...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\tError: {toolsQuery.error.message}\n\t\t\t\t\t\t

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.data && data && (\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n", + "type": "registry:component", + "target": "components/smithery/active-connection.tsx" + }, { "path": "registry/new-york/smithery/server-search.tsx", - "content": "\"use client\";\n\nimport Smithery, { AuthenticationError } from \"@smithery/api\";\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport type { ServerListResponse } from \"@smithery/api/resources/servers/servers.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n\tAlertTriangle,\n\tArrowRight,\n\tCheckCircle,\n\tLink,\n\tLoader2,\n\tLock,\n} from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tCombobox,\n\tComboboxContent,\n\tComboboxEmpty,\n\tComboboxInput,\n\tComboboxItem,\n\tComboboxList,\n} from \"@/components/ui/combobox\";\nimport {\n\tItem,\n\tItemContent,\n\tItemDescription,\n\tItemMedia,\n\tItemTitle,\n} from \"@/components/ui/item\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\n\ninterface AuthRequiredBannerProps {\n\tserverName: string;\n\tauthorizationUrl?: string;\n\tcountdown: number | null;\n}\n\nconst AuthRequiredBanner = ({\n\tserverName,\n\tauthorizationUrl,\n\tcountdown,\n}: AuthRequiredBannerProps) => (\n\t
\n\t\t
\n\t\t\t

\n\t\t\t\t{\" \"}\n\t\t\t\tAuthorization required\n\t\t\t

\n\t\t\t

\n\t\t\t\tThis server requires you to authorize access. You should be\n\t\t\t\tautomatically redirected to complete authentication.{\" \"}\n\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t: \"If not, click the link below.\"}\n\t\t\t

\n\t\t\t{authorizationUrl && (\n\t\t\t\t\n\t\t\t\t\tSign in to {serverName}{\" \"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t
\n);\n\ninterface ExistingConnectionWarningBannerProps {\n\tserverName: string;\n\tonUseExisting: () => void;\n\tonCreateNew: () => void;\n\tisConnecting: boolean;\n}\n\nconst ExistingConnectionWarningBanner = ({\n\tserverName,\n\tonUseExisting,\n\tonCreateNew,\n\tisConnecting,\n}: ExistingConnectionWarningBannerProps) => (\n\t
\n\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t\t

\n\t\t\t\t\tConnection already exists\n\t\t\t\t

\n\t\t\t\t

\n\t\t\t\t\tA connection to {serverName} already exists. Would you like to use the\n\t\t\t\t\texisting connection or create a new one?\n\t\t\t\t

\n\t\t\t
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Use existing\"\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Create new\"\n\t\t\t\t)}\n\t\t\t\n\t\t
\n\t
\n);\n\ninterface ConnectionButtonProps {\n\tconnectionStatus?:\n\t\t| \"connected\"\n\t\t| \"auth_required\"\n\t\t| \"existing_connection_warning\"\n\t\t| \"error\";\n\tisConnecting: boolean;\n\tonConnect: () => void;\n}\n\nconst ConnectionButton = ({\n\tconnectionStatus,\n\tisConnecting,\n\tonConnect,\n}: ConnectionButtonProps) => {\n\tswitch (connectionStatus) {\n\t\tcase \"connected\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"auth_required\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"existing_connection_warning\":\n\t\t\t// Buttons are rendered separately in ExistingConnectionWarningBanner\n\t\t\treturn null;\n\t\tdefault:\n\t\t\treturn (\n\t\t\t\t\n\t\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tConnecting...\n\t\t\t\t\t\t\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tConnect \n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t);\n\t}\n};\n\ntype OnExistingConnectionMode = \"warn\" | \"error\" | \"use\" | \"create-new\";\n\ninterface ServerDisplayProps {\n\tserver: ServerListResponse;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ServerDisplay = ({\n\tserver,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ServerDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: activeNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (namespace) {\n\t\t\t\treturn namespace;\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst serverUrl =\n\t\tserver.qualifiedName.startsWith(\"http://\") ||\n\t\tserver.qualifiedName.startsWith(\"https://\")\n\t\t\t? server.qualifiedName\n\t\t\t: `https://server.smithery.ai/${server.qualifiedName}/mcp`;\n\tconst serverName = server.displayName || server.qualifiedName;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\tserverUrl,\n\t\t\t\tserverName,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\n\t\t\t\t\"error connecting to server\",\n\t\t\t\t`server: ${server.qualifiedName}, namespace: ${activeNamespace}`,\n\t\t\t\terror,\n\t\t\t);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t{server.verified && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.qualifiedName}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t{server.description &&

{server.description}

}\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t {\n\t\t\t\t\t\twindow.open(server.homepage, \"_blank\");\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tView Details\n\t\t\t\t\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\ntype ConnectionStatus =\n\t| { status: \"connected\"; connection: Connection }\n\t| { status: \"auth_required\"; connectionId: string; authorizationUrl?: string }\n\t| {\n\t\t\tstatus: \"existing_connection_warning\";\n\t\t\texistingConnectionId: string;\n\t\t\tname: string;\n\t }\n\t| { status: \"error\"; error: unknown };\n\nasync function checkConnectionStatus(\n\tclient: Smithery,\n\tconnectionId: string,\n\tnamespace: string,\n): Promise {\n\ttry {\n\t\tconst jsonRpcResponse = await client.beta.connect.mcp.call(connectionId, {\n\t\t\tnamespace,\n\t\t\tjsonrpc: \"2.0\",\n\t\t\tmethod: \"tools/list\",\n\t\t});\n\t\tconsole.log(\"jsonRpcResponse\", jsonRpcResponse);\n\n\t\tconst connection = await client.beta.connect.connections.get(connectionId, {\n\t\t\tnamespace: namespace,\n\t\t});\n\n\t\treturn {\n\t\t\tstatus: \"connected\",\n\t\t\tconnection,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof AuthenticationError) {\n\t\t\tconst errorData = error.error as unknown as {\n\t\t\t\terror?: { data?: { authorizationUrl?: string } };\n\t\t\t\tdata?: { authorizationUrl?: string };\n\t\t\t};\n\t\t\tconst authorizationUrl =\n\t\t\t\terrorData?.error?.data?.authorizationUrl ||\n\t\t\t\terrorData?.data?.authorizationUrl;\n\t\t\treturn {\n\t\t\t\tstatus: \"auth_required\",\n\t\t\t\tconnectionId,\n\t\t\t\tauthorizationUrl,\n\t\t\t};\n\t\t}\n\t\tconsole.error(\n\t\t\t\"error connecting to server\",\n\t\t\t`connectionId: ${connectionId}, namespace: ${namespace}`,\n\t\t\terror,\n\t\t);\n\t\treturn {\n\t\t\tstatus: \"error\",\n\t\t\terror,\n\t\t};\n\t}\n}\n\nasync function createConnection(\n\tclient: Smithery,\n\tnamespace: string,\n\tmcpUrl: string,\n\tname: string,\n\tonExistingConnection: OnExistingConnectionMode,\n): Promise {\n\t// Check for existing connection by name\n\tconst existingList = await client.beta.connect.connections.list(namespace, {\n\t\tname,\n\t});\n\tconst existing = existingList.connections[0];\n\n\tif (existing) {\n\t\tswitch (onExistingConnection) {\n\t\t\tcase \"error\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: new Error(`Connection \"${name}\" already exists`),\n\t\t\t\t};\n\t\t\tcase \"warn\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"existing_connection_warning\",\n\t\t\t\t\texistingConnectionId: existing.connectionId,\n\t\t\t\t\tname,\n\t\t\t\t};\n\t\t\tcase \"use\":\n\t\t\t\treturn checkConnectionStatus(client, existing.connectionId, namespace);\n\t\t\tcase \"create-new\":\n\t\t\t\t// Continue to create new connection\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Create new connection (API auto-generates unique ID)\n\tconsole.log(\"creating connection\", name);\n\tconst connection = await client.beta.connect.connections.create(namespace, {\n\t\tmcpUrl,\n\t\tname,\n\t});\n\tconsole.log(\"connection\", connection);\n\n\tif (connection.status?.state === \"auth_required\") {\n\t\treturn {\n\t\t\tstatus: \"auth_required\",\n\t\t\tconnectionId: connection.connectionId,\n\t\t\tauthorizationUrl: connection.status?.authorizationUrl,\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: \"connected\",\n\t\tconnection,\n\t};\n}\n\n// Helper function to detect if input is a URL\nconst isValidUrl = (str: string): boolean => {\n\tif (!str.trim()) return false;\n\ttry {\n\t\tconst url = new URL(str.trim());\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n};\n\ninterface ExternalURLDisplayProps {\n\turl: string;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ExternalURLDisplay = ({\n\turl,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ExternalURLDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: defaultNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst activeNamespace = namespace || defaultNamespace;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\turl,\n\t\t\t\turl,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\"error connecting to external URL\", error);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when auth is required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tconnectionData?.status === \"auth_required\" &&\n\t\t\tconnectionData.authorizationUrl &&\n\t\t\tcountdown === 0\n\t\t) {\n\t\t\twindow.open(connectionData.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [connectionData, countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

External MCP Server

\n\t\t\t\t\t

{url}

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nconst ServerSearchInner = ({\n\ttoken,\n\tnamespace,\n\tonExistingConnection = \"warn\",\n}: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => {\n\tconst [query, setQuery] = useState(\"\");\n\tconst [selectedServer, setSelectedServer] =\n\t\tuseState(null);\n\tconst [selectedExternalUrl, setSelectedExternalUrl] = useState(\n\t\tnull,\n\t);\n\tconst debouncedQuery = useDebounce(query, 300);\n\tconst isUrl = isValidUrl(query);\n\n\tconst { data, isLoading } = useQuery({\n\t\tqueryKey: [\"servers\", token, debouncedQuery],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconsole.log(\"searching\", debouncedQuery);\n\t\t\tconst servers = debouncedQuery\n\t\t\t\t? await client.servers.list({ q: debouncedQuery, pageSize: 3 })\n\t\t\t\t: await client.servers.list();\n\t\t\tconsole.log(`servers for ${debouncedQuery}`, servers);\n\t\t\treturn servers;\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst servers = data?.servers ?? [];\n\n\tconst handleKeyDown = (e: React.KeyboardEvent) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\te.preventDefault();\n\t\t\tif (isUrl) {\n\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\tsetSelectedServer(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t} else if (servers.length > 0) {\n\t\t\t\tsetSelectedServer(servers[0]);\n\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\tvalue={selectedServer}\n\t\t\t\tonValueChange={(server) => {\n\t\t\t\t\tsetSelectedServer(server);\n\t\t\t\t\t// Only clear external URL when a server is actually selected\n\t\t\t\t\t// (not when combobox fires null from closing without selection)\n\t\t\t\t\tif (server) {\n\t\t\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tonInputValueChange={(value) => setQuery(value)}\n\t\t\t\titemToStringLabel={(server) =>\n\t\t\t\t\tserver.displayName || server.qualifiedName\n\t\t\t\t}\n\t\t\t\tdefaultOpen={true}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{isUrl ? (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\t\t\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\t\t\t\t\t\tsetSelectedServer(null);\n\t\t\t\t\t\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tConnect to external MCP URL\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{query.trim()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{servers.length === 0 && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{isLoading ? \"Loading...\" : \"No servers found.\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{servers.map((server) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.description || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\n\n\t\t\t{selectedServer && token && namespace && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{selectedExternalUrl && token && (\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t);\n};\n\nexport const ServerSearch = (props: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => (\n\t\n\t\t\n\t\n);\n", + "content": "\"use client\";\n\nimport Smithery, { AuthenticationError } from \"@smithery/api\";\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport type { ServerListResponse } from \"@smithery/api/resources/servers/servers.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n\tAlertTriangle,\n\tArrowRight,\n\tCheckCircle,\n\tLink,\n\tLoader2,\n\tLock,\n} from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tCombobox,\n\tComboboxContent,\n\tComboboxEmpty,\n\tComboboxInput,\n\tComboboxItem,\n\tComboboxList,\n} from \"@/components/ui/combobox\";\nimport {\n\tItem,\n\tItemContent,\n\tItemDescription,\n\tItemMedia,\n\tItemTitle,\n} from \"@/components/ui/item\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\n\ninterface AuthRequiredBannerProps {\n\tserverName: string;\n\tauthorizationUrl?: string;\n\tcountdown: number | null;\n}\n\nconst AuthRequiredBanner = ({\n\tserverName,\n\tauthorizationUrl,\n\tcountdown,\n}: AuthRequiredBannerProps) => (\n\t
\n\t\t
\n\t\t\t

\n\t\t\t\t{\" \"}\n\t\t\t\tAuthorization required\n\t\t\t

\n\t\t\t

\n\t\t\t\tThis server requires you to authorize access. You should be\n\t\t\t\tautomatically redirected to complete authentication.{\" \"}\n\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t: \"If not, click the link below.\"}\n\t\t\t

\n\t\t\t{authorizationUrl && (\n\t\t\t\t\n\t\t\t\t\tSign in to {serverName}{\" \"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t
\n);\n\ninterface ExistingConnectionWarningBannerProps {\n\tserverName: string;\n\tonUseExisting: () => void;\n\tonCreateNew: () => void;\n\tisConnecting: boolean;\n}\n\nconst ExistingConnectionWarningBanner = ({\n\tserverName,\n\tonUseExisting,\n\tonCreateNew,\n\tisConnecting,\n}: ExistingConnectionWarningBannerProps) => (\n\t
\n\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t\t

\n\t\t\t\t\tConnection already exists\n\t\t\t\t

\n\t\t\t\t

\n\t\t\t\t\tA connection to {serverName} already exists. Would you like to use the\n\t\t\t\t\texisting connection or create a new one?\n\t\t\t\t

\n\t\t\t
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Use existing\"\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Create new\"\n\t\t\t\t)}\n\t\t\t\n\t\t
\n\t
\n);\n\ninterface ConnectionButtonProps {\n\tconnectionStatus?:\n\t\t| \"connected\"\n\t\t| \"auth_required\"\n\t\t| \"existing_connection_warning\"\n\t\t| \"error\";\n\tisConnecting: boolean;\n\tonConnect: () => void;\n}\n\nconst ConnectionButton = ({\n\tconnectionStatus,\n\tisConnecting,\n\tonConnect,\n}: ConnectionButtonProps) => {\n\tswitch (connectionStatus) {\n\t\tcase \"connected\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"auth_required\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"existing_connection_warning\":\n\t\t\t// Buttons are rendered separately in ExistingConnectionWarningBanner\n\t\t\treturn null;\n\t\tdefault:\n\t\t\treturn (\n\t\t\t\t\n\t\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tConnecting...\n\t\t\t\t\t\t\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tConnect \n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t);\n\t}\n};\n\ntype OnExistingConnectionMode = \"warn\" | \"error\" | \"use\" | \"create-new\";\n\ninterface ServerDisplayProps {\n\tserver: ServerListResponse;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ServerDisplay = ({\n\tserver,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ServerDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: activeNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (namespace) {\n\t\t\t\treturn namespace;\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst serverUrl =\n\t\tserver.qualifiedName.startsWith(\"http://\") ||\n\t\tserver.qualifiedName.startsWith(\"https://\")\n\t\t\t? server.qualifiedName\n\t\t\t: `https://server.smithery.ai/${server.qualifiedName}/mcp`;\n\tconst serverName = server.displayName || server.qualifiedName;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\tserverUrl,\n\t\t\t\tserverName,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\n\t\t\t\t\"error connecting to server\",\n\t\t\t\t`server: ${server.qualifiedName}, namespace: ${activeNamespace}`,\n\t\t\t\terror,\n\t\t\t);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t{server.verified && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.qualifiedName}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t{server.description &&

{server.description}

}\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t {\n\t\t\t\t\t\twindow.open(server.homepage, \"_blank\");\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tView Details\n\t\t\t\t\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\ntype ConnectionStatus =\n\t| { status: \"connected\"; connection: Connection }\n\t| { status: \"auth_required\"; connectionId: string; authorizationUrl?: string }\n\t| {\n\t\t\tstatus: \"existing_connection_warning\";\n\t\t\texistingConnectionId: string;\n\t\t\tname: string;\n\t }\n\t| { status: \"error\"; error: unknown };\n\nasync function checkConnectionStatus(\n\tclient: Smithery,\n\tconnectionId: string,\n\tnamespace: string,\n): Promise {\n\ttry {\n\t\tconst jsonRpcResponse = await client.beta.connect.rpc.call(connectionId, {\n\t\t\tnamespace,\n\t\t\tjsonrpc: \"2.0\",\n\t\t\tmethod: \"tools/list\",\n\t\t});\n\t\tconsole.log(\"jsonRpcResponse\", jsonRpcResponse);\n\n\t\tconst connection = await client.beta.connect.connections.get(connectionId, {\n\t\t\tnamespace: namespace,\n\t\t});\n\n\t\treturn {\n\t\t\tstatus: \"connected\",\n\t\t\tconnection,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof AuthenticationError) {\n\t\t\tconst errorData = error.error as unknown as {\n\t\t\t\terror?: { data?: { authorizationUrl?: string } };\n\t\t\t\tdata?: { authorizationUrl?: string };\n\t\t\t};\n\t\t\tconst authorizationUrl =\n\t\t\t\terrorData?.error?.data?.authorizationUrl ||\n\t\t\t\terrorData?.data?.authorizationUrl;\n\t\t\treturn {\n\t\t\t\tstatus: \"auth_required\",\n\t\t\t\tconnectionId,\n\t\t\t\tauthorizationUrl,\n\t\t\t};\n\t\t}\n\t\tconsole.error(\n\t\t\t\"error connecting to server\",\n\t\t\t`connectionId: ${connectionId}, namespace: ${namespace}`,\n\t\t\terror,\n\t\t);\n\t\treturn {\n\t\t\tstatus: \"error\",\n\t\t\terror,\n\t\t};\n\t}\n}\n\nasync function createConnection(\n\tclient: Smithery,\n\tnamespace: string,\n\tmcpUrl: string,\n\tname: string,\n\tonExistingConnection: OnExistingConnectionMode,\n): Promise {\n\t// Check for existing connection by name\n\tconst existingList = await client.beta.connect.connections.list(namespace, {\n\t\tname,\n\t});\n\tconst existing = existingList.connections[0];\n\n\tif (existing) {\n\t\tswitch (onExistingConnection) {\n\t\t\tcase \"error\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: new Error(`Connection \"${name}\" already exists`),\n\t\t\t\t};\n\t\t\tcase \"warn\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"existing_connection_warning\",\n\t\t\t\t\texistingConnectionId: existing.connectionId,\n\t\t\t\t\tname,\n\t\t\t\t};\n\t\t\tcase \"use\":\n\t\t\t\treturn checkConnectionStatus(client, existing.connectionId, namespace);\n\t\t\tcase \"create-new\":\n\t\t\t\t// Continue to create new connection\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Create new connection (API auto-generates unique ID)\n\tconsole.log(\"creating connection\", name);\n\tconst connection = await client.beta.connect.connections.create(namespace, {\n\t\tmcpUrl,\n\t\tname,\n\t});\n\tconsole.log(\"connection\", connection);\n\n\tif (connection.status?.state === \"auth_required\") {\n\t\treturn {\n\t\t\tstatus: \"auth_required\",\n\t\t\tconnectionId: connection.connectionId,\n\t\t\tauthorizationUrl: connection.status?.authorizationUrl,\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: \"connected\",\n\t\tconnection,\n\t};\n}\n\n// Helper function to detect if input is a URL\nconst isValidUrl = (str: string): boolean => {\n\tif (!str.trim()) return false;\n\ttry {\n\t\tconst url = new URL(str.trim());\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n};\n\ninterface ExternalURLDisplayProps {\n\turl: string;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ExternalURLDisplay = ({\n\turl,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ExternalURLDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: defaultNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst activeNamespace = namespace || defaultNamespace;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\turl,\n\t\t\t\turl,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\"error connecting to external URL\", error);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when auth is required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tconnectionData?.status === \"auth_required\" &&\n\t\t\tconnectionData.authorizationUrl &&\n\t\t\tcountdown === 0\n\t\t) {\n\t\t\twindow.open(connectionData.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [connectionData, countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

External MCP Server

\n\t\t\t\t\t

{url}

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nconst ServerSearchInner = ({\n\ttoken,\n\tnamespace,\n\tonExistingConnection = \"warn\",\n}: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => {\n\tconst [query, setQuery] = useState(\"\");\n\tconst [selectedServer, setSelectedServer] =\n\t\tuseState(null);\n\tconst [selectedExternalUrl, setSelectedExternalUrl] = useState(\n\t\tnull,\n\t);\n\tconst debouncedQuery = useDebounce(query, 300);\n\tconst isUrl = isValidUrl(query);\n\n\tconst { data, isLoading } = useQuery({\n\t\tqueryKey: [\"servers\", token, debouncedQuery],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconsole.log(\"searching\", debouncedQuery);\n\t\t\tconst servers = debouncedQuery\n\t\t\t\t? await client.servers.list({ q: debouncedQuery, pageSize: 3 })\n\t\t\t\t: await client.servers.list();\n\t\t\tconsole.log(`servers for ${debouncedQuery}`, servers);\n\t\t\treturn servers;\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst servers = data?.servers ?? [];\n\n\tconst handleKeyDown = (e: React.KeyboardEvent) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\te.preventDefault();\n\t\t\tif (isUrl) {\n\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\tsetSelectedServer(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t} else if (servers.length > 0) {\n\t\t\t\tsetSelectedServer(servers[0]);\n\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\tvalue={selectedServer}\n\t\t\t\tonValueChange={(server) => {\n\t\t\t\t\tsetSelectedServer(server);\n\t\t\t\t\t// Only clear external URL when a server is actually selected\n\t\t\t\t\t// (not when combobox fires null from closing without selection)\n\t\t\t\t\tif (server) {\n\t\t\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tonInputValueChange={(value) => setQuery(value)}\n\t\t\t\titemToStringLabel={(server) =>\n\t\t\t\t\tserver.displayName || server.qualifiedName\n\t\t\t\t}\n\t\t\t\tdefaultOpen={true}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{isUrl ? (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\t\t\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\t\t\t\t\t\tsetSelectedServer(null);\n\t\t\t\t\t\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tConnect to external MCP URL\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{query.trim()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{servers.length === 0 && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{isLoading ? \"Loading...\" : \"No servers found.\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{servers.map((server) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.description || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\n\n\t\t\t{selectedServer && token && namespace && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{selectedExternalUrl && token && (\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t);\n};\n\nexport const ServerSearch = (props: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => (\n\t\n\t\t\n\t\n);\n", "type": "registry:component", "target": "components/smithery/server-search.tsx" }, { "path": "registry/new-york/smithery/connections.tsx", - "content": "\"use client\";\n\nimport { createMCPClient } from \"@ai-sdk/mcp\";\nimport Smithery from \"@smithery/api\";\nimport { createConnection } from \"@smithery/api/mcp\";\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport type { ToolExecutionOptions } from \"ai\";\nimport { Plus, RefreshCw, Trash2, X } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Toggle } from \"@/components/ui/toggle\";\nimport { cn } from \"@/lib/utils\";\nimport {\n\tConnectionConfigContext,\n\tuseConnectionConfig,\n} from \"@/registry/new-york/smithery/connection-context\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\nimport { ServerSearch } from \"@/registry/new-york/smithery/server-search\";\nimport { ToolsPanel } from \"@/registry/new-york/smithery/tools-panel\";\n\n// Re-export useConnectionConfig for backward compatibility\nexport { useConnectionConfig };\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\nconst ConnectionCardInner = ({\n\tconnection,\n\ttoken,\n\tnamespace,\n\tclassName,\n\t...rest\n}: {\n\tconnection: Connection;\n\ttoken: string;\n\tnamespace: string;\n\tclassName?: string;\n} & React.HTMLAttributes) => {\n\tconst queryClient = useQueryClient();\n\tconst deleteMutation = useMutation({\n\t\tmutationFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tawait client.beta.connect.connections.delete(connection.connectionId, {\n\t\t\t\tnamespace: namespace,\n\t\t\t});\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t});\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{connection.name.charAt(0)}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.serverInfo?.title ??\n\t\t\t\t\t\t\tconnection.serverInfo?.name ??\n\t\t\t\t\t\t\tconnection.name}\n\t\t\t\t\t\t{connection.connectionId && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{\"•\".repeat(Math.min(connection.connectionId.length - 10, 4))}\n\t\t\t\t\t\t\t\t{connection.connectionId.slice(-10)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.mcpUrl}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.metadata && JSON.stringify(connection.metadata)}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t\t deleteMutation.mutate()}\n\t\t\t\t\tdisabled={deleteMutation.isPending}\n\t\t\t\t>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionCard = (\n\tprops: {\n\t\tconnection: Connection;\n\t\ttoken: string;\n\t\tnamespace: string;\n\t\tclassName?: string;\n\t} & React.HTMLAttributes,\n) => (\n\t\n\t\t\n\t\n);\n\nconst ConnectionsListInner = ({\n\ttoken,\n\tnamespace,\n\tdefaultActiveConnectionId,\n\tonActiveConnectionIdChange,\n\tdefaultShowSearchServers = true,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tdefaultActiveConnectionId || null,\n\t);\n\tconst [showSearchServers, setShowSearchServers] = useState(\n\t\tdefaultShowSearchServers || false,\n\t);\n\tconst { data, isLoading, error, refetch, isFetching } = useQuery({\n\t\tqueryKey: [\"connections\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst { connections } =\n\t\t\t\tawait client.beta.connect.connections.list(namespaceToUse);\n\t\t\treturn { connections, namespace: namespaceToUse };\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tuseEffect(() => {\n\t\tif (data?.connections && !defaultActiveConnectionId) {\n\t\t\tsetActiveConnectionId(data?.connections[0]?.connectionId || null);\n\t\t}\n\t}, [data?.connections, defaultActiveConnectionId]);\n\n\tuseEffect(() => {\n\t\tif (activeConnectionId) {\n\t\t\tonActiveConnectionIdChange(activeConnectionId);\n\t\t}\n\t}, [activeConnectionId, onActiveConnectionIdChange]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

Connections

\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\t{showSearchServers ? (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t\n\t\t\t\t\t refetch()}\n\t\t\t\t\t\tdisabled={isFetching}\n\t\t\t\t\t\ttitle=\"Refresh connections\"\n\t\t\t\t\t>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t{showSearchServers && (\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{isLoading &&

Loading...

}\n\t\t\t\t{error && (\n\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t{data.connections.length === 0 && (\n\t\t\t\t\t\t\t

No connections found

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{data.connections.map((connection: Connection) => (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t setActiveConnectionId(connection.connectionId)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t))}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionsList = (props: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => (\n\t\n\t\t\n\t\n);\n\nconst ActiveConnection = ({\n\ttoken,\n\tnamespace,\n\tconnectionId,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tconnectionId: string;\n}) => {\n\tconst [countdown, setCountdown] = useState(null);\n\tconst [hasRedirected, setHasRedirected] = useState(false);\n\n\tconst { data, isLoading, error } = useQuery({\n\t\tqueryKey: [\"connection\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst data = await client.beta.connect.connections.get(connectionId, {\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\treturn { namespace: namespaceToUse, ...data };\n\t\t},\n\t\t// Poll every 2 seconds when auth_required, stop when connected or error\n\t\trefetchInterval: (query) => {\n\t\t\tconst state = query.state.data?.status?.state;\n\t\t\tif (state === \"auth_required\") {\n\t\t\t\treturn 2000;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t});\n\n\t// Start countdown when auth_required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tcountdown === null &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetCountdown(5);\n\t\t}\n\t\t// Reset state when connectionId changes or status is no longer auth_required\n\t\tif (data?.status?.state !== \"auth_required\") {\n\t\t\tsetCountdown(null);\n\t\t\tsetHasRedirected(false);\n\t\t}\n\t}, [data?.status?.state, countdown, hasRedirected]);\n\n\t// Countdown timer\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when countdown reaches 0\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tdata?.status?.authorizationUrl &&\n\t\t\tcountdown === 0 &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetHasRedirected(true);\n\t\t\twindow.open(data.status.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [data?.status, countdown, hasRedirected]);\n\n\tconst clientQuery = useQuery({\n\t\tqueryKey: [\"mcp-client\", token, connectionId, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst namespaceToUse =\n\t\t\t\tnamespace || (await getDefaultNamespace(getSmitheryClient(token)));\n\t\t\tconst { transport } = await createConnection({\n\t\t\t\tclient: getSmitheryClient(token),\n\t\t\t\tconnectionId: connectionId,\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\tconst mcpClient = await createMCPClient({ transport });\n\t\t\treturn mcpClient;\n\t\t},\n\t\tenabled: !!connectionId && data?.status?.state === \"connected\",\n\t});\n\n\tconst toolsQuery = useQuery({\n\t\tqueryKey: [\"tools\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tif (!clientQuery.data) {\n\t\t\t\tthrow new Error(\"Client not available\");\n\t\t\t}\n\t\t\tconst client = clientQuery.data;\n\t\t\treturn await client.tools();\n\t\t},\n\t\tenabled: !!clientQuery.data && data?.status?.state === \"connected\",\n\t});\n\tconst handleExecute = async (\n\t\ttoolName: string,\n\t\tparams: Record,\n\t) => {\n\t\tif (!toolsQuery.data) {\n\t\t\tthrow new Error(\"Tools not available\");\n\t\t}\n\t\tconst tool = toolsQuery.data[toolName];\n\t\tif (!tool) {\n\t\t\tthrow new Error(`Tool ${toolName} not found`);\n\t\t}\n\t\t// The execute method from AI SDK tools expects (params, options)\n\t\t// We're executing tools directly, so provide minimal required options\n\t\tconst options: ToolExecutionOptions = {\n\t\t\ttoolCallId: `manual-${Date.now()}`,\n\t\t\tmessages: [],\n\t\t};\n\t\treturn await tool.execute(params, options);\n\t};\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading connection...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{data.iconUrl && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{(\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.title ??\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.name ??\n\t\t\t\t\t\t\t\t\t\t\tdata.name\n\t\t\t\t\t\t\t\t\t\t)?.charAt(0)}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\t{data.serverInfo?.title ?? data.serverInfo?.name ?? data.name}\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.websiteUrl && (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\tView website\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.serverInfo?.description && (\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.description}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\tConnection created:{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t

·

\n\t\t\t\t\t\t\t

Connection ID: {data.connectionId}

\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.metadata && (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tMetadata\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t{JSON.stringify(data.metadata, null, 2)}\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data?.status && data.status.state === \"auth_required\" && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Auth required

\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t\t\t\t: \"You should be automatically redirected to authenticate. If not, click the link below:\"}\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tOpen authentication window\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading tools...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\tError: {toolsQuery.error.message}\n\t\t\t\t\t\t

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.data && data && (\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nconst ConnectionsInner = ({\n\ttoken,\n\tnamespace,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tnull,\n\t);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t{activeConnectionId && (\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const Connections = (props: { token: string; namespace?: string }) => (\n\t\n\t\t\n\t\n);\n", + "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { ActiveConnection } from \"@/registry/new-york/smithery/active-connection\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ConnectionsList } from \"@/registry/new-york/smithery/connections-list\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\n\n// Re-export useConnectionConfig for backward compatibility\nexport { useConnectionConfig };\n\nconst ConnectionsInner = ({\n\ttoken,\n\tnamespace,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tnull,\n\t);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t{activeConnectionId && (\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const Connections = (props: { token: string; namespace?: string }) => (\n\t\n\t\t\n\t\n);\n", "type": "registry:component", "target": "components/smithery/connections.tsx" } diff --git a/public/r/smithery-server-search.json b/public/r/smithery-server-search.json index b825e42..9f35def 100644 --- a/public/r/smithery-server-search.json +++ b/public/r/smithery-server-search.json @@ -29,7 +29,7 @@ }, { "path": "registry/new-york/smithery/server-search.tsx", - "content": "\"use client\";\n\nimport Smithery, { AuthenticationError } from \"@smithery/api\";\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport type { ServerListResponse } from \"@smithery/api/resources/servers/servers.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n\tAlertTriangle,\n\tArrowRight,\n\tCheckCircle,\n\tLink,\n\tLoader2,\n\tLock,\n} from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tCombobox,\n\tComboboxContent,\n\tComboboxEmpty,\n\tComboboxInput,\n\tComboboxItem,\n\tComboboxList,\n} from \"@/components/ui/combobox\";\nimport {\n\tItem,\n\tItemContent,\n\tItemDescription,\n\tItemMedia,\n\tItemTitle,\n} from \"@/components/ui/item\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\n\ninterface AuthRequiredBannerProps {\n\tserverName: string;\n\tauthorizationUrl?: string;\n\tcountdown: number | null;\n}\n\nconst AuthRequiredBanner = ({\n\tserverName,\n\tauthorizationUrl,\n\tcountdown,\n}: AuthRequiredBannerProps) => (\n\t
\n\t\t
\n\t\t\t

\n\t\t\t\t{\" \"}\n\t\t\t\tAuthorization required\n\t\t\t

\n\t\t\t

\n\t\t\t\tThis server requires you to authorize access. You should be\n\t\t\t\tautomatically redirected to complete authentication.{\" \"}\n\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t: \"If not, click the link below.\"}\n\t\t\t

\n\t\t\t{authorizationUrl && (\n\t\t\t\t\n\t\t\t\t\tSign in to {serverName}{\" \"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t
\n);\n\ninterface ExistingConnectionWarningBannerProps {\n\tserverName: string;\n\tonUseExisting: () => void;\n\tonCreateNew: () => void;\n\tisConnecting: boolean;\n}\n\nconst ExistingConnectionWarningBanner = ({\n\tserverName,\n\tonUseExisting,\n\tonCreateNew,\n\tisConnecting,\n}: ExistingConnectionWarningBannerProps) => (\n\t
\n\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t\t

\n\t\t\t\t\tConnection already exists\n\t\t\t\t

\n\t\t\t\t

\n\t\t\t\t\tA connection to {serverName} already exists. Would you like to use the\n\t\t\t\t\texisting connection or create a new one?\n\t\t\t\t

\n\t\t\t
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Use existing\"\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Create new\"\n\t\t\t\t)}\n\t\t\t\n\t\t
\n\t
\n);\n\ninterface ConnectionButtonProps {\n\tconnectionStatus?:\n\t\t| \"connected\"\n\t\t| \"auth_required\"\n\t\t| \"existing_connection_warning\"\n\t\t| \"error\";\n\tisConnecting: boolean;\n\tonConnect: () => void;\n}\n\nconst ConnectionButton = ({\n\tconnectionStatus,\n\tisConnecting,\n\tonConnect,\n}: ConnectionButtonProps) => {\n\tswitch (connectionStatus) {\n\t\tcase \"connected\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"auth_required\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"existing_connection_warning\":\n\t\t\t// Buttons are rendered separately in ExistingConnectionWarningBanner\n\t\t\treturn null;\n\t\tdefault:\n\t\t\treturn (\n\t\t\t\t\n\t\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tConnecting...\n\t\t\t\t\t\t\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tConnect \n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t);\n\t}\n};\n\ntype OnExistingConnectionMode = \"warn\" | \"error\" | \"use\" | \"create-new\";\n\ninterface ServerDisplayProps {\n\tserver: ServerListResponse;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ServerDisplay = ({\n\tserver,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ServerDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: activeNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (namespace) {\n\t\t\t\treturn namespace;\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst serverUrl =\n\t\tserver.qualifiedName.startsWith(\"http://\") ||\n\t\tserver.qualifiedName.startsWith(\"https://\")\n\t\t\t? server.qualifiedName\n\t\t\t: `https://server.smithery.ai/${server.qualifiedName}/mcp`;\n\tconst serverName = server.displayName || server.qualifiedName;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\tserverUrl,\n\t\t\t\tserverName,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\n\t\t\t\t\"error connecting to server\",\n\t\t\t\t`server: ${server.qualifiedName}, namespace: ${activeNamespace}`,\n\t\t\t\terror,\n\t\t\t);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t{server.verified && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.qualifiedName}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t{server.description &&

{server.description}

}\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t {\n\t\t\t\t\t\twindow.open(server.homepage, \"_blank\");\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tView Details\n\t\t\t\t\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\ntype ConnectionStatus =\n\t| { status: \"connected\"; connection: Connection }\n\t| { status: \"auth_required\"; connectionId: string; authorizationUrl?: string }\n\t| {\n\t\t\tstatus: \"existing_connection_warning\";\n\t\t\texistingConnectionId: string;\n\t\t\tname: string;\n\t }\n\t| { status: \"error\"; error: unknown };\n\nasync function checkConnectionStatus(\n\tclient: Smithery,\n\tconnectionId: string,\n\tnamespace: string,\n): Promise {\n\ttry {\n\t\tconst jsonRpcResponse = await client.beta.connect.mcp.call(connectionId, {\n\t\t\tnamespace,\n\t\t\tjsonrpc: \"2.0\",\n\t\t\tmethod: \"tools/list\",\n\t\t});\n\t\tconsole.log(\"jsonRpcResponse\", jsonRpcResponse);\n\n\t\tconst connection = await client.beta.connect.connections.get(connectionId, {\n\t\t\tnamespace: namespace,\n\t\t});\n\n\t\treturn {\n\t\t\tstatus: \"connected\",\n\t\t\tconnection,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof AuthenticationError) {\n\t\t\tconst errorData = error.error as unknown as {\n\t\t\t\terror?: { data?: { authorizationUrl?: string } };\n\t\t\t\tdata?: { authorizationUrl?: string };\n\t\t\t};\n\t\t\tconst authorizationUrl =\n\t\t\t\terrorData?.error?.data?.authorizationUrl ||\n\t\t\t\terrorData?.data?.authorizationUrl;\n\t\t\treturn {\n\t\t\t\tstatus: \"auth_required\",\n\t\t\t\tconnectionId,\n\t\t\t\tauthorizationUrl,\n\t\t\t};\n\t\t}\n\t\tconsole.error(\n\t\t\t\"error connecting to server\",\n\t\t\t`connectionId: ${connectionId}, namespace: ${namespace}`,\n\t\t\terror,\n\t\t);\n\t\treturn {\n\t\t\tstatus: \"error\",\n\t\t\terror,\n\t\t};\n\t}\n}\n\nasync function createConnection(\n\tclient: Smithery,\n\tnamespace: string,\n\tmcpUrl: string,\n\tname: string,\n\tonExistingConnection: OnExistingConnectionMode,\n): Promise {\n\t// Check for existing connection by name\n\tconst existingList = await client.beta.connect.connections.list(namespace, {\n\t\tname,\n\t});\n\tconst existing = existingList.connections[0];\n\n\tif (existing) {\n\t\tswitch (onExistingConnection) {\n\t\t\tcase \"error\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: new Error(`Connection \"${name}\" already exists`),\n\t\t\t\t};\n\t\t\tcase \"warn\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"existing_connection_warning\",\n\t\t\t\t\texistingConnectionId: existing.connectionId,\n\t\t\t\t\tname,\n\t\t\t\t};\n\t\t\tcase \"use\":\n\t\t\t\treturn checkConnectionStatus(client, existing.connectionId, namespace);\n\t\t\tcase \"create-new\":\n\t\t\t\t// Continue to create new connection\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Create new connection (API auto-generates unique ID)\n\tconsole.log(\"creating connection\", name);\n\tconst connection = await client.beta.connect.connections.create(namespace, {\n\t\tmcpUrl,\n\t\tname,\n\t});\n\tconsole.log(\"connection\", connection);\n\n\tif (connection.status?.state === \"auth_required\") {\n\t\treturn {\n\t\t\tstatus: \"auth_required\",\n\t\t\tconnectionId: connection.connectionId,\n\t\t\tauthorizationUrl: connection.status?.authorizationUrl,\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: \"connected\",\n\t\tconnection,\n\t};\n}\n\n// Helper function to detect if input is a URL\nconst isValidUrl = (str: string): boolean => {\n\tif (!str.trim()) return false;\n\ttry {\n\t\tconst url = new URL(str.trim());\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n};\n\ninterface ExternalURLDisplayProps {\n\turl: string;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ExternalURLDisplay = ({\n\turl,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ExternalURLDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: defaultNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst activeNamespace = namespace || defaultNamespace;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\turl,\n\t\t\t\turl,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\"error connecting to external URL\", error);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when auth is required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tconnectionData?.status === \"auth_required\" &&\n\t\t\tconnectionData.authorizationUrl &&\n\t\t\tcountdown === 0\n\t\t) {\n\t\t\twindow.open(connectionData.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [connectionData, countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

External MCP Server

\n\t\t\t\t\t

{url}

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nconst ServerSearchInner = ({\n\ttoken,\n\tnamespace,\n\tonExistingConnection = \"warn\",\n}: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => {\n\tconst [query, setQuery] = useState(\"\");\n\tconst [selectedServer, setSelectedServer] =\n\t\tuseState(null);\n\tconst [selectedExternalUrl, setSelectedExternalUrl] = useState(\n\t\tnull,\n\t);\n\tconst debouncedQuery = useDebounce(query, 300);\n\tconst isUrl = isValidUrl(query);\n\n\tconst { data, isLoading } = useQuery({\n\t\tqueryKey: [\"servers\", token, debouncedQuery],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconsole.log(\"searching\", debouncedQuery);\n\t\t\tconst servers = debouncedQuery\n\t\t\t\t? await client.servers.list({ q: debouncedQuery, pageSize: 3 })\n\t\t\t\t: await client.servers.list();\n\t\t\tconsole.log(`servers for ${debouncedQuery}`, servers);\n\t\t\treturn servers;\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst servers = data?.servers ?? [];\n\n\tconst handleKeyDown = (e: React.KeyboardEvent) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\te.preventDefault();\n\t\t\tif (isUrl) {\n\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\tsetSelectedServer(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t} else if (servers.length > 0) {\n\t\t\t\tsetSelectedServer(servers[0]);\n\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\tvalue={selectedServer}\n\t\t\t\tonValueChange={(server) => {\n\t\t\t\t\tsetSelectedServer(server);\n\t\t\t\t\t// Only clear external URL when a server is actually selected\n\t\t\t\t\t// (not when combobox fires null from closing without selection)\n\t\t\t\t\tif (server) {\n\t\t\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tonInputValueChange={(value) => setQuery(value)}\n\t\t\t\titemToStringLabel={(server) =>\n\t\t\t\t\tserver.displayName || server.qualifiedName\n\t\t\t\t}\n\t\t\t\tdefaultOpen={true}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{isUrl ? (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\t\t\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\t\t\t\t\t\tsetSelectedServer(null);\n\t\t\t\t\t\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tConnect to external MCP URL\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{query.trim()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{servers.length === 0 && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{isLoading ? \"Loading...\" : \"No servers found.\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{servers.map((server) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.description || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\n\n\t\t\t{selectedServer && token && namespace && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{selectedExternalUrl && token && (\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t);\n};\n\nexport const ServerSearch = (props: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => (\n\t\n\t\t\n\t\n);\n", + "content": "\"use client\";\n\nimport Smithery, { AuthenticationError } from \"@smithery/api\";\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport type { ServerListResponse } from \"@smithery/api/resources/servers/servers.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n\tAlertTriangle,\n\tArrowRight,\n\tCheckCircle,\n\tLink,\n\tLoader2,\n\tLock,\n} from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tCombobox,\n\tComboboxContent,\n\tComboboxEmpty,\n\tComboboxInput,\n\tComboboxItem,\n\tComboboxList,\n} from \"@/components/ui/combobox\";\nimport {\n\tItem,\n\tItemContent,\n\tItemDescription,\n\tItemMedia,\n\tItemTitle,\n} from \"@/components/ui/item\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\n\ninterface AuthRequiredBannerProps {\n\tserverName: string;\n\tauthorizationUrl?: string;\n\tcountdown: number | null;\n}\n\nconst AuthRequiredBanner = ({\n\tserverName,\n\tauthorizationUrl,\n\tcountdown,\n}: AuthRequiredBannerProps) => (\n\t
\n\t\t
\n\t\t\t

\n\t\t\t\t{\" \"}\n\t\t\t\tAuthorization required\n\t\t\t

\n\t\t\t

\n\t\t\t\tThis server requires you to authorize access. You should be\n\t\t\t\tautomatically redirected to complete authentication.{\" \"}\n\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t: \"If not, click the link below.\"}\n\t\t\t

\n\t\t\t{authorizationUrl && (\n\t\t\t\t\n\t\t\t\t\tSign in to {serverName}{\" \"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t
\n);\n\ninterface ExistingConnectionWarningBannerProps {\n\tserverName: string;\n\tonUseExisting: () => void;\n\tonCreateNew: () => void;\n\tisConnecting: boolean;\n}\n\nconst ExistingConnectionWarningBanner = ({\n\tserverName,\n\tonUseExisting,\n\tonCreateNew,\n\tisConnecting,\n}: ExistingConnectionWarningBannerProps) => (\n\t
\n\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t\t

\n\t\t\t\t\tConnection already exists\n\t\t\t\t

\n\t\t\t\t

\n\t\t\t\t\tA connection to {serverName} already exists. Would you like to use the\n\t\t\t\t\texisting connection or create a new one?\n\t\t\t\t

\n\t\t\t
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Use existing\"\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Create new\"\n\t\t\t\t)}\n\t\t\t\n\t\t
\n\t
\n);\n\ninterface ConnectionButtonProps {\n\tconnectionStatus?:\n\t\t| \"connected\"\n\t\t| \"auth_required\"\n\t\t| \"existing_connection_warning\"\n\t\t| \"error\";\n\tisConnecting: boolean;\n\tonConnect: () => void;\n}\n\nconst ConnectionButton = ({\n\tconnectionStatus,\n\tisConnecting,\n\tonConnect,\n}: ConnectionButtonProps) => {\n\tswitch (connectionStatus) {\n\t\tcase \"connected\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"auth_required\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"existing_connection_warning\":\n\t\t\t// Buttons are rendered separately in ExistingConnectionWarningBanner\n\t\t\treturn null;\n\t\tdefault:\n\t\t\treturn (\n\t\t\t\t\n\t\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tConnecting...\n\t\t\t\t\t\t\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tConnect \n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t);\n\t}\n};\n\ntype OnExistingConnectionMode = \"warn\" | \"error\" | \"use\" | \"create-new\";\n\ninterface ServerDisplayProps {\n\tserver: ServerListResponse;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ServerDisplay = ({\n\tserver,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ServerDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: activeNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (namespace) {\n\t\t\t\treturn namespace;\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst serverUrl =\n\t\tserver.qualifiedName.startsWith(\"http://\") ||\n\t\tserver.qualifiedName.startsWith(\"https://\")\n\t\t\t? server.qualifiedName\n\t\t\t: `https://server.smithery.ai/${server.qualifiedName}/mcp`;\n\tconst serverName = server.displayName || server.qualifiedName;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\tserverUrl,\n\t\t\t\tserverName,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\n\t\t\t\t\"error connecting to server\",\n\t\t\t\t`server: ${server.qualifiedName}, namespace: ${activeNamespace}`,\n\t\t\t\terror,\n\t\t\t);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t{server.verified && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.qualifiedName}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t{server.description &&

{server.description}

}\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t {\n\t\t\t\t\t\twindow.open(server.homepage, \"_blank\");\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tView Details\n\t\t\t\t\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\ntype ConnectionStatus =\n\t| { status: \"connected\"; connection: Connection }\n\t| { status: \"auth_required\"; connectionId: string; authorizationUrl?: string }\n\t| {\n\t\t\tstatus: \"existing_connection_warning\";\n\t\t\texistingConnectionId: string;\n\t\t\tname: string;\n\t }\n\t| { status: \"error\"; error: unknown };\n\nasync function checkConnectionStatus(\n\tclient: Smithery,\n\tconnectionId: string,\n\tnamespace: string,\n): Promise {\n\ttry {\n\t\tconst jsonRpcResponse = await client.beta.connect.rpc.call(connectionId, {\n\t\t\tnamespace,\n\t\t\tjsonrpc: \"2.0\",\n\t\t\tmethod: \"tools/list\",\n\t\t});\n\t\tconsole.log(\"jsonRpcResponse\", jsonRpcResponse);\n\n\t\tconst connection = await client.beta.connect.connections.get(connectionId, {\n\t\t\tnamespace: namespace,\n\t\t});\n\n\t\treturn {\n\t\t\tstatus: \"connected\",\n\t\t\tconnection,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof AuthenticationError) {\n\t\t\tconst errorData = error.error as unknown as {\n\t\t\t\terror?: { data?: { authorizationUrl?: string } };\n\t\t\t\tdata?: { authorizationUrl?: string };\n\t\t\t};\n\t\t\tconst authorizationUrl =\n\t\t\t\terrorData?.error?.data?.authorizationUrl ||\n\t\t\t\terrorData?.data?.authorizationUrl;\n\t\t\treturn {\n\t\t\t\tstatus: \"auth_required\",\n\t\t\t\tconnectionId,\n\t\t\t\tauthorizationUrl,\n\t\t\t};\n\t\t}\n\t\tconsole.error(\n\t\t\t\"error connecting to server\",\n\t\t\t`connectionId: ${connectionId}, namespace: ${namespace}`,\n\t\t\terror,\n\t\t);\n\t\treturn {\n\t\t\tstatus: \"error\",\n\t\t\terror,\n\t\t};\n\t}\n}\n\nasync function createConnection(\n\tclient: Smithery,\n\tnamespace: string,\n\tmcpUrl: string,\n\tname: string,\n\tonExistingConnection: OnExistingConnectionMode,\n): Promise {\n\t// Check for existing connection by name\n\tconst existingList = await client.beta.connect.connections.list(namespace, {\n\t\tname,\n\t});\n\tconst existing = existingList.connections[0];\n\n\tif (existing) {\n\t\tswitch (onExistingConnection) {\n\t\t\tcase \"error\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: new Error(`Connection \"${name}\" already exists`),\n\t\t\t\t};\n\t\t\tcase \"warn\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"existing_connection_warning\",\n\t\t\t\t\texistingConnectionId: existing.connectionId,\n\t\t\t\t\tname,\n\t\t\t\t};\n\t\t\tcase \"use\":\n\t\t\t\treturn checkConnectionStatus(client, existing.connectionId, namespace);\n\t\t\tcase \"create-new\":\n\t\t\t\t// Continue to create new connection\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Create new connection (API auto-generates unique ID)\n\tconsole.log(\"creating connection\", name);\n\tconst connection = await client.beta.connect.connections.create(namespace, {\n\t\tmcpUrl,\n\t\tname,\n\t});\n\tconsole.log(\"connection\", connection);\n\n\tif (connection.status?.state === \"auth_required\") {\n\t\treturn {\n\t\t\tstatus: \"auth_required\",\n\t\t\tconnectionId: connection.connectionId,\n\t\t\tauthorizationUrl: connection.status?.authorizationUrl,\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: \"connected\",\n\t\tconnection,\n\t};\n}\n\n// Helper function to detect if input is a URL\nconst isValidUrl = (str: string): boolean => {\n\tif (!str.trim()) return false;\n\ttry {\n\t\tconst url = new URL(str.trim());\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n};\n\ninterface ExternalURLDisplayProps {\n\turl: string;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ExternalURLDisplay = ({\n\turl,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ExternalURLDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: defaultNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst activeNamespace = namespace || defaultNamespace;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\turl,\n\t\t\t\turl,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\"error connecting to external URL\", error);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when auth is required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tconnectionData?.status === \"auth_required\" &&\n\t\t\tconnectionData.authorizationUrl &&\n\t\t\tcountdown === 0\n\t\t) {\n\t\t\twindow.open(connectionData.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [connectionData, countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

External MCP Server

\n\t\t\t\t\t

{url}

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nconst ServerSearchInner = ({\n\ttoken,\n\tnamespace,\n\tonExistingConnection = \"warn\",\n}: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => {\n\tconst [query, setQuery] = useState(\"\");\n\tconst [selectedServer, setSelectedServer] =\n\t\tuseState(null);\n\tconst [selectedExternalUrl, setSelectedExternalUrl] = useState(\n\t\tnull,\n\t);\n\tconst debouncedQuery = useDebounce(query, 300);\n\tconst isUrl = isValidUrl(query);\n\n\tconst { data, isLoading } = useQuery({\n\t\tqueryKey: [\"servers\", token, debouncedQuery],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconsole.log(\"searching\", debouncedQuery);\n\t\t\tconst servers = debouncedQuery\n\t\t\t\t? await client.servers.list({ q: debouncedQuery, pageSize: 3 })\n\t\t\t\t: await client.servers.list();\n\t\t\tconsole.log(`servers for ${debouncedQuery}`, servers);\n\t\t\treturn servers;\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst servers = data?.servers ?? [];\n\n\tconst handleKeyDown = (e: React.KeyboardEvent) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\te.preventDefault();\n\t\t\tif (isUrl) {\n\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\tsetSelectedServer(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t} else if (servers.length > 0) {\n\t\t\t\tsetSelectedServer(servers[0]);\n\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\tvalue={selectedServer}\n\t\t\t\tonValueChange={(server) => {\n\t\t\t\t\tsetSelectedServer(server);\n\t\t\t\t\t// Only clear external URL when a server is actually selected\n\t\t\t\t\t// (not when combobox fires null from closing without selection)\n\t\t\t\t\tif (server) {\n\t\t\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tonInputValueChange={(value) => setQuery(value)}\n\t\t\t\titemToStringLabel={(server) =>\n\t\t\t\t\tserver.displayName || server.qualifiedName\n\t\t\t\t}\n\t\t\t\tdefaultOpen={true}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{isUrl ? (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\t\t\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\t\t\t\t\t\tsetSelectedServer(null);\n\t\t\t\t\t\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tConnect to external MCP URL\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{query.trim()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{servers.length === 0 && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{isLoading ? \"Loading...\" : \"No servers found.\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{servers.map((server) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.description || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\n\n\t\t\t{selectedServer && token && namespace && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{selectedExternalUrl && token && (\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t);\n};\n\nexport const ServerSearch = (props: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => (\n\t\n\t\t\n\t\n);\n", "type": "registry:component", "target": "components/smithery/server-search.tsx" } diff --git a/public/r/smithery-tool-card.json b/public/r/smithery-tool-card.json index 270ae49..12cb26e 100644 --- a/public/r/smithery-tool-card.json +++ b/public/r/smithery-tool-card.json @@ -37,7 +37,7 @@ }, { "path": "registry/new-york/smithery/tool-detail-dialog.tsx", - "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", + "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst _namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", "type": "registry:component", "target": "components/smithery/tool-detail-dialog.tsx" }, diff --git a/public/r/smithery-tool-detail-dialog.json b/public/r/smithery-tool-detail-dialog.json index 2683462..8a56154 100644 --- a/public/r/smithery-tool-detail-dialog.json +++ b/public/r/smithery-tool-detail-dialog.json @@ -36,7 +36,7 @@ }, { "path": "registry/new-york/smithery/tool-detail-dialog.tsx", - "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", + "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst _namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", "type": "registry:component", "target": "components/smithery/tool-detail-dialog.tsx" } diff --git a/public/r/smithery-tools-panel.json b/public/r/smithery-tools-panel.json index cf5fffc..ff1811c 100644 --- a/public/r/smithery-tools-panel.json +++ b/public/r/smithery-tools-panel.json @@ -37,7 +37,7 @@ }, { "path": "registry/new-york/smithery/tool-detail-dialog.tsx", - "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", + "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst _namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", "type": "registry:component", "target": "components/smithery/tool-detail-dialog.tsx" }, diff --git a/public/r/smithery-utils.json b/public/r/smithery-utils.json new file mode 100644 index 0000000..b7c6283 --- /dev/null +++ b/public/r/smithery-utils.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://ui.shadcn.com/schema/registry-item.json", + "name": "smithery-utils", + "title": "Smithery Utils", + "description": "Utility functions for Smithery API client instantiation and namespace management.", + "dependencies": [ + "@smithery/api" + ], + "files": [ + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "content": "import Smithery from \"@smithery/api\";\n\nexport async function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nexport const getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + } + ], + "type": "registry:block" +} \ No newline at end of file diff --git a/public/r/smithery.json b/public/r/smithery.json index 2d2b897..b7393e0 100644 --- a/public/r/smithery.json +++ b/public/r/smithery.json @@ -40,6 +40,12 @@ "type": "registry:component", "target": "components/smithery/query-client-wrapper.tsx" }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "content": "import Smithery from \"@smithery/api\";\n\nexport async function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nexport const getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, { "path": "registry/new-york/smithery/connection-context.tsx", "content": "\"use client\";\n\nimport { createContext, useContext } from \"react\";\n\n// Context for connection config - consumed by ToolDetailDialog for code generation\nexport interface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nexport const ConnectionConfigContext = createContext(\n\tnull,\n);\n\nexport function useConnectionConfig() {\n\treturn useContext(ConnectionConfigContext);\n}\n", @@ -60,7 +66,7 @@ }, { "path": "registry/new-york/smithery/tool-detail-dialog.tsx", - "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", + "content": "\"use client\";\n\nimport type { Tool } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { useForm } from \"react-hook-form\";\nimport { estimateTokenCount } from \"tokenx\";\nimport {\n\tCodeBlock,\n\tCodeBlockCopyButton,\n} from \"@/components/smithery/code-block\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tDialog,\n\tDialogContent,\n\tDialogDescription,\n\tDialogHeader,\n\tDialogTitle,\n} from \"@/components/ui/dialog\";\nimport {\n\tField,\n\tFieldDescription,\n\tFieldError,\n\tFieldGroup,\n\tFieldLabel,\n\tFieldLegend,\n\tFieldSeparator,\n\tFieldSet,\n} from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n\tSelect,\n\tSelectContent,\n\tSelectItem,\n\tSelectTrigger,\n\tSelectValue,\n} from \"@/components/ui/select\";\nimport { Switch } from \"@/components/ui/switch\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ToolOutputViewer } from \"@/registry/new-york/smithery/tool-output-viewer\";\n\ninterface JSONSchema {\n\ttype?: string;\n\tproperties?: Record;\n\trequired?: string[];\n\tadditionalProperties?: boolean;\n}\n\ninterface JSONSchemaProperty {\n\ttype?: string | string[];\n\tdescription?: string;\n\tenum?: string[];\n\tminimum?: number;\n\tmaximum?: number;\n\tdefault?: unknown;\n\titems?: JSONSchemaProperty;\n}\n\ninterface ToolDetailDialogProps {\n\topen: boolean;\n\tonOpenChange: (open: boolean) => void;\n\tname: string;\n\ttool: Tool;\n\tonExecute?: (params: Record) => Promise;\n}\n\nexport function ToolDetailDialog({\n\topen,\n\tonOpenChange,\n\tname,\n\ttool,\n\tonExecute,\n}: ToolDetailDialogProps) {\n\tconst connectionConfig = useConnectionConfig();\n\tconst [isExecuting, setIsExecuting] = useState(false);\n\tconst [result, setResult] = useState(null);\n\tconst [error, setError] = useState(null);\n\tconst [executedAt, setExecutedAt] = useState(null);\n\tconst [activeTab, setActiveTab] = useState(\"code\");\n\tconst [estimatedTokens, setEstimatedTokens] = useState(null);\n\tconst [latency, setLatency] = useState(null);\n\n\t// Extract schema\n\tconst inputSchema = tool.inputSchema;\n\tconst schema: JSONSchema =\n\t\ttypeof inputSchema === \"object\" &&\n\t\tinputSchema &&\n\t\t\"jsonSchema\" in inputSchema\n\t\t\t? (inputSchema as { jsonSchema: JSONSchema }).jsonSchema\n\t\t\t: (inputSchema as JSONSchema) || {};\n\n\tconst properties = schema.properties || {};\n\tconst requiredFields = schema.required || [];\n\tconst hasParameters = Object.keys(properties).length > 0;\n\n\tconst {\n\t\tregister,\n\t\thandleSubmit,\n\t\tformState: { errors },\n\t\tsetValue,\n\t\twatch,\n\t\treset,\n\t} = useForm>({\n\t\tdefaultValues: getDefaultValues(schema),\n\t});\n\n\tconst formValues = watch();\n\n\t// Reset form when dialog opens\n\tuseEffect(() => {\n\t\tif (open) {\n\t\t\treset(getDefaultValues(schema));\n\t\t\tsetResult(null);\n\t\t\tsetError(null);\n\t\t\tsetExecutedAt(null);\n\t\t\tsetActiveTab(\"code\");\n\t\t\tsetEstimatedTokens(null);\n\t\t\tsetLatency(null);\n\t\t}\n\t}, [open, reset, schema]);\n\n\t// Check if a value is empty and should be excluded\n\tconst isEmptyValue = (value: unknown): boolean => {\n\t\tif (value === \"\" || value === undefined || value === null) return true;\n\t\tif (typeof value === \"number\" && Number.isNaN(value)) return true;\n\t\tif (Array.isArray(value) && value.length === 0) return true;\n\t\tif (\n\t\t\ttypeof value === \"object\" &&\n\t\t\tvalue !== null &&\n\t\t\tObject.keys(value).length === 0\n\t\t)\n\t\t\treturn true;\n\t\treturn false;\n\t};\n\n\tconst handleExecuteSubmit = async (params: Record) => {\n\t\tif (!onExecute) return;\n\n\t\t// Switch to output tab when execution starts\n\t\tsetActiveTab(\"output\");\n\t\tsetIsExecuting(true);\n\t\tsetError(null);\n\t\tsetResult(null);\n\t\tsetEstimatedTokens(null);\n\t\tsetLatency(null);\n\n\t\tconst startTime = performance.now();\n\n\t\ttry {\n\t\t\t// Parse JSON strings for arrays and objects, filter out empty values\n\t\t\tconst processedParams: Record = {};\n\t\t\tfor (const [key, value] of Object.entries(params)) {\n\t\t\t\t// Try to parse JSON strings first\n\t\t\t\tlet parsedValue = value;\n\t\t\t\tif (\n\t\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t\t) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Skip empty values\n\t\t\t\tif (isEmptyValue(parsedValue)) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tprocessedParams[key] = parsedValue;\n\t\t\t}\n\n\t\t\tconst res = await onExecute(processedParams);\n\t\t\tconst endTime = performance.now();\n\t\t\tconst executionLatency = endTime - startTime;\n\n\t\t\t// Estimate token count of the result\n\t\t\tconst resultString = JSON.stringify(res);\n\t\t\tconst tokens = estimateTokenCount(resultString);\n\n\t\t\tsetResult(res);\n\t\t\tsetExecutedAt(new Date());\n\t\t\tsetLatency(executionLatency);\n\t\t\tsetEstimatedTokens(tokens);\n\t\t} catch (err) {\n\t\t\tsetError(err instanceof Error ? err.message : \"An error occurred\");\n\t\t} finally {\n\t\t\tsetIsExecuting(false);\n\t\t}\n\t};\n\n\t// Generate code preview\n\tconst generateCodePreview = () => {\n\t\tconst params: Record = {};\n\t\tfor (const [key, value] of Object.entries(formValues)) {\n\t\t\t// Try to parse JSON strings first\n\t\t\tlet parsedValue = value;\n\t\t\tif (\n\t\t\t\ttypeof value === \"string\" &&\n\t\t\t\t(value.startsWith(\"[\") || value.startsWith(\"{\"))\n\t\t\t) {\n\t\t\t\ttry {\n\t\t\t\t\tparsedValue = JSON.parse(value);\n\t\t\t\t} catch {\n\t\t\t\t\t// Keep original string if parsing fails\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!isEmptyValue(parsedValue)) {\n\t\t\t\tparams[key] = parsedValue;\n\t\t\t}\n\t\t}\n\n\t\treturn generateToolExecuteCode(name, params, connectionConfig);\n\t};\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{name}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t{tool.description && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{tool.description}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{tool.type && tool.type !== \"dynamic\" && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{tool.type}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t
\n\n\t\t\t\t
\n\t\t\t\t\t{/* Left Panel - Parameters */}\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{hasParameters ? (\n\t\t\t\t\t\t\t\t(() => {\n\t\t\t\t\t\t\t\t\tconst requiredEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalEntries = Object.entries(properties).filter(\n\t\t\t\t\t\t\t\t\t\t([fieldName]) => !requiredFields.includes(fieldName),\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\tconst renderFieldSet = (\n\t\t\t\t\t\t\t\t\t\tentries: Array<[string, JSONSchemaProperty]>,\n\t\t\t\t\t\t\t\t\t\tisRequired: boolean,\n\t\t\t\t\t\t\t\t\t\tlabel: string,\n\t\t\t\t\t\t\t\t\t) => {\n\t\t\t\t\t\t\t\t\t\tif (entries.length === 0) return null;\n\n\t\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t\t{label}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{entries.map(([fieldName, property]) => (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{fieldName}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{renderField(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tfieldName,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tproperty,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tregister,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tsetValue,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\twatch,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tisRequired,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description && (\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{property.description}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\t};\n\n\t\t\t\t\t\t\t\t\tconst requiredSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\trequiredEntries,\n\t\t\t\t\t\t\t\t\t\ttrue,\n\t\t\t\t\t\t\t\t\t\t\"Required\",\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t\tconst optionalSection = renderFieldSet(\n\t\t\t\t\t\t\t\t\t\toptionalEntries,\n\t\t\t\t\t\t\t\t\t\tfalse,\n\t\t\t\t\t\t\t\t\t\t\"Optional\",\n\t\t\t\t\t\t\t\t\t);\n\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection}\n\t\t\t\t\t\t\t\t\t\t\t{requiredSection && optionalSection && }\n\t\t\t\t\t\t\t\t\t\t\t{optionalSection}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})()\n\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tThis tool has no parameters.\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\n\t\t\t\t\t{/* Right Panel - Code & Output */}\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\tCode\n\t\t\t\t\t\t\t\t\tOutput\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t handleSubmit(handleExecuteSubmit)()}\n\t\t\t\t\t\t\t\t\tsize=\"sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t{isExecuting ? \"Executing...\" : \"Execute\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{executedAt && estimatedTokens !== null && latency !== null && (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{executedAt.toLocaleTimeString()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{estimatedTokens.toLocaleString()} tokens\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{latency.toLocaleString()}ms\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{isExecuting ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t\tExecuting...\n\t\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : error ? (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\t{error}\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t) : result ? (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t\tExecute the tool to see output\n\t\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t\n\t\t
\n\t);\n}\n\nfunction renderField(\n\tname: string,\n\tproperty: JSONSchemaProperty,\n\tregister: ReturnType[\"register\"],\n\tsetValue: ReturnType[\"setValue\"],\n\twatch: ReturnType[\"watch\"],\n\tisRequired: boolean,\n) {\n\tconst type = Array.isArray(property.type) ? property.type[0] : property.type;\n\n\t// Handle enums with Select\n\tif (property.enum && property.enum.length > 0) {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t setValue(name, value)}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{property.enum.map((option) => (\n\t\t\t\t\t\t\n\t\t\t\t\t\t\t{option}\n\t\t\t\t\t\t\n\t\t\t\t\t))}\n\t\t\t\t\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle boolean with Switch\n\tif (type === \"boolean\") {\n\t\tconst currentValue = watch(name);\n\t\treturn (\n\t\t\t
\n\t\t\t\t setValue(name, checked)}\n\t\t\t\t/>\n\t\t\t\t\n\t\t\t\t\t{currentValue ? \"Enabled\" : \"Disabled\"}\n\t\t\t\t\n\t\t\t
\n\t\t);\n\t}\n\n\t// Handle number with Input type number\n\tif (type === \"number\" || type === \"integer\") {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Handle arrays\n\tif (type === \"array\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn Array.isArray(parsed) || \"Must be a valid JSON array\";\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle objects\n\tif (type === \"object\") {\n\t\treturn (\n\t\t\t {\n\t\t\t\t\t\tif (!value) return true;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst parsed = JSON.parse(value as string);\n\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\ttypeof parsed === \"object\" || \"Must be a valid JSON object\"\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\treturn \"Must be valid JSON\";\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t})}\n\t\t\t/>\n\t\t);\n\t}\n\n\t// Handle string - use Textarea if description suggests long text\n\tconst useLongText =\n\t\tproperty.description?.toLowerCase().includes(\"description\") ||\n\t\tproperty.description?.toLowerCase().includes(\"long\") ||\n\t\tproperty.description?.toLowerCase().includes(\"paragraph\");\n\n\tif (useLongText) {\n\t\treturn (\n\t\t\t\n\t\t);\n\t}\n\n\t// Default to regular Input for strings\n\treturn (\n\t\t\n\t);\n}\n\ninterface ConnectionConfig {\n\tmcpUrl: string;\n\tapiKey: string;\n\tnamespace: string;\n\tconnectionId: string;\n}\n\nfunction generateToolExecuteCode(\n\ttoolName: string,\n\tparams: Record,\n\tconfig: ConnectionConfig | null,\n): string {\n\tconst mcpUrl = config?.mcpUrl || \"process.env.MCP_URL\";\n\tconst _namespace = config?.namespace\n\t\t? `\"${config.namespace}\"`\n\t\t: \"process.env.SMITHERY_NAMESPACE\";\n\tconst connectionId = config?.connectionId\n\t\t? `\"${config.connectionId}\"`\n\t\t: \"connectionId\";\n\n\tconst code = `import { Client } from '@modelcontextprotocol/sdk/client/index.js';\nimport Smithery from '@smithery/api';\nimport { createConnection } from '@smithery/api/mcp';\n\nconst mcpUrl = \"${mcpUrl}\";\nconst connectionId = ${connectionId};\nconst apiKey = process.env.SMITHERY_API_KEY;\n\nconst { transport } = await createConnection({\n client: new Smithery({ apiKey }),\n connectionId,\n mcpUrl,\n});\n\n// Initialize the Traditional MCP Client\nconst mcpClient = new Client({\n name: \"smithery-mcp-client\",\n version: \"1.0.0\",\n});\n\n// Connect explicitly\nawait mcpClient.connect(transport);\n\nconst result = await mcpClient.callTool({\n name: \"${toolName}\",\n arguments: ${JSON.stringify(params, null, 2)},\n});\nconsole.log(result);\n`;\n\n\treturn code.trim();\n}\n\nfunction getDefaultValues(schema: JSONSchema): Record {\n\tconst defaults: Record = {};\n\tconst properties = schema.properties || {};\n\n\tfor (const [name, property] of Object.entries(properties)) {\n\t\tif (property.default !== undefined) {\n\t\t\tdefaults[name] = property.default;\n\t\t} else {\n\t\t\tconst type = Array.isArray(property.type)\n\t\t\t\t? property.type[0]\n\t\t\t\t: property.type;\n\t\t\t// Set sensible defaults based on type\n\t\t\tif (type === \"boolean\") {\n\t\t\t\tdefaults[name] = false;\n\t\t\t} else if (type === \"number\" || type === \"integer\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"array\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else if (type === \"object\") {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t} else {\n\t\t\t\tdefaults[name] = \"\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn defaults;\n}\n", "type": "registry:component", "target": "components/smithery/tool-detail-dialog.tsx" }, @@ -76,15 +82,33 @@ "type": "registry:component", "target": "components/smithery/tools-panel.tsx" }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "content": "\"use client\";\n\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport { useMutation, useQueryClient } from \"@tanstack/react-query\";\nimport { Trash2 } from \"lucide-react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\nimport { getSmitheryClient } from \"@/registry/new-york/smithery/smithery-utils\";\n\nconst ConnectionCardInner = ({\n\tconnection,\n\ttoken,\n\tnamespace,\n\tclassName,\n\t...rest\n}: {\n\tconnection: Connection;\n\ttoken: string;\n\tnamespace: string;\n\tclassName?: string;\n} & React.HTMLAttributes) => {\n\tconst queryClient = useQueryClient();\n\tconst deleteMutation = useMutation({\n\t\tmutationFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tawait client.beta.connect.connections.delete(connection.connectionId, {\n\t\t\t\tnamespace: namespace,\n\t\t\t});\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t});\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{connection.name.charAt(0)}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.serverInfo?.title ??\n\t\t\t\t\t\t\tconnection.serverInfo?.name ??\n\t\t\t\t\t\t\tconnection.name}\n\t\t\t\t\t\t{connection.connectionId && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{\"•\".repeat(Math.min(connection.connectionId.length - 10, 4))}\n\t\t\t\t\t\t\t\t{connection.connectionId.slice(-10)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.mcpUrl}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.metadata && JSON.stringify(connection.metadata)}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t\t deleteMutation.mutate()}\n\t\t\t\t\tdisabled={deleteMutation.isPending}\n\t\t\t\t>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionCard = (\n\tprops: {\n\t\tconnection: Connection;\n\t\ttoken: string;\n\t\tnamespace: string;\n\t\tclassName?: string;\n\t} & React.HTMLAttributes,\n) => (\n\t\n\t\t\n\t\n);\n", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + }, + { + "path": "registry/new-york/smithery/connections-list.tsx", + "content": "\"use client\";\n\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport { Plus, RefreshCw, X } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Toggle } from \"@/components/ui/toggle\";\nimport { cn } from \"@/lib/utils\";\nimport { ConnectionCard } from \"@/registry/new-york/smithery/connection-card\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\nimport { ServerSearch } from \"@/registry/new-york/smithery/server-search\";\nimport {\n\tgetDefaultNamespace,\n\tgetSmitheryClient,\n} from \"@/registry/new-york/smithery/smithery-utils\";\n\nconst ConnectionsListInner = ({\n\ttoken,\n\tnamespace,\n\tdefaultActiveConnectionId,\n\tonActiveConnectionIdChange,\n\tdefaultShowSearchServers = true,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tdefaultActiveConnectionId || null,\n\t);\n\tconst [showSearchServers, setShowSearchServers] = useState(\n\t\tdefaultShowSearchServers || false,\n\t);\n\tconst { data, isLoading, error, refetch, isFetching } = useQuery({\n\t\tqueryKey: [\"connections\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst { connections } =\n\t\t\t\tawait client.beta.connect.connections.list(namespaceToUse);\n\t\t\treturn { connections, namespace: namespaceToUse };\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tuseEffect(() => {\n\t\tif (data?.connections && !defaultActiveConnectionId) {\n\t\t\tsetActiveConnectionId(data?.connections[0]?.connectionId || null);\n\t\t}\n\t}, [data?.connections, defaultActiveConnectionId]);\n\n\tuseEffect(() => {\n\t\tif (activeConnectionId) {\n\t\t\tonActiveConnectionIdChange(activeConnectionId);\n\t\t}\n\t}, [activeConnectionId, onActiveConnectionIdChange]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

Connections

\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\t{showSearchServers ? (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t\n\t\t\t\t\t refetch()}\n\t\t\t\t\t\tdisabled={isFetching}\n\t\t\t\t\t\ttitle=\"Refresh connections\"\n\t\t\t\t\t>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t{showSearchServers && (\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{isLoading &&

Loading...

}\n\t\t\t\t{error && (\n\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t{data.connections.length === 0 && (\n\t\t\t\t\t\t\t

No connections found

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{data.connections.map((connection: Connection) => (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t setActiveConnectionId(connection.connectionId)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t))}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionsList = (props: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => (\n\t\n\t\t\n\t\n);\n", + "type": "registry:component", + "target": "components/smithery/connections-list.tsx" + }, + { + "path": "registry/new-york/smithery/active-connection.tsx", + "content": "\"use client\";\n\nimport { createMCPClient } from \"@ai-sdk/mcp\";\nimport { SmitheryTransport } from \"@smithery/api/mcp\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport type { ToolExecutionOptions } from \"ai\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport {\n\tConnectionConfigContext,\n\tuseConnectionConfig,\n} from \"@/registry/new-york/smithery/connection-context\";\nimport {\n\tgetDefaultNamespace,\n\tgetSmitheryClient,\n} from \"@/registry/new-york/smithery/smithery-utils\";\nimport { ToolsPanel } from \"@/registry/new-york/smithery/tools-panel\";\n\n// Re-export useConnectionConfig for backward compatibility\nexport { useConnectionConfig };\n\nexport const ActiveConnection = ({\n\ttoken,\n\tnamespace,\n\tconnectionId,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tconnectionId: string;\n}) => {\n\tconst [countdown, setCountdown] = useState(null);\n\tconst [hasRedirected, setHasRedirected] = useState(false);\n\n\tconst { data, isLoading, error } = useQuery({\n\t\tqueryKey: [\"connection\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst data = await client.beta.connect.connections.get(connectionId, {\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\treturn { namespace: namespaceToUse, ...data };\n\t\t},\n\t\t// Poll every 2 seconds when auth_required, stop when connected or error\n\t\trefetchInterval: (query) => {\n\t\t\tconst state = query.state.data?.status?.state;\n\t\t\tif (state === \"auth_required\") {\n\t\t\t\treturn 2000;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t});\n\n\t// Start countdown when auth_required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tcountdown === null &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetCountdown(5);\n\t\t}\n\t\t// Reset state when connectionId changes or status is no longer auth_required\n\t\tif (data?.status?.state !== \"auth_required\") {\n\t\t\tsetCountdown(null);\n\t\t\tsetHasRedirected(false);\n\t\t}\n\t}, [data?.status?.state, countdown, hasRedirected]);\n\n\t// Countdown timer\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when countdown reaches 0\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tdata?.status?.authorizationUrl &&\n\t\t\tcountdown === 0 &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetHasRedirected(true);\n\t\t\twindow.open(data.status.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [data?.status, countdown, hasRedirected]);\n\n\tconst clientQuery = useQuery({\n\t\tqueryKey: [\"mcp-client\", token, connectionId, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst namespaceToUse =\n\t\t\t\tnamespace || (await getDefaultNamespace(getSmitheryClient(token)));\n\t\t\tconst transport = new SmitheryTransport({\n\t\t\t\tclient: getSmitheryClient(token),\n\t\t\t\tconnectionId: connectionId,\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\tconst mcpClient = await createMCPClient({ transport });\n\t\t\treturn mcpClient;\n\t\t},\n\t\tenabled: !!connectionId && data?.status?.state === \"connected\",\n\t});\n\n\tconst toolsQuery = useQuery({\n\t\tqueryKey: [\"tools\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tif (!clientQuery.data) {\n\t\t\t\tthrow new Error(\"Client not available\");\n\t\t\t}\n\t\t\tconst client = clientQuery.data;\n\t\t\treturn await client.tools();\n\t\t},\n\t\tenabled: !!clientQuery.data && data?.status?.state === \"connected\",\n\t});\n\tconst handleExecute = async (\n\t\ttoolName: string,\n\t\tparams: Record,\n\t) => {\n\t\tif (!toolsQuery.data) {\n\t\t\tthrow new Error(\"Tools not available\");\n\t\t}\n\t\tconst tool = toolsQuery.data[toolName];\n\t\tif (!tool) {\n\t\t\tthrow new Error(`Tool ${toolName} not found`);\n\t\t}\n\t\t// The execute method from AI SDK tools expects (params, options)\n\t\t// We're executing tools directly, so provide minimal required options\n\t\tconst options: ToolExecutionOptions = {\n\t\t\ttoolCallId: `manual-${Date.now()}`,\n\t\t\tmessages: [],\n\t\t};\n\t\treturn await tool.execute(params, options);\n\t};\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading connection...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{data.iconUrl && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{(\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.title ??\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.name ??\n\t\t\t\t\t\t\t\t\t\t\tdata.name\n\t\t\t\t\t\t\t\t\t\t)?.charAt(0)}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\t{data.serverInfo?.title ?? data.serverInfo?.name ?? data.name}\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.websiteUrl && (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\tView website\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.serverInfo?.description && (\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.description}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\tConnection created:{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t

·

\n\t\t\t\t\t\t\t

Connection ID: {data.connectionId}

\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.metadata && (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tMetadata\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t{JSON.stringify(data.metadata, null, 2)}\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data?.status && data.status.state === \"auth_required\" && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Auth required

\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t\t\t\t: \"You should be automatically redirected to authenticate. If not, click the link below:\"}\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tOpen authentication window\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading tools...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\tError: {toolsQuery.error.message}\n\t\t\t\t\t\t

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.data && data && (\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n", + "type": "registry:component", + "target": "components/smithery/active-connection.tsx" + }, { "path": "registry/new-york/smithery/connections.tsx", - "content": "\"use client\";\n\nimport { createMCPClient } from \"@ai-sdk/mcp\";\nimport Smithery from \"@smithery/api\";\nimport { createConnection } from \"@smithery/api/mcp\";\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport type { ToolExecutionOptions } from \"ai\";\nimport { Plus, RefreshCw, Trash2, X } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent } from \"@/components/ui/card\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { Toggle } from \"@/components/ui/toggle\";\nimport { cn } from \"@/lib/utils\";\nimport {\n\tConnectionConfigContext,\n\tuseConnectionConfig,\n} from \"@/registry/new-york/smithery/connection-context\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\nimport { ServerSearch } from \"@/registry/new-york/smithery/server-search\";\nimport { ToolsPanel } from \"@/registry/new-york/smithery/tools-panel\";\n\n// Re-export useConnectionConfig for backward compatibility\nexport { useConnectionConfig };\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\nconst ConnectionCardInner = ({\n\tconnection,\n\ttoken,\n\tnamespace,\n\tclassName,\n\t...rest\n}: {\n\tconnection: Connection;\n\ttoken: string;\n\tnamespace: string;\n\tclassName?: string;\n} & React.HTMLAttributes) => {\n\tconst queryClient = useQueryClient();\n\tconst deleteMutation = useMutation({\n\t\tmutationFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tawait client.beta.connect.connections.delete(connection.connectionId, {\n\t\t\t\tnamespace: namespace,\n\t\t\t});\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t});\n\n\treturn (\n\t\t\n\t\t\t\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{connection.name.charAt(0)}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.serverInfo?.title ??\n\t\t\t\t\t\t\tconnection.serverInfo?.name ??\n\t\t\t\t\t\t\tconnection.name}\n\t\t\t\t\t\t{connection.connectionId && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{\"•\".repeat(Math.min(connection.connectionId.length - 10, 4))}\n\t\t\t\t\t\t\t\t{connection.connectionId.slice(-10)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.mcpUrl}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t{new Date(connection.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{connection.metadata && JSON.stringify(connection.metadata)}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t\t deleteMutation.mutate()}\n\t\t\t\t\tdisabled={deleteMutation.isPending}\n\t\t\t\t>\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionCard = (\n\tprops: {\n\t\tconnection: Connection;\n\t\ttoken: string;\n\t\tnamespace: string;\n\t\tclassName?: string;\n\t} & React.HTMLAttributes,\n) => (\n\t\n\t\t\n\t\n);\n\nconst ConnectionsListInner = ({\n\ttoken,\n\tnamespace,\n\tdefaultActiveConnectionId,\n\tonActiveConnectionIdChange,\n\tdefaultShowSearchServers = true,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tdefaultActiveConnectionId || null,\n\t);\n\tconst [showSearchServers, setShowSearchServers] = useState(\n\t\tdefaultShowSearchServers || false,\n\t);\n\tconst { data, isLoading, error, refetch, isFetching } = useQuery({\n\t\tqueryKey: [\"connections\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst { connections } =\n\t\t\t\tawait client.beta.connect.connections.list(namespaceToUse);\n\t\t\treturn { connections, namespace: namespaceToUse };\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tuseEffect(() => {\n\t\tif (data?.connections && !defaultActiveConnectionId) {\n\t\t\tsetActiveConnectionId(data?.connections[0]?.connectionId || null);\n\t\t}\n\t}, [data?.connections, defaultActiveConnectionId]);\n\n\tuseEffect(() => {\n\t\tif (activeConnectionId) {\n\t\t\tonActiveConnectionIdChange(activeConnectionId);\n\t\t}\n\t}, [activeConnectionId, onActiveConnectionIdChange]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t

Connections

\n\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t\t\t{showSearchServers ? (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t\n\t\t\t\t\t refetch()}\n\t\t\t\t\t\tdisabled={isFetching}\n\t\t\t\t\t\ttitle=\"Refresh connections\"\n\t\t\t\t\t>\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
\n\t\t\t
\n\t\t\t
\n\t\t\t\t{showSearchServers && (\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{isLoading &&

Loading...

}\n\t\t\t\t{error && (\n\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t{data.connections.length === 0 && (\n\t\t\t\t\t\t\t

No connections found

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{data.connections.map((connection: Connection) => (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t setActiveConnectionId(connection.connectionId)}\n\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t))}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const ConnectionsList = (props: {\n\ttoken: string;\n\tnamespace?: string;\n\tdefaultActiveConnectionId?: string;\n\tonActiveConnectionIdChange: (connectionId: string) => void;\n\tdefaultShowSearchServers?: boolean;\n}) => (\n\t\n\t\t\n\t\n);\n\nconst ActiveConnection = ({\n\ttoken,\n\tnamespace,\n\tconnectionId,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n\tconnectionId: string;\n}) => {\n\tconst [countdown, setCountdown] = useState(null);\n\tconst [hasRedirected, setHasRedirected] = useState(false);\n\n\tconst { data, isLoading, error } = useQuery({\n\t\tqueryKey: [\"connection\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconst namespaceToUse = namespace || (await getDefaultNamespace(client));\n\t\t\tconst data = await client.beta.connect.connections.get(connectionId, {\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\treturn { namespace: namespaceToUse, ...data };\n\t\t},\n\t\t// Poll every 2 seconds when auth_required, stop when connected or error\n\t\trefetchInterval: (query) => {\n\t\t\tconst state = query.state.data?.status?.state;\n\t\t\tif (state === \"auth_required\") {\n\t\t\t\treturn 2000;\n\t\t\t}\n\t\t\treturn false;\n\t\t},\n\t});\n\n\t// Start countdown when auth_required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tcountdown === null &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetCountdown(5);\n\t\t}\n\t\t// Reset state when connectionId changes or status is no longer auth_required\n\t\tif (data?.status?.state !== \"auth_required\") {\n\t\t\tsetCountdown(null);\n\t\t\tsetHasRedirected(false);\n\t\t}\n\t}, [data?.status?.state, countdown, hasRedirected]);\n\n\t// Countdown timer\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when countdown reaches 0\n\tuseEffect(() => {\n\t\tif (\n\t\t\tdata?.status?.state === \"auth_required\" &&\n\t\t\tdata?.status?.authorizationUrl &&\n\t\t\tcountdown === 0 &&\n\t\t\t!hasRedirected\n\t\t) {\n\t\t\tsetHasRedirected(true);\n\t\t\twindow.open(data.status.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [data?.status, countdown, hasRedirected]);\n\n\tconst clientQuery = useQuery({\n\t\tqueryKey: [\"mcp-client\", token, connectionId, namespace],\n\t\tqueryFn: async () => {\n\t\t\tconst namespaceToUse =\n\t\t\t\tnamespace || (await getDefaultNamespace(getSmitheryClient(token)));\n\t\t\tconst { transport } = await createConnection({\n\t\t\t\tclient: getSmitheryClient(token),\n\t\t\t\tconnectionId: connectionId,\n\t\t\t\tnamespace: namespaceToUse,\n\t\t\t});\n\t\t\tconst mcpClient = await createMCPClient({ transport });\n\t\t\treturn mcpClient;\n\t\t},\n\t\tenabled: !!connectionId && data?.status?.state === \"connected\",\n\t});\n\n\tconst toolsQuery = useQuery({\n\t\tqueryKey: [\"tools\", connectionId, token, namespace],\n\t\tqueryFn: async () => {\n\t\t\tif (!clientQuery.data) {\n\t\t\t\tthrow new Error(\"Client not available\");\n\t\t\t}\n\t\t\tconst client = clientQuery.data;\n\t\t\treturn await client.tools();\n\t\t},\n\t\tenabled: !!clientQuery.data && data?.status?.state === \"connected\",\n\t});\n\tconst handleExecute = async (\n\t\ttoolName: string,\n\t\tparams: Record,\n\t) => {\n\t\tif (!toolsQuery.data) {\n\t\t\tthrow new Error(\"Tools not available\");\n\t\t}\n\t\tconst tool = toolsQuery.data[toolName];\n\t\tif (!tool) {\n\t\t\tthrow new Error(`Tool ${toolName} not found`);\n\t\t}\n\t\t// The execute method from AI SDK tools expects (params, options)\n\t\t// We're executing tools directly, so provide minimal required options\n\t\tconst options: ToolExecutionOptions = {\n\t\t\ttoolCallId: `manual-${Date.now()}`,\n\t\t\tmessages: [],\n\t\t};\n\t\treturn await tool.execute(params, options);\n\t};\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t{isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading connection...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Error: {error.message}

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data && (\n\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t{data.iconUrl && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t{(\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.title ??\n\t\t\t\t\t\t\t\t\t\t\tdata.serverInfo?.name ??\n\t\t\t\t\t\t\t\t\t\t\tdata.name\n\t\t\t\t\t\t\t\t\t\t)?.charAt(0)}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\t{data.serverInfo?.title ?? data.serverInfo?.name ?? data.name}\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.websiteUrl && (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\tView website\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.serverInfo?.description && (\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t{data.serverInfo?.description}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\tConnection created:{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleDateString()}{\" \"}\n\t\t\t\t\t\t\t\t{new Date(data.createdAt || \"\").toLocaleTimeString()}\n\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t

·

\n\t\t\t\t\t\t\t

Connection ID: {data.connectionId}

\n\t\t\t\t\t\t
\n\t\t\t\t\t\t{data.metadata && (\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t\tMetadata\n\t\t\t\t\t\t\t\t

\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\t{JSON.stringify(data.metadata, null, 2)}\n\t\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t)}\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{data?.status && data.status.state === \"auth_required\" && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Auth required

\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t\t\t\t: \"You should be automatically redirected to authenticate. If not, click the link below:\"}\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\n\t\t\t\t\t\t\tOpen authentication window\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.isLoading && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

Loading tools...

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.error && (\n\t\t\t\t\t
\n\t\t\t\t\t\t

\n\t\t\t\t\t\t\tError: {toolsQuery.error.message}\n\t\t\t\t\t\t

\n\t\t\t\t\t
\n\t\t\t\t)}\n\t\t\t\t{toolsQuery.data && data && (\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nconst ConnectionsInner = ({\n\ttoken,\n\tnamespace,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tnull,\n\t);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t{activeConnectionId && (\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const Connections = (props: { token: string; namespace?: string }) => (\n\t\n\t\t\n\t\n);\n", + "content": "\"use client\";\n\nimport { useState } from \"react\";\nimport { ActiveConnection } from \"@/registry/new-york/smithery/active-connection\";\nimport { useConnectionConfig } from \"@/registry/new-york/smithery/connection-context\";\nimport { ConnectionsList } from \"@/registry/new-york/smithery/connections-list\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\n\n// Re-export useConnectionConfig for backward compatibility\nexport { useConnectionConfig };\n\nconst ConnectionsInner = ({\n\ttoken,\n\tnamespace,\n}: {\n\ttoken: string;\n\tnamespace?: string;\n}) => {\n\tconst [activeConnectionId, setActiveConnectionId] = useState(\n\t\tnull,\n\t);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t\t
\n\t\t\t\t{activeConnectionId && (\n\t\t\t\t\t\n\t\t\t\t)}\n\t\t\t
\n\t\t
\n\t);\n};\n\nexport const Connections = (props: { token: string; namespace?: string }) => (\n\t\n\t\t\n\t\n);\n", "type": "registry:component", "target": "components/smithery/connections.tsx" }, { "path": "registry/new-york/smithery/server-search.tsx", - "content": "\"use client\";\n\nimport Smithery, { AuthenticationError } from \"@smithery/api\";\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport type { ServerListResponse } from \"@smithery/api/resources/servers/servers.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n\tAlertTriangle,\n\tArrowRight,\n\tCheckCircle,\n\tLink,\n\tLoader2,\n\tLock,\n} from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tCombobox,\n\tComboboxContent,\n\tComboboxEmpty,\n\tComboboxInput,\n\tComboboxItem,\n\tComboboxList,\n} from \"@/components/ui/combobox\";\nimport {\n\tItem,\n\tItemContent,\n\tItemDescription,\n\tItemMedia,\n\tItemTitle,\n} from \"@/components/ui/item\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\n\ninterface AuthRequiredBannerProps {\n\tserverName: string;\n\tauthorizationUrl?: string;\n\tcountdown: number | null;\n}\n\nconst AuthRequiredBanner = ({\n\tserverName,\n\tauthorizationUrl,\n\tcountdown,\n}: AuthRequiredBannerProps) => (\n\t
\n\t\t
\n\t\t\t

\n\t\t\t\t{\" \"}\n\t\t\t\tAuthorization required\n\t\t\t

\n\t\t\t

\n\t\t\t\tThis server requires you to authorize access. You should be\n\t\t\t\tautomatically redirected to complete authentication.{\" \"}\n\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t: \"If not, click the link below.\"}\n\t\t\t

\n\t\t\t{authorizationUrl && (\n\t\t\t\t\n\t\t\t\t\tSign in to {serverName}{\" \"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t
\n);\n\ninterface ExistingConnectionWarningBannerProps {\n\tserverName: string;\n\tonUseExisting: () => void;\n\tonCreateNew: () => void;\n\tisConnecting: boolean;\n}\n\nconst ExistingConnectionWarningBanner = ({\n\tserverName,\n\tonUseExisting,\n\tonCreateNew,\n\tisConnecting,\n}: ExistingConnectionWarningBannerProps) => (\n\t
\n\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t\t

\n\t\t\t\t\tConnection already exists\n\t\t\t\t

\n\t\t\t\t

\n\t\t\t\t\tA connection to {serverName} already exists. Would you like to use the\n\t\t\t\t\texisting connection or create a new one?\n\t\t\t\t

\n\t\t\t
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Use existing\"\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Create new\"\n\t\t\t\t)}\n\t\t\t\n\t\t
\n\t
\n);\n\ninterface ConnectionButtonProps {\n\tconnectionStatus?:\n\t\t| \"connected\"\n\t\t| \"auth_required\"\n\t\t| \"existing_connection_warning\"\n\t\t| \"error\";\n\tisConnecting: boolean;\n\tonConnect: () => void;\n}\n\nconst ConnectionButton = ({\n\tconnectionStatus,\n\tisConnecting,\n\tonConnect,\n}: ConnectionButtonProps) => {\n\tswitch (connectionStatus) {\n\t\tcase \"connected\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"auth_required\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"existing_connection_warning\":\n\t\t\t// Buttons are rendered separately in ExistingConnectionWarningBanner\n\t\t\treturn null;\n\t\tdefault:\n\t\t\treturn (\n\t\t\t\t\n\t\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tConnecting...\n\t\t\t\t\t\t\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tConnect \n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t);\n\t}\n};\n\ntype OnExistingConnectionMode = \"warn\" | \"error\" | \"use\" | \"create-new\";\n\ninterface ServerDisplayProps {\n\tserver: ServerListResponse;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ServerDisplay = ({\n\tserver,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ServerDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: activeNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (namespace) {\n\t\t\t\treturn namespace;\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst serverUrl =\n\t\tserver.qualifiedName.startsWith(\"http://\") ||\n\t\tserver.qualifiedName.startsWith(\"https://\")\n\t\t\t? server.qualifiedName\n\t\t\t: `https://server.smithery.ai/${server.qualifiedName}/mcp`;\n\tconst serverName = server.displayName || server.qualifiedName;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\tserverUrl,\n\t\t\t\tserverName,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\n\t\t\t\t\"error connecting to server\",\n\t\t\t\t`server: ${server.qualifiedName}, namespace: ${activeNamespace}`,\n\t\t\t\terror,\n\t\t\t);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t{server.verified && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.qualifiedName}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t{server.description &&

{server.description}

}\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t {\n\t\t\t\t\t\twindow.open(server.homepage, \"_blank\");\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tView Details\n\t\t\t\t\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\ntype ConnectionStatus =\n\t| { status: \"connected\"; connection: Connection }\n\t| { status: \"auth_required\"; connectionId: string; authorizationUrl?: string }\n\t| {\n\t\t\tstatus: \"existing_connection_warning\";\n\t\t\texistingConnectionId: string;\n\t\t\tname: string;\n\t }\n\t| { status: \"error\"; error: unknown };\n\nasync function checkConnectionStatus(\n\tclient: Smithery,\n\tconnectionId: string,\n\tnamespace: string,\n): Promise {\n\ttry {\n\t\tconst jsonRpcResponse = await client.beta.connect.mcp.call(connectionId, {\n\t\t\tnamespace,\n\t\t\tjsonrpc: \"2.0\",\n\t\t\tmethod: \"tools/list\",\n\t\t});\n\t\tconsole.log(\"jsonRpcResponse\", jsonRpcResponse);\n\n\t\tconst connection = await client.beta.connect.connections.get(connectionId, {\n\t\t\tnamespace: namespace,\n\t\t});\n\n\t\treturn {\n\t\t\tstatus: \"connected\",\n\t\t\tconnection,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof AuthenticationError) {\n\t\t\tconst errorData = error.error as unknown as {\n\t\t\t\terror?: { data?: { authorizationUrl?: string } };\n\t\t\t\tdata?: { authorizationUrl?: string };\n\t\t\t};\n\t\t\tconst authorizationUrl =\n\t\t\t\terrorData?.error?.data?.authorizationUrl ||\n\t\t\t\terrorData?.data?.authorizationUrl;\n\t\t\treturn {\n\t\t\t\tstatus: \"auth_required\",\n\t\t\t\tconnectionId,\n\t\t\t\tauthorizationUrl,\n\t\t\t};\n\t\t}\n\t\tconsole.error(\n\t\t\t\"error connecting to server\",\n\t\t\t`connectionId: ${connectionId}, namespace: ${namespace}`,\n\t\t\terror,\n\t\t);\n\t\treturn {\n\t\t\tstatus: \"error\",\n\t\t\terror,\n\t\t};\n\t}\n}\n\nasync function createConnection(\n\tclient: Smithery,\n\tnamespace: string,\n\tmcpUrl: string,\n\tname: string,\n\tonExistingConnection: OnExistingConnectionMode,\n): Promise {\n\t// Check for existing connection by name\n\tconst existingList = await client.beta.connect.connections.list(namespace, {\n\t\tname,\n\t});\n\tconst existing = existingList.connections[0];\n\n\tif (existing) {\n\t\tswitch (onExistingConnection) {\n\t\t\tcase \"error\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: new Error(`Connection \"${name}\" already exists`),\n\t\t\t\t};\n\t\t\tcase \"warn\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"existing_connection_warning\",\n\t\t\t\t\texistingConnectionId: existing.connectionId,\n\t\t\t\t\tname,\n\t\t\t\t};\n\t\t\tcase \"use\":\n\t\t\t\treturn checkConnectionStatus(client, existing.connectionId, namespace);\n\t\t\tcase \"create-new\":\n\t\t\t\t// Continue to create new connection\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Create new connection (API auto-generates unique ID)\n\tconsole.log(\"creating connection\", name);\n\tconst connection = await client.beta.connect.connections.create(namespace, {\n\t\tmcpUrl,\n\t\tname,\n\t});\n\tconsole.log(\"connection\", connection);\n\n\tif (connection.status?.state === \"auth_required\") {\n\t\treturn {\n\t\t\tstatus: \"auth_required\",\n\t\t\tconnectionId: connection.connectionId,\n\t\t\tauthorizationUrl: connection.status?.authorizationUrl,\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: \"connected\",\n\t\tconnection,\n\t};\n}\n\n// Helper function to detect if input is a URL\nconst isValidUrl = (str: string): boolean => {\n\tif (!str.trim()) return false;\n\ttry {\n\t\tconst url = new URL(str.trim());\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n};\n\ninterface ExternalURLDisplayProps {\n\turl: string;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ExternalURLDisplay = ({\n\turl,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ExternalURLDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: defaultNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst activeNamespace = namespace || defaultNamespace;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\turl,\n\t\t\t\turl,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\"error connecting to external URL\", error);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when auth is required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tconnectionData?.status === \"auth_required\" &&\n\t\t\tconnectionData.authorizationUrl &&\n\t\t\tcountdown === 0\n\t\t) {\n\t\t\twindow.open(connectionData.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [connectionData, countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

External MCP Server

\n\t\t\t\t\t

{url}

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nconst ServerSearchInner = ({\n\ttoken,\n\tnamespace,\n\tonExistingConnection = \"warn\",\n}: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => {\n\tconst [query, setQuery] = useState(\"\");\n\tconst [selectedServer, setSelectedServer] =\n\t\tuseState(null);\n\tconst [selectedExternalUrl, setSelectedExternalUrl] = useState(\n\t\tnull,\n\t);\n\tconst debouncedQuery = useDebounce(query, 300);\n\tconst isUrl = isValidUrl(query);\n\n\tconst { data, isLoading } = useQuery({\n\t\tqueryKey: [\"servers\", token, debouncedQuery],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconsole.log(\"searching\", debouncedQuery);\n\t\t\tconst servers = debouncedQuery\n\t\t\t\t? await client.servers.list({ q: debouncedQuery, pageSize: 3 })\n\t\t\t\t: await client.servers.list();\n\t\t\tconsole.log(`servers for ${debouncedQuery}`, servers);\n\t\t\treturn servers;\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst servers = data?.servers ?? [];\n\n\tconst handleKeyDown = (e: React.KeyboardEvent) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\te.preventDefault();\n\t\t\tif (isUrl) {\n\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\tsetSelectedServer(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t} else if (servers.length > 0) {\n\t\t\t\tsetSelectedServer(servers[0]);\n\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\tvalue={selectedServer}\n\t\t\t\tonValueChange={(server) => {\n\t\t\t\t\tsetSelectedServer(server);\n\t\t\t\t\t// Only clear external URL when a server is actually selected\n\t\t\t\t\t// (not when combobox fires null from closing without selection)\n\t\t\t\t\tif (server) {\n\t\t\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tonInputValueChange={(value) => setQuery(value)}\n\t\t\t\titemToStringLabel={(server) =>\n\t\t\t\t\tserver.displayName || server.qualifiedName\n\t\t\t\t}\n\t\t\t\tdefaultOpen={true}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{isUrl ? (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\t\t\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\t\t\t\t\t\tsetSelectedServer(null);\n\t\t\t\t\t\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tConnect to external MCP URL\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{query.trim()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{servers.length === 0 && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{isLoading ? \"Loading...\" : \"No servers found.\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{servers.map((server) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.description || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\n\n\t\t\t{selectedServer && token && namespace && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{selectedExternalUrl && token && (\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t);\n};\n\nexport const ServerSearch = (props: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => (\n\t\n\t\t\n\t\n);\n", + "content": "\"use client\";\n\nimport Smithery, { AuthenticationError } from \"@smithery/api\";\nimport type { Connection } from \"@smithery/api/resources/beta/connect/connections.mjs\";\nimport type { ServerListResponse } from \"@smithery/api/resources/servers/servers.mjs\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n\tAlertTriangle,\n\tArrowRight,\n\tCheckCircle,\n\tLink,\n\tLoader2,\n\tLock,\n} from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\nimport { Avatar, AvatarFallback, AvatarImage } from \"@/components/ui/avatar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n\tCombobox,\n\tComboboxContent,\n\tComboboxEmpty,\n\tComboboxInput,\n\tComboboxItem,\n\tComboboxList,\n} from \"@/components/ui/combobox\";\nimport {\n\tItem,\n\tItemContent,\n\tItemDescription,\n\tItemMedia,\n\tItemTitle,\n} from \"@/components/ui/item\";\nimport { useDebounce } from \"@/hooks/use-debounce\";\nimport { WithQueryClient } from \"@/registry/new-york/smithery/query-client-wrapper\";\n\ninterface AuthRequiredBannerProps {\n\tserverName: string;\n\tauthorizationUrl?: string;\n\tcountdown: number | null;\n}\n\nconst AuthRequiredBanner = ({\n\tserverName,\n\tauthorizationUrl,\n\tcountdown,\n}: AuthRequiredBannerProps) => (\n\t
\n\t\t
\n\t\t\t

\n\t\t\t\t{\" \"}\n\t\t\t\tAuthorization required\n\t\t\t

\n\t\t\t

\n\t\t\t\tThis server requires you to authorize access. You should be\n\t\t\t\tautomatically redirected to complete authentication.{\" \"}\n\t\t\t\t{countdown !== null && countdown > 0\n\t\t\t\t\t? `Redirecting in ${countdown}...`\n\t\t\t\t\t: \"If not, click the link below.\"}\n\t\t\t

\n\t\t\t{authorizationUrl && (\n\t\t\t\t\n\t\t\t\t\tSign in to {serverName}{\" \"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t
\n);\n\ninterface ExistingConnectionWarningBannerProps {\n\tserverName: string;\n\tonUseExisting: () => void;\n\tonCreateNew: () => void;\n\tisConnecting: boolean;\n}\n\nconst ExistingConnectionWarningBanner = ({\n\tserverName,\n\tonUseExisting,\n\tonCreateNew,\n\tisConnecting,\n}: ExistingConnectionWarningBannerProps) => (\n\t
\n\t\t
\n\t\t\t\n\t\t\t
\n\t\t\t\t

\n\t\t\t\t\tConnection already exists\n\t\t\t\t

\n\t\t\t\t

\n\t\t\t\t\tA connection to {serverName} already exists. Would you like to use the\n\t\t\t\t\texisting connection or create a new one?\n\t\t\t\t

\n\t\t\t
\n\t\t
\n\t\t
\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Use existing\"\n\t\t\t\t)}\n\t\t\t\n\t\t\t\n\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\n\t\t\t\t) : (\n\t\t\t\t\t\"Create new\"\n\t\t\t\t)}\n\t\t\t\n\t\t
\n\t
\n);\n\ninterface ConnectionButtonProps {\n\tconnectionStatus?:\n\t\t| \"connected\"\n\t\t| \"auth_required\"\n\t\t| \"existing_connection_warning\"\n\t\t| \"error\";\n\tisConnecting: boolean;\n\tonConnect: () => void;\n}\n\nconst ConnectionButton = ({\n\tconnectionStatus,\n\tisConnecting,\n\tonConnect,\n}: ConnectionButtonProps) => {\n\tswitch (connectionStatus) {\n\t\tcase \"connected\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"auth_required\":\n\t\t\treturn (\n\t\t\t\t\n\t\t\t);\n\t\tcase \"existing_connection_warning\":\n\t\t\t// Buttons are rendered separately in ExistingConnectionWarningBanner\n\t\t\treturn null;\n\t\tdefault:\n\t\t\treturn (\n\t\t\t\t\n\t\t\t\t\t{isConnecting ? (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tConnecting...\n\t\t\t\t\t\t\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\tConnect \n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t\n\t\t\t);\n\t}\n};\n\ntype OnExistingConnectionMode = \"warn\" | \"error\" | \"use\" | \"create-new\";\n\ninterface ServerDisplayProps {\n\tserver: ServerListResponse;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ServerDisplay = ({\n\tserver,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ServerDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: activeNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tif (namespace) {\n\t\t\t\treturn namespace;\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst serverUrl =\n\t\tserver.qualifiedName.startsWith(\"http://\") ||\n\t\tserver.qualifiedName.startsWith(\"https://\")\n\t\t\t? server.qualifiedName\n\t\t\t: `https://server.smithery.ai/${server.qualifiedName}/mcp`;\n\tconst serverName = server.displayName || server.qualifiedName;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\tserverUrl,\n\t\t\t\tserverName,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\n\t\t\t\t\"error connecting to server\",\n\t\t\t\t`server: ${server.qualifiedName}, namespace: ${activeNamespace}`,\n\t\t\t\terror,\n\t\t\t);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t{server.verified && (\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t)}\n\t\t\t\t\t

\n\t\t\t\t\t

\n\t\t\t\t\t\t{server.qualifiedName}\n\t\t\t\t\t

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t
\n\t\t\t\t{server.description &&

{server.description}

}\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t {\n\t\t\t\t\t\twindow.open(server.homepage, \"_blank\");\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\tView Details\n\t\t\t\t\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nasync function getDefaultNamespace(client: Smithery) {\n\tconst namespaces = await client.namespaces.list();\n\tif (namespaces.namespaces.length === 0) {\n\t\tthrow new Error(\"No namespaces found\");\n\t}\n\treturn namespaces.namespaces[0].name;\n}\n\nconst getSmitheryClient = (token: string) => {\n\treturn new Smithery({\n\t\tapiKey: token,\n\t\tbaseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL,\n\t});\n};\n\ntype ConnectionStatus =\n\t| { status: \"connected\"; connection: Connection }\n\t| { status: \"auth_required\"; connectionId: string; authorizationUrl?: string }\n\t| {\n\t\t\tstatus: \"existing_connection_warning\";\n\t\t\texistingConnectionId: string;\n\t\t\tname: string;\n\t }\n\t| { status: \"error\"; error: unknown };\n\nasync function checkConnectionStatus(\n\tclient: Smithery,\n\tconnectionId: string,\n\tnamespace: string,\n): Promise {\n\ttry {\n\t\tconst jsonRpcResponse = await client.beta.connect.rpc.call(connectionId, {\n\t\t\tnamespace,\n\t\t\tjsonrpc: \"2.0\",\n\t\t\tmethod: \"tools/list\",\n\t\t});\n\t\tconsole.log(\"jsonRpcResponse\", jsonRpcResponse);\n\n\t\tconst connection = await client.beta.connect.connections.get(connectionId, {\n\t\t\tnamespace: namespace,\n\t\t});\n\n\t\treturn {\n\t\t\tstatus: \"connected\",\n\t\t\tconnection,\n\t\t};\n\t} catch (error) {\n\t\tif (error instanceof AuthenticationError) {\n\t\t\tconst errorData = error.error as unknown as {\n\t\t\t\terror?: { data?: { authorizationUrl?: string } };\n\t\t\t\tdata?: { authorizationUrl?: string };\n\t\t\t};\n\t\t\tconst authorizationUrl =\n\t\t\t\terrorData?.error?.data?.authorizationUrl ||\n\t\t\t\terrorData?.data?.authorizationUrl;\n\t\t\treturn {\n\t\t\t\tstatus: \"auth_required\",\n\t\t\t\tconnectionId,\n\t\t\t\tauthorizationUrl,\n\t\t\t};\n\t\t}\n\t\tconsole.error(\n\t\t\t\"error connecting to server\",\n\t\t\t`connectionId: ${connectionId}, namespace: ${namespace}`,\n\t\t\terror,\n\t\t);\n\t\treturn {\n\t\t\tstatus: \"error\",\n\t\t\terror,\n\t\t};\n\t}\n}\n\nasync function createConnection(\n\tclient: Smithery,\n\tnamespace: string,\n\tmcpUrl: string,\n\tname: string,\n\tonExistingConnection: OnExistingConnectionMode,\n): Promise {\n\t// Check for existing connection by name\n\tconst existingList = await client.beta.connect.connections.list(namespace, {\n\t\tname,\n\t});\n\tconst existing = existingList.connections[0];\n\n\tif (existing) {\n\t\tswitch (onExistingConnection) {\n\t\t\tcase \"error\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"error\",\n\t\t\t\t\terror: new Error(`Connection \"${name}\" already exists`),\n\t\t\t\t};\n\t\t\tcase \"warn\":\n\t\t\t\treturn {\n\t\t\t\t\tstatus: \"existing_connection_warning\",\n\t\t\t\t\texistingConnectionId: existing.connectionId,\n\t\t\t\t\tname,\n\t\t\t\t};\n\t\t\tcase \"use\":\n\t\t\t\treturn checkConnectionStatus(client, existing.connectionId, namespace);\n\t\t\tcase \"create-new\":\n\t\t\t\t// Continue to create new connection\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Create new connection (API auto-generates unique ID)\n\tconsole.log(\"creating connection\", name);\n\tconst connection = await client.beta.connect.connections.create(namespace, {\n\t\tmcpUrl,\n\t\tname,\n\t});\n\tconsole.log(\"connection\", connection);\n\n\tif (connection.status?.state === \"auth_required\") {\n\t\treturn {\n\t\t\tstatus: \"auth_required\",\n\t\t\tconnectionId: connection.connectionId,\n\t\t\tauthorizationUrl: connection.status?.authorizationUrl,\n\t\t};\n\t}\n\n\treturn {\n\t\tstatus: \"connected\",\n\t\tconnection,\n\t};\n}\n\n// Helper function to detect if input is a URL\nconst isValidUrl = (str: string): boolean => {\n\tif (!str.trim()) return false;\n\ttry {\n\t\tconst url = new URL(str.trim());\n\t\treturn url.protocol === \"http:\" || url.protocol === \"https:\";\n\t} catch {\n\t\treturn false;\n\t}\n};\n\ninterface ExternalURLDisplayProps {\n\turl: string;\n\ttoken: string;\n\tnamespace?: string;\n\tonExistingConnection: OnExistingConnectionMode;\n}\n\nconst ExternalURLDisplay = ({\n\turl,\n\ttoken,\n\tnamespace,\n\tonExistingConnection,\n}: ExternalURLDisplayProps) => {\n\tconst queryClient = useQueryClient();\n\tconst [countdown, setCountdown] = useState(null);\n\n\tconst { data: defaultNamespace } = useQuery({\n\t\tqueryKey: [\"defaultNamespace\", token],\n\t\tqueryFn: async () => {\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await getDefaultNamespace(client);\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst activeNamespace = namespace || defaultNamespace;\n\n\tconst {\n\t\tmutate: connect,\n\t\tisPending: isConnecting,\n\t\tdata: connectionData,\n\t\tmutateAsync: connectAsync,\n\t} = useMutation({\n\t\tmutationFn: async (\n\t\t\toverrideMode?: \"use\" | \"create-new\",\n\t\t): Promise => {\n\t\t\tif (!token || !activeNamespace) {\n\t\t\t\tthrow new Error(\"Token and namespace are required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\treturn await createConnection(\n\t\t\t\tclient,\n\t\t\t\tactiveNamespace,\n\t\t\t\turl,\n\t\t\t\turl,\n\t\t\t\toverrideMode ?? onExistingConnection,\n\t\t\t);\n\t\t},\n\t\tonSuccess: () => {\n\t\t\tqueryClient.invalidateQueries({ queryKey: [\"connections\"] });\n\t\t},\n\t\tonError: (error) => {\n\t\t\tconsole.error(\"error connecting to external URL\", error);\n\t\t},\n\t});\n\n\tconst handleUseExisting = () => {\n\t\tconnect(\"use\");\n\t};\n\n\tconst handleCreateNew = () => {\n\t\tconnect(\"create-new\");\n\t};\n\n\t// Countdown timer for auth_required state\n\tuseEffect(() => {\n\t\tif (connectionData?.status === \"auth_required\" && countdown === null) {\n\t\t\tsetCountdown(3);\n\t\t}\n\t}, [connectionData?.status, countdown]);\n\n\tuseEffect(() => {\n\t\tif (countdown === null || countdown <= 0) return;\n\n\t\tconst timer = setTimeout(() => {\n\t\t\tsetCountdown(countdown - 1);\n\t\t}, 1000);\n\n\t\treturn () => clearTimeout(timer);\n\t}, [countdown]);\n\n\t// Auto-redirect when auth is required\n\tuseEffect(() => {\n\t\tif (\n\t\t\tconnectionData?.status === \"auth_required\" &&\n\t\t\tconnectionData.authorizationUrl &&\n\t\t\tcountdown === 0\n\t\t) {\n\t\t\twindow.open(connectionData.authorizationUrl, \"_blank\");\n\t\t}\n\t}, [connectionData, countdown]);\n\n\t// Poll connection status when auth_required\n\tconst authConnectionId =\n\t\tconnectionData?.status === \"auth_required\"\n\t\t\t? connectionData.connectionId\n\t\t\t: null;\n\n\tuseEffect(() => {\n\t\tif (!authConnectionId || !token || !activeNamespace) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst pollInterval = setInterval(async () => {\n\t\t\ttry {\n\t\t\t\tconst client = getSmitheryClient(token);\n\t\t\t\tconst status = await checkConnectionStatus(\n\t\t\t\t\tclient,\n\t\t\t\t\tauthConnectionId,\n\t\t\t\t\tactiveNamespace,\n\t\t\t\t);\n\n\t\t\t\tif (status.status === \"connected\") {\n\t\t\t\t\t// Update mutation data to trigger re-render\n\t\t\t\t\t// Use \"use\" mode to reuse the existing connection we're polling\n\t\t\t\t\tawait connectAsync(\"use\");\n\t\t\t\t\tsetCountdown(null);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"error checking connection status\", error);\n\t\t\t}\n\t\t}, 2000); // Check every 2 seconds\n\n\t\treturn () => clearInterval(pollInterval);\n\t}, [authConnectionId, token, activeNamespace, connectAsync]);\n\n\treturn (\n\t\t
\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t
\n\t\t\t\t\t

External MCP Server

\n\t\t\t\t\t

{url}

\n\t\t\t\t
\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"auth_required\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t
\n\t\t\t\t connect(undefined)}\n\t\t\t\t/>\n\t\t\t
\n\n\t\t\t{connectionData?.status === \"existing_connection_warning\" && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{connectionData?.status === \"error\" && (\n\t\t\t\t

\n\t\t\t\t\tError connecting to server\n\t\t\t\t

\n\t\t\t)}\n\t\t
\n\t);\n};\n\nconst ServerSearchInner = ({\n\ttoken,\n\tnamespace,\n\tonExistingConnection = \"warn\",\n}: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => {\n\tconst [query, setQuery] = useState(\"\");\n\tconst [selectedServer, setSelectedServer] =\n\t\tuseState(null);\n\tconst [selectedExternalUrl, setSelectedExternalUrl] = useState(\n\t\tnull,\n\t);\n\tconst debouncedQuery = useDebounce(query, 300);\n\tconst isUrl = isValidUrl(query);\n\n\tconst { data, isLoading } = useQuery({\n\t\tqueryKey: [\"servers\", token, debouncedQuery],\n\t\tqueryFn: async () => {\n\t\t\tif (!token) {\n\t\t\t\tthrow new Error(\"API token is required\");\n\t\t\t}\n\t\t\tconst client = getSmitheryClient(token);\n\t\t\tconsole.log(\"searching\", debouncedQuery);\n\t\t\tconst servers = debouncedQuery\n\t\t\t\t? await client.servers.list({ q: debouncedQuery, pageSize: 3 })\n\t\t\t\t: await client.servers.list();\n\t\t\tconsole.log(`servers for ${debouncedQuery}`, servers);\n\t\t\treturn servers;\n\t\t},\n\t\tenabled: !!token,\n\t});\n\n\tconst servers = data?.servers ?? [];\n\n\tconst handleKeyDown = (e: React.KeyboardEvent) => {\n\t\tif (e.key === \"Enter\") {\n\t\t\te.preventDefault();\n\t\t\tif (isUrl) {\n\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\tsetSelectedServer(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t} else if (servers.length > 0) {\n\t\t\t\tsetSelectedServer(servers[0]);\n\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t}\n\t\t}\n\t};\n\n\treturn (\n\t\t
\n\t\t\t\n\t\t\t\tvalue={selectedServer}\n\t\t\t\tonValueChange={(server) => {\n\t\t\t\t\tsetSelectedServer(server);\n\t\t\t\t\t// Only clear external URL when a server is actually selected\n\t\t\t\t\t// (not when combobox fires null from closing without selection)\n\t\t\t\t\tif (server) {\n\t\t\t\t\t\tsetSelectedExternalUrl(null);\n\t\t\t\t\t}\n\t\t\t\t}}\n\t\t\t\tonInputValueChange={(value) => setQuery(value)}\n\t\t\t\titemToStringLabel={(server) =>\n\t\t\t\t\tserver.displayName || server.qualifiedName\n\t\t\t\t}\n\t\t\t\tdefaultOpen={true}\n\t\t\t>\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\t{isUrl ? (\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t {\n\t\t\t\t\t\t\t\t\tconst urlToConnect = query.trim();\n\t\t\t\t\t\t\t\t\tsetSelectedExternalUrl(urlToConnect);\n\t\t\t\t\t\t\t\t\tsetSelectedServer(null);\n\t\t\t\t\t\t\t\t\tsetTimeout(() => setQuery(\"\"), 0);\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\tConnect to external MCP URL\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t{query.trim()}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t) : (\n\t\t\t\t\t\t<>\n\t\t\t\t\t\t\t{servers.length === 0 && (\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t{isLoading ? \"Loading...\" : \"No servers found.\"}\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t{servers.map((server) => (\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\tserver.qualifiedName?.charAt(0) ||\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\"S\"}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.displayName || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\t\t{server.description || server.qualifiedName}\n\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t\t)}\n\t\t\t\t
\n\t\t\t\n\n\t\t\t{selectedServer && token && namespace && (\n\t\t\t\t\n\t\t\t)}\n\n\t\t\t{selectedExternalUrl && token && (\n\t\t\t\t\n\t\t\t)}\n\t\t
\n\t);\n};\n\nexport const ServerSearch = (props: {\n\ttoken?: string;\n\tnamespace?: string;\n\tonExistingConnection?: OnExistingConnectionMode;\n}) => (\n\t\n\t\t\n\t\n);\n", "type": "registry:component", "target": "components/smithery/server-search.tsx" }, diff --git a/registry/new-york/smithery/active-connection.tsx b/registry/new-york/smithery/active-connection.tsx new file mode 100644 index 0000000..967a556 --- /dev/null +++ b/registry/new-york/smithery/active-connection.tsx @@ -0,0 +1,258 @@ +"use client"; + +import { createMCPClient } from "@ai-sdk/mcp"; +import { SmitheryTransport } from "@smithery/api/mcp"; +import { useQuery } from "@tanstack/react-query"; +import type { ToolExecutionOptions } from "ai"; +import { useEffect, useState } from "react"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { + ConnectionConfigContext, + useConnectionConfig, +} from "@/registry/new-york/smithery/connection-context"; +import { + getDefaultNamespace, + getSmitheryClient, +} from "@/registry/new-york/smithery/smithery-utils"; +import { ToolsPanel } from "@/registry/new-york/smithery/tools-panel"; + +// Re-export useConnectionConfig for backward compatibility +export { useConnectionConfig }; + +export const ActiveConnection = ({ + token, + namespace, + connectionId, +}: { + token: string; + namespace?: string; + connectionId: string; +}) => { + const [countdown, setCountdown] = useState(null); + const [hasRedirected, setHasRedirected] = useState(false); + + const { data, isLoading, error } = useQuery({ + queryKey: ["connection", connectionId, token, namespace], + queryFn: async () => { + const client = getSmitheryClient(token); + const namespaceToUse = namespace || (await getDefaultNamespace(client)); + const data = await client.beta.connect.connections.get(connectionId, { + namespace: namespaceToUse, + }); + return { namespace: namespaceToUse, ...data }; + }, + // Poll every 2 seconds when auth_required, stop when connected or error + refetchInterval: (query) => { + const state = query.state.data?.status?.state; + if (state === "auth_required") { + return 2000; + } + return false; + }, + }); + + // Start countdown when auth_required + useEffect(() => { + if ( + data?.status?.state === "auth_required" && + countdown === null && + !hasRedirected + ) { + setCountdown(5); + } + // Reset state when connectionId changes or status is no longer auth_required + if (data?.status?.state !== "auth_required") { + setCountdown(null); + setHasRedirected(false); + } + }, [data?.status?.state, countdown, hasRedirected]); + + // Countdown timer + useEffect(() => { + if (countdown === null || countdown <= 0) return; + + const timer = setTimeout(() => { + setCountdown(countdown - 1); + }, 1000); + + return () => clearTimeout(timer); + }, [countdown]); + + // Auto-redirect when countdown reaches 0 + useEffect(() => { + if ( + data?.status?.state === "auth_required" && + data?.status?.authorizationUrl && + countdown === 0 && + !hasRedirected + ) { + setHasRedirected(true); + window.open(data.status.authorizationUrl, "_blank"); + } + }, [data?.status, countdown, hasRedirected]); + + const clientQuery = useQuery({ + queryKey: ["mcp-client", token, connectionId, namespace], + queryFn: async () => { + const namespaceToUse = + namespace || (await getDefaultNamespace(getSmitheryClient(token))); + const transport = new SmitheryTransport({ + client: getSmitheryClient(token), + connectionId: connectionId, + namespace: namespaceToUse, + }); + const mcpClient = await createMCPClient({ transport }); + return mcpClient; + }, + enabled: !!connectionId && data?.status?.state === "connected", + }); + + const toolsQuery = useQuery({ + queryKey: ["tools", connectionId, token, namespace], + queryFn: async () => { + if (!clientQuery.data) { + throw new Error("Client not available"); + } + const client = clientQuery.data; + return await client.tools(); + }, + enabled: !!clientQuery.data && data?.status?.state === "connected", + }); + const handleExecute = async ( + toolName: string, + params: Record, + ) => { + if (!toolsQuery.data) { + throw new Error("Tools not available"); + } + const tool = toolsQuery.data[toolName]; + if (!tool) { + throw new Error(`Tool ${toolName} not found`); + } + // The execute method from AI SDK tools expects (params, options) + // We're executing tools directly, so provide minimal required options + const options: ToolExecutionOptions = { + toolCallId: `manual-${Date.now()}`, + messages: [], + }; + return await tool.execute(params, options); + }; + + return ( +
+
+ {isLoading && ( +
+

Loading connection...

+
+ )} + {error && ( +
+

Error: {error.message}

+
+ )} + {data && ( +
+
+ {data.iconUrl && ( + + + + {( + data.serverInfo?.title ?? + data.serverInfo?.name ?? + data.name + )?.charAt(0)} + + + )} +
+

+ {data.serverInfo?.title ?? data.serverInfo?.name ?? data.name} +

+ {data.serverInfo?.websiteUrl && ( + + View website + + )} +
+
+ {data.serverInfo?.description && ( +

+ {data.serverInfo?.description} +

+ )} +
+

+ Connection created:{" "} + {new Date(data.createdAt || "").toLocaleDateString()}{" "} + {new Date(data.createdAt || "").toLocaleTimeString()} +

+

·

+

Connection ID: {data.connectionId}

+
+ {data.metadata && ( +
+

+ Metadata +

+
+									{JSON.stringify(data.metadata, null, 2)}
+								
+
+ )} +
+ )} + {data?.status && data.status.state === "auth_required" && ( +
+

Auth required

+

+ {countdown !== null && countdown > 0 + ? `Redirecting in ${countdown}...` + : "You should be automatically redirected to authenticate. If not, click the link below:"} +

+ + Open authentication window + +
+ )} + {toolsQuery.isLoading && ( +
+

Loading tools...

+
+ )} + {toolsQuery.error && ( +
+

+ Error: {toolsQuery.error.message} +

+
+ )} + {toolsQuery.data && data && ( + +
+ +
+
+ )} +
+
+ ); +}; diff --git a/registry/new-york/smithery/connection-card.tsx b/registry/new-york/smithery/connection-card.tsx new file mode 100644 index 0000000..a23878b --- /dev/null +++ b/registry/new-york/smithery/connection-card.tsx @@ -0,0 +1,94 @@ +"use client"; + +import type { Connection } from "@smithery/api/resources/beta/connect/connections.mjs"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { Trash2 } from "lucide-react"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent } from "@/components/ui/card"; +import { cn } from "@/lib/utils"; +import { WithQueryClient } from "@/registry/new-york/smithery/query-client-wrapper"; +import { getSmitheryClient } from "@/registry/new-york/smithery/smithery-utils"; + +const ConnectionCardInner = ({ + connection, + token, + namespace, + className, + ...rest +}: { + connection: Connection; + token: string; + namespace: string; + className?: string; +} & React.HTMLAttributes) => { + const queryClient = useQueryClient(); + const deleteMutation = useMutation({ + mutationFn: async () => { + const client = getSmitheryClient(token); + await client.beta.connect.connections.delete(connection.connectionId, { + namespace: namespace, + }); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["connections"] }); + }, + }); + + return ( + + + + + + {connection.name.charAt(0)} + + +
+

+ {connection.serverInfo?.title ?? + connection.serverInfo?.name ?? + connection.name} + {connection.connectionId && ( + + {"•".repeat(Math.min(connection.connectionId.length - 10, 4))} + {connection.connectionId.slice(-10)} + + )} +

+

+ {connection.mcpUrl} +

+

+ {new Date(connection.createdAt || "").toLocaleDateString()}{" "} + {new Date(connection.createdAt || "").toLocaleTimeString()} +

+

+ {connection.metadata && JSON.stringify(connection.metadata)} +

+
+ +
+
+ ); +}; + +export const ConnectionCard = ( + props: { + connection: Connection; + token: string; + namespace: string; + className?: string; + } & React.HTMLAttributes, +) => ( + + + +); diff --git a/registry/new-york/smithery/connections-list.tsx b/registry/new-york/smithery/connections-list.tsx new file mode 100644 index 0000000..d1fee0f --- /dev/null +++ b/registry/new-york/smithery/connections-list.tsx @@ -0,0 +1,142 @@ +"use client"; + +import type { Connection } from "@smithery/api/resources/beta/connect/connections.mjs"; +import { useQuery } from "@tanstack/react-query"; +import { Plus, RefreshCw, X } from "lucide-react"; +import { useEffect, useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Separator } from "@/components/ui/separator"; +import { Toggle } from "@/components/ui/toggle"; +import { cn } from "@/lib/utils"; +import { ConnectionCard } from "@/registry/new-york/smithery/connection-card"; +import { WithQueryClient } from "@/registry/new-york/smithery/query-client-wrapper"; +import { ServerSearch } from "@/registry/new-york/smithery/server-search"; +import { + getDefaultNamespace, + getSmitheryClient, +} from "@/registry/new-york/smithery/smithery-utils"; + +const ConnectionsListInner = ({ + token, + namespace, + defaultActiveConnectionId, + onActiveConnectionIdChange, + defaultShowSearchServers = true, +}: { + token: string; + namespace?: string; + defaultActiveConnectionId?: string; + onActiveConnectionIdChange: (connectionId: string) => void; + defaultShowSearchServers?: boolean; +}) => { + const [activeConnectionId, setActiveConnectionId] = useState( + defaultActiveConnectionId || null, + ); + const [showSearchServers, setShowSearchServers] = useState( + defaultShowSearchServers || false, + ); + const { data, isLoading, error, refetch, isFetching } = useQuery({ + queryKey: ["connections", token], + queryFn: async () => { + if (!token) { + throw new Error("API token is required"); + } + const client = getSmitheryClient(token); + const namespaceToUse = namespace || (await getDefaultNamespace(client)); + const { connections } = + await client.beta.connect.connections.list(namespaceToUse); + return { connections, namespace: namespaceToUse }; + }, + enabled: !!token, + }); + + useEffect(() => { + if (data?.connections && !defaultActiveConnectionId) { + setActiveConnectionId(data?.connections[0]?.connectionId || null); + } + }, [data?.connections, defaultActiveConnectionId]); + + useEffect(() => { + if (activeConnectionId) { + onActiveConnectionIdChange(activeConnectionId); + } + }, [activeConnectionId, onActiveConnectionIdChange]); + + return ( +
+
+

Connections

+
+ + {showSearchServers ? ( + + ) : ( + + )} + + +
+
+
+ {showSearchServers && ( +
+ +
+ )} + {isLoading &&

Loading...

} + {error && ( +

Error: {error.message}

+ )} + {data && ( +
+ {data.connections.length === 0 && ( +

No connections found

+ )} + {data.connections.map((connection: Connection) => ( +
+ + setActiveConnectionId(connection.connectionId)} + /> +
+ ))} +
+ )} +
+
+ ); +}; + +export const ConnectionsList = (props: { + token: string; + namespace?: string; + defaultActiveConnectionId?: string; + onActiveConnectionIdChange: (connectionId: string) => void; + defaultShowSearchServers?: boolean; +}) => ( + + + +); diff --git a/registry/new-york/smithery/connections.tsx b/registry/new-york/smithery/connections.tsx index 4e6b8c1..fe13955 100644 --- a/registry/new-york/smithery/connections.tsx +++ b/registry/new-york/smithery/connections.tsx @@ -1,491 +1,14 @@ "use client"; -import { createMCPClient } from "@ai-sdk/mcp"; -import Smithery from "@smithery/api"; -import { createConnection } from "@smithery/api/mcp"; -import type { Connection } from "@smithery/api/resources/beta/connect/connections.mjs"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import type { ToolExecutionOptions } from "ai"; -import { Plus, RefreshCw, Trash2, X } from "lucide-react"; -import { useEffect, useState } from "react"; -import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent } from "@/components/ui/card"; -import { Separator } from "@/components/ui/separator"; -import { Toggle } from "@/components/ui/toggle"; -import { cn } from "@/lib/utils"; -import { - ConnectionConfigContext, - useConnectionConfig, -} from "@/registry/new-york/smithery/connection-context"; +import { useState } from "react"; +import { ActiveConnection } from "@/registry/new-york/smithery/active-connection"; +import { useConnectionConfig } from "@/registry/new-york/smithery/connection-context"; +import { ConnectionsList } from "@/registry/new-york/smithery/connections-list"; import { WithQueryClient } from "@/registry/new-york/smithery/query-client-wrapper"; -import { ServerSearch } from "@/registry/new-york/smithery/server-search"; -import { ToolsPanel } from "@/registry/new-york/smithery/tools-panel"; // Re-export useConnectionConfig for backward compatibility export { useConnectionConfig }; -async function getDefaultNamespace(client: Smithery) { - const namespaces = await client.namespaces.list(); - if (namespaces.namespaces.length === 0) { - throw new Error("No namespaces found"); - } - return namespaces.namespaces[0].name; -} - -const getSmitheryClient = (token: string) => { - return new Smithery({ - apiKey: token, - baseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL, - }); -}; - -const ConnectionCardInner = ({ - connection, - token, - namespace, - className, - ...rest -}: { - connection: Connection; - token: string; - namespace: string; - className?: string; -} & React.HTMLAttributes) => { - const queryClient = useQueryClient(); - const deleteMutation = useMutation({ - mutationFn: async () => { - const client = getSmitheryClient(token); - await client.beta.connect.connections.delete(connection.connectionId, { - namespace: namespace, - }); - }, - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ["connections"] }); - }, - }); - - return ( - - - - - - {connection.name.charAt(0)} - - -
-

- {connection.serverInfo?.title ?? - connection.serverInfo?.name ?? - connection.name} - {connection.connectionId && ( - - {"•".repeat(Math.min(connection.connectionId.length - 10, 4))} - {connection.connectionId.slice(-10)} - - )} -

-

- {connection.mcpUrl} -

-

- {new Date(connection.createdAt || "").toLocaleDateString()}{" "} - {new Date(connection.createdAt || "").toLocaleTimeString()} -

-

- {connection.metadata && JSON.stringify(connection.metadata)} -

-
- -
-
- ); -}; - -export const ConnectionCard = ( - props: { - connection: Connection; - token: string; - namespace: string; - className?: string; - } & React.HTMLAttributes, -) => ( - - - -); - -const ConnectionsListInner = ({ - token, - namespace, - defaultActiveConnectionId, - onActiveConnectionIdChange, - defaultShowSearchServers = true, -}: { - token: string; - namespace?: string; - defaultActiveConnectionId?: string; - onActiveConnectionIdChange: (connectionId: string) => void; - defaultShowSearchServers?: boolean; -}) => { - const [activeConnectionId, setActiveConnectionId] = useState( - defaultActiveConnectionId || null, - ); - const [showSearchServers, setShowSearchServers] = useState( - defaultShowSearchServers || false, - ); - const { data, isLoading, error, refetch, isFetching } = useQuery({ - queryKey: ["connections", token], - queryFn: async () => { - if (!token) { - throw new Error("API token is required"); - } - const client = getSmitheryClient(token); - const namespaceToUse = namespace || (await getDefaultNamespace(client)); - const { connections } = - await client.beta.connect.connections.list(namespaceToUse); - return { connections, namespace: namespaceToUse }; - }, - enabled: !!token, - }); - - useEffect(() => { - if (data?.connections && !defaultActiveConnectionId) { - setActiveConnectionId(data?.connections[0]?.connectionId || null); - } - }, [data?.connections, defaultActiveConnectionId]); - - useEffect(() => { - if (activeConnectionId) { - onActiveConnectionIdChange(activeConnectionId); - } - }, [activeConnectionId, onActiveConnectionIdChange]); - - return ( -
-
-

Connections

-
- - {showSearchServers ? ( - - ) : ( - - )} - - -
-
-
- {showSearchServers && ( -
- -
- )} - {isLoading &&

Loading...

} - {error && ( -

Error: {error.message}

- )} - {data && ( -
- {data.connections.length === 0 && ( -

No connections found

- )} - {data.connections.map((connection: Connection) => ( -
- - setActiveConnectionId(connection.connectionId)} - /> -
- ))} -
- )} -
-
- ); -}; - -export const ConnectionsList = (props: { - token: string; - namespace?: string; - defaultActiveConnectionId?: string; - onActiveConnectionIdChange: (connectionId: string) => void; - defaultShowSearchServers?: boolean; -}) => ( - - - -); - -const ActiveConnection = ({ - token, - namespace, - connectionId, -}: { - token: string; - namespace?: string; - connectionId: string; -}) => { - const [countdown, setCountdown] = useState(null); - const [hasRedirected, setHasRedirected] = useState(false); - - const { data, isLoading, error } = useQuery({ - queryKey: ["connection", connectionId, token, namespace], - queryFn: async () => { - const client = getSmitheryClient(token); - const namespaceToUse = namespace || (await getDefaultNamespace(client)); - const data = await client.beta.connect.connections.get(connectionId, { - namespace: namespaceToUse, - }); - return { namespace: namespaceToUse, ...data }; - }, - // Poll every 2 seconds when auth_required, stop when connected or error - refetchInterval: (query) => { - const state = query.state.data?.status?.state; - if (state === "auth_required") { - return 2000; - } - return false; - }, - }); - - // Start countdown when auth_required - useEffect(() => { - if ( - data?.status?.state === "auth_required" && - countdown === null && - !hasRedirected - ) { - setCountdown(5); - } - // Reset state when connectionId changes or status is no longer auth_required - if (data?.status?.state !== "auth_required") { - setCountdown(null); - setHasRedirected(false); - } - }, [data?.status?.state, countdown, hasRedirected]); - - // Countdown timer - useEffect(() => { - if (countdown === null || countdown <= 0) return; - - const timer = setTimeout(() => { - setCountdown(countdown - 1); - }, 1000); - - return () => clearTimeout(timer); - }, [countdown]); - - // Auto-redirect when countdown reaches 0 - useEffect(() => { - if ( - data?.status?.state === "auth_required" && - data?.status?.authorizationUrl && - countdown === 0 && - !hasRedirected - ) { - setHasRedirected(true); - window.open(data.status.authorizationUrl, "_blank"); - } - }, [data?.status, countdown, hasRedirected]); - - const clientQuery = useQuery({ - queryKey: ["mcp-client", token, connectionId, namespace], - queryFn: async () => { - const namespaceToUse = - namespace || (await getDefaultNamespace(getSmitheryClient(token))); - const { transport } = await createConnection({ - client: getSmitheryClient(token), - connectionId: connectionId, - namespace: namespaceToUse, - }); - const mcpClient = await createMCPClient({ transport }); - return mcpClient; - }, - enabled: !!connectionId && data?.status?.state === "connected", - }); - - const toolsQuery = useQuery({ - queryKey: ["tools", connectionId, token, namespace], - queryFn: async () => { - if (!clientQuery.data) { - throw new Error("Client not available"); - } - const client = clientQuery.data; - return await client.tools(); - }, - enabled: !!clientQuery.data && data?.status?.state === "connected", - }); - const handleExecute = async ( - toolName: string, - params: Record, - ) => { - if (!toolsQuery.data) { - throw new Error("Tools not available"); - } - const tool = toolsQuery.data[toolName]; - if (!tool) { - throw new Error(`Tool ${toolName} not found`); - } - // The execute method from AI SDK tools expects (params, options) - // We're executing tools directly, so provide minimal required options - const options: ToolExecutionOptions = { - toolCallId: `manual-${Date.now()}`, - messages: [], - }; - return await tool.execute(params, options); - }; - - return ( -
-
- {isLoading && ( -
-

Loading connection...

-
- )} - {error && ( -
-

Error: {error.message}

-
- )} - {data && ( -
-
- {data.iconUrl && ( - - - - {( - data.serverInfo?.title ?? - data.serverInfo?.name ?? - data.name - )?.charAt(0)} - - - )} -
-

- {data.serverInfo?.title ?? data.serverInfo?.name ?? data.name} -

- {data.serverInfo?.websiteUrl && ( - - View website - - )} -
-
- {data.serverInfo?.description && ( -

- {data.serverInfo?.description} -

- )} -
-

- Connection created:{" "} - {new Date(data.createdAt || "").toLocaleDateString()}{" "} - {new Date(data.createdAt || "").toLocaleTimeString()} -

-

·

-

Connection ID: {data.connectionId}

-
- {data.metadata && ( -
-

- Metadata -

-
-									{JSON.stringify(data.metadata, null, 2)}
-								
-
- )} -
- )} - {data?.status && data.status.state === "auth_required" && ( -
-

Auth required

-

- {countdown !== null && countdown > 0 - ? `Redirecting in ${countdown}...` - : "You should be automatically redirected to authenticate. If not, click the link below:"} -

- - Open authentication window - -
- )} - {toolsQuery.isLoading && ( -
-

Loading tools...

-
- )} - {toolsQuery.error && ( -
-

- Error: {toolsQuery.error.message} -

-
- )} - {toolsQuery.data && data && ( - -
- -
-
- )} -
-
- ); -}; - const ConnectionsInner = ({ token, namespace, @@ -500,7 +23,7 @@ const ConnectionsInner = ({ return (
- { try { - const jsonRpcResponse = await client.beta.connect.mcp.call(connectionId, { + const jsonRpcResponse = await client.beta.connect.rpc.call(connectionId, { namespace, jsonrpc: "2.0", method: "tools/list", diff --git a/registry/new-york/smithery/smithery-utils.ts b/registry/new-york/smithery/smithery-utils.ts new file mode 100644 index 0000000..eff811d --- /dev/null +++ b/registry/new-york/smithery/smithery-utils.ts @@ -0,0 +1,16 @@ +import Smithery from "@smithery/api"; + +export async function getDefaultNamespace(client: Smithery) { + const namespaces = await client.namespaces.list(); + if (namespaces.namespaces.length === 0) { + throw new Error("No namespaces found"); + } + return namespaces.namespaces[0].name; +} + +export const getSmitheryClient = (token: string) => { + return new Smithery({ + apiKey: token, + baseURL: process.env.NEXT_PUBLIC_SMITHERY_API_URL, + }); +}; diff --git a/registry/new-york/smithery/tool-detail-dialog.tsx b/registry/new-york/smithery/tool-detail-dialog.tsx index 7ffff6f..69931b7 100644 --- a/registry/new-york/smithery/tool-detail-dialog.tsx +++ b/registry/new-york/smithery/tool-detail-dialog.tsx @@ -557,7 +557,7 @@ function generateToolExecuteCode( config: ConnectionConfig | null, ): string { const mcpUrl = config?.mcpUrl || "process.env.MCP_URL"; - const namespace = config?.namespace + const _namespace = config?.namespace ? `"${config.namespace}"` : "process.env.SMITHERY_NAMESPACE"; const connectionId = config?.connectionId diff --git a/registry/registry.json b/registry/registry.json index 8a36088..23a19df 100644 --- a/registry/registry.json +++ b/registry/registry.json @@ -234,6 +234,11 @@ "type": "registry:component", "target": "components/smithery/query-client-wrapper.tsx" }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, { "path": "registry/new-york/smithery/connection-context.tsx", "type": "registry:component", @@ -259,6 +264,21 @@ "type": "registry:component", "target": "components/smithery/tools-panel.tsx" }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + }, + { + "path": "registry/new-york/smithery/connections-list.tsx", + "type": "registry:component", + "target": "components/smithery/connections-list.tsx" + }, + { + "path": "registry/new-york/smithery/active-connection.tsx", + "type": "registry:component", + "target": "components/smithery/active-connection.tsx" + }, { "path": "registry/new-york/smithery/server-search.tsx", "type": "registry:component", @@ -300,6 +320,159 @@ } ] }, + { + "name": "smithery-utils", + "type": "registry:block", + "title": "Smithery Utils", + "description": "Utility functions for Smithery API client instantiation and namespace management.", + "dependencies": ["@smithery/api"], + "files": [ + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + } + ] + }, + { + "name": "smithery-connection-card", + "type": "registry:block", + "title": "Smithery Connection Card", + "description": "A reusable card component for displaying individual MCP server connection details.", + "dependencies": [ + "@smithery/api", + "@tanstack/react-query", + "lucide-react" + ], + "registryDependencies": ["avatar", "button", "card"], + "files": [ + { + "path": "registry/new-york/smithery/query-client-wrapper.tsx", + "type": "registry:component", + "target": "components/smithery/query-client-wrapper.tsx" + }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + } + ] + }, + { + "name": "smithery-connections-list", + "type": "registry:block", + "title": "Smithery Connections List", + "description": "A list component for browsing and managing MCP server connections with search functionality.", + "dependencies": [ + "@smithery/api", + "@tanstack/react-query", + "lucide-react" + ], + "registryDependencies": ["button", "separator", "toggle"], + "files": [ + { + "path": "registry/new-york/smithery/query-client-wrapper.tsx", + "type": "registry:component", + "target": "components/smithery/query-client-wrapper.tsx" + }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + }, + { + "path": "registry/new-york/smithery/server-search.tsx", + "type": "registry:component", + "target": "components/smithery/server-search.tsx" + }, + { + "path": "registry/new-york/smithery/connections-list.tsx", + "type": "registry:component", + "target": "components/smithery/connections-list.tsx" + } + ] + }, + { + "name": "smithery-active-connection", + "type": "registry:block", + "title": "Smithery Active Connection", + "description": "A component for viewing active connection details, handling authentication flow, and displaying available tools.", + "dependencies": [ + "@ai-sdk/mcp", + "@smithery/api", + "@tanstack/react-query", + "ai", + "lucide-react", + "react-hook-form", + "tokenx", + "shiki" + ], + "registryDependencies": [ + "avatar", + "badge", + "button", + "card", + "dialog", + "field", + "input", + "select", + "switch", + "tabs", + "textarea" + ], + "files": [ + { + "path": "registry/new-york/smithery/query-client-wrapper.tsx", + "type": "registry:component", + "target": "components/smithery/query-client-wrapper.tsx" + }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, + { + "path": "registry/new-york/smithery/connection-context.tsx", + "type": "registry:component", + "target": "components/smithery/connection-context.tsx" + }, + { + "path": "registry/new-york/smithery/code-block.tsx", + "type": "registry:component", + "target": "components/smithery/code-block.tsx" + }, + { + "path": "registry/new-york/smithery/tool-detail-dialog.tsx", + "type": "registry:component", + "target": "components/smithery/tool-detail-dialog.tsx" + }, + { + "path": "registry/new-york/smithery/tool-card.tsx", + "type": "registry:component", + "target": "components/smithery/tool-card.tsx" + }, + { + "path": "registry/new-york/smithery/tools-panel.tsx", + "type": "registry:component", + "target": "components/smithery/tools-panel.tsx" + }, + { + "path": "registry/new-york/smithery/active-connection.tsx", + "type": "registry:component", + "target": "components/smithery/active-connection.tsx" + } + ] + }, { "name": "smithery-tokens", "type": "registry:block", @@ -356,6 +529,11 @@ "type": "registry:component", "target": "components/smithery/query-client-wrapper.tsx" }, + { + "path": "registry/new-york/smithery/smithery-utils.ts", + "type": "registry:lib", + "target": "lib/smithery/smithery-utils.ts" + }, { "path": "registry/new-york/smithery/connection-context.tsx", "type": "registry:component", @@ -386,6 +564,21 @@ "type": "registry:component", "target": "components/smithery/tools-panel.tsx" }, + { + "path": "registry/new-york/smithery/connection-card.tsx", + "type": "registry:component", + "target": "components/smithery/connection-card.tsx" + }, + { + "path": "registry/new-york/smithery/connections-list.tsx", + "type": "registry:component", + "target": "components/smithery/connections-list.tsx" + }, + { + "path": "registry/new-york/smithery/active-connection.tsx", + "type": "registry:component", + "target": "components/smithery/active-connection.tsx" + }, { "path": "registry/new-york/smithery/connections.tsx", "type": "registry:component",