diff --git a/src/extension/Makefile b/src/extension/Makefile
index 6a8335bb..a884fd8e 100644
--- a/src/extension/Makefile
+++ b/src/extension/Makefile
@@ -1,6 +1,6 @@
# IMAGE?=docker/labs-ai-tools-for-devs
IMAGE?=docker/labs-ai-tools-for-devs
-TAG?=0.2.34
+TAG?=0.2.35
BUILDER=buildx-multi-arch
@@ -18,10 +18,10 @@ build-extension: cross ## Build service image to be deployed as a desktop extens
docker buildx build --load --tag=$(IMAGE):$(TAG) .
install-extension: build-extension ## Install the extension
- docker extension install $(IMAGE):$(TAG)
+ echo y | docker extension install $(IMAGE):$(TAG)
update-extension: build-extension ## Update the extension
- docker extension update $(IMAGE):$(TAG)
+ echo y | docker extension update $(IMAGE):$(TAG)
prepare-buildx: ## Create buildx builder for multi-arch build, if not exists
docker buildx inspect $(BUILDER) || docker buildx create --name=$(BUILDER) --driver=docker-container --driver-opt=network=host
diff --git a/src/extension/host-binary/cmd/main.go b/src/extension/host-binary/cmd/main.go
index b42bc3e1..bb1d1e27 100644
--- a/src/extension/host-binary/cmd/main.go
+++ b/src/extension/host-binary/cmd/main.go
@@ -49,6 +49,10 @@ type addOptions struct {
Value string
}
+type deleteOptions struct {
+ Name string
+}
+
func AddSecret(ctx context.Context) *cobra.Command {
opts := &addOptions{}
cmd := &cobra.Command{
@@ -79,6 +83,19 @@ func ListSecrets(ctx context.Context) *cobra.Command {
return cmd
}
+func DeleteSecret(ctx context.Context) *cobra.Command {
+ opts := &deleteOptions{}
+ cmd := &cobra.Command{
+ Use: "delete",
+ Short: "Delete a secret",
+ Args: cobra.NoArgs,
+ }
+ flags := cmd.Flags()
+ flags.StringVarP(&opts.Name, "name", "n", "", "Name of the secret")
+ _ = cmd.MarkFlagRequired("name")
+ return cmd
+}
+
const mcpPolicyName = "MCP"
func runAddSecret(ctx context.Context, opts addOptions) error {
@@ -104,6 +121,14 @@ func runListSecrets(ctx context.Context) error {
return json.NewEncoder(os.Stdout).Encode(secrets)
}
+func runDeleteSecret(ctx context.Context, opts deleteOptions) error {
+ c, err := newApiClient()
+ if err != nil {
+ return err
+ }
+ return c.DeleteSecret(ctx, opts.Name)
+}
+
func assertMcpPolicyExists(ctx context.Context, apiClient client.ApiClient) error {
return apiClient.SetPolicy(ctx, secretsapi.Policy{Name: mcpPolicyName, Images: []string{"*"}})
}
diff --git a/src/extension/ui/src/App.tsx b/src/extension/ui/src/App.tsx
index d2c2a0c0..a4e0ca40 100644
--- a/src/extension/ui/src/App.tsx
+++ b/src/extension/ui/src/App.tsx
@@ -1,7 +1,7 @@
-import React, { useEffect, useState, Suspense } from 'react';
+import React, { useState, Suspense } from 'react';
import { createDockerDesktopClient } from '@docker/extension-api-client';
-import { Stack, Typography, Button, IconButton, Alert, DialogTitle, Dialog, DialogContent, CircularProgress, Paper, Box, SvgIcon, useTheme } from '@mui/material';
-import { CatalogItemWithName } from './components/tile/Tile';
+import { Typography, Button, IconButton, Alert, DialogTitle, Dialog, DialogContent, CircularProgress, Paper, Box, SvgIcon, useTheme } from '@mui/material';
+import { CatalogItemWithName } from './types/catalog';
import { Close } from '@mui/icons-material';
import { CatalogGrid } from './components/CatalogGrid';
import { POLL_INTERVAL } from './Constants';
@@ -9,28 +9,9 @@ import { CatalogProvider, useCatalogContext } from './context/CatalogContext';
import { ConfigProvider } from './context/ConfigContext';
import { MCPClientProvider, useMCPClientContext } from './context/MCPClientContext';
import ConfigurationModal from './components/ConfigurationModal';
-import { Settings as SettingsIcon } from '@mui/icons-material';
const Settings = React.lazy(() => import('./components/Settings'));
-// Create lazy-loaded logo components
-const LazyDarkLogo = React.lazy(() => import('./components/DarkLogo'));
-const LazyLightLogo = React.lazy(() => import('./components/LightLogo'));
-
-// Logo component that uses Suspense for conditional loading
-const Logo = () => {
- const theme = useTheme();
- const isDarkMode = theme.palette.mode === 'dark';
-
- return (
- }>
-
- {isDarkMode ? : }
-
-
- );
-}
-
export const client = createDockerDesktopClient();
const DEFAULT_SETTINGS = {
@@ -112,7 +93,7 @@ function AppContent({ settings, setSettings, configuringItem, setConfiguringItem
);
}
-
+ const isDataFetching = imagesIsFetching || secretsLoading || catalogLoading || registryLoading || mcpFetching;
return (
<>
{settings.showModal && (
@@ -147,39 +128,10 @@ function AppContent({ settings, setSettings, configuringItem, setConfiguringItem
/>
)}
- {/* Show a small loading indicator in the corner during background refetching */}
- {(imagesIsFetching || secretsLoading || catalogLoading || registryLoading || mcpFetching) && (
-
-
- Refreshing data...
-
- )}
-
-
-
-
- setSettings({ ...settings, showModal: true })}>
-
-
-
- setSettings({ ...settings, showModal: true })}
- />
-
+ setSettings({ ...settings, showModal: true })}
+ />
>
);
}
\ No newline at end of file
diff --git a/src/extension/ui/src/Constants.ts b/src/extension/ui/src/Constants.ts
index 627ce788..ed32f190 100644
--- a/src/extension/ui/src/Constants.ts
+++ b/src/extension/ui/src/Constants.ts
@@ -1,11 +1,18 @@
export const POLL_INTERVAL = 1000 * 30;
export const MCP_POLICY_NAME = 'MCP=*';
export const DD_BUILD_WITH_SECRET_SUPPORT = 184396;
-export const CATALOG_URL = 'https://raw.githubusercontent.com/docker/labs-ai-tools-for-devs/refs/heads/main/prompts/catalog.yaml'
+export const CATALOG_URL = localStorage.getItem('catalogUrl') || 'https://raw.githubusercontent.com/docker/labs-ai-tools-for-devs/refs/heads/main/prompts/catalog.yaml'
+
+export const getUnsupportedSecretMessage = (ddVersion: { version: string, build: number }) =>
+ `Secret support is not available in this version of Docker Desktop. You are on version ${ddVersion.version}, but the minimum required version is 4.40.0.`
-export const getUnsupportedSecretMessage = (ddVersion: { version: string, build: number }) => {
- return `Secret support is not available in this version of Docker Desktop. You are on version ${ddVersion.version}, but the minimum required version is 4.40.0.`
-}
export const DOCKER_MCP_IMAGE = 'alpine/socat'
export const DOCKER_MCP_CONTAINER_ARGS = 'STDIO TCP:host.docker.internal:8811'
-export const DOCKER_MCP_COMMAND = `docker run -i --rm ${DOCKER_MCP_IMAGE} ${DOCKER_MCP_CONTAINER_ARGS}`
\ No newline at end of file
+export const DOCKER_MCP_COMMAND = `docker run -i --rm ${DOCKER_MCP_IMAGE} ${DOCKER_MCP_CONTAINER_ARGS}`
+
+export const TILE_DESCRIPTION_MAX_LENGTH = 80;
+
+export const CATALOG_LAYOUT_SX = {
+ width: '90vw',
+ maxWidth: '1200px',
+}
\ No newline at end of file
diff --git a/src/extension/ui/src/MCP Catalog_dark.svg b/src/extension/ui/src/MCP Catalog_dark.svg
deleted file mode 100644
index ea69780e..00000000
--- a/src/extension/ui/src/MCP Catalog_dark.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/extension/ui/src/MCP Catalog_light.svg b/src/extension/ui/src/MCP Catalog_light.svg
deleted file mode 100644
index 18cbb925..00000000
--- a/src/extension/ui/src/MCP Catalog_light.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/extension/ui/src/MCPClients.ts b/src/extension/ui/src/MCPClients.ts
index a282f3fb..1f802d6d 100644
--- a/src/extension/ui/src/MCPClients.ts
+++ b/src/extension/ui/src/MCPClients.ts
@@ -1,6 +1,6 @@
import { v1 } from "@docker/extension-api-client-types";
import { SUPPORTED_MCP_CLIENTS } from "./mcp-clients";
-import { MCPClient } from "./mcp-clients/MCPTypes";
+import { MCPClient } from "./types/mcp";
export type MCPClientState = {
client: MCPClient;
diff --git a/src/extension/ui/src/MergeDeep.ts b/src/extension/ui/src/MergeDeep.ts
index ba74f63f..d91ae53a 100644
--- a/src/extension/ui/src/MergeDeep.ts
+++ b/src/extension/ui/src/MergeDeep.ts
@@ -1,7 +1,4 @@
-/**
- * Type for any object with string keys
- */
-export type DeepObject = { [key: string]: any };
+import { DeepObject } from './types/utils';
/**
* Simple object check.
diff --git a/src/extension/ui/src/Registry.ts b/src/extension/ui/src/Registry.ts
index 0b403b76..331841dd 100644
--- a/src/extension/ui/src/Registry.ts
+++ b/src/extension/ui/src/Registry.ts
@@ -1,8 +1,8 @@
import { v1 } from "@docker/extension-api-client-types";
import { parse, stringify } from "yaml";
import { readFileInPromptsVolume, writeFileToPromptsVolume } from "./FileWatcher";
-import { ParsedParameters } from "./components/ConfigurationModal";
import { mergeDeep } from "./MergeDeep";
+import { ParsedParameters } from "./types/config";
export const getRegistry = async (client: v1.DockerDesktopClient) => {
const parseRegistry = async () => {
diff --git a/src/extension/ui/src/Secrets.ts b/src/extension/ui/src/Secrets.ts
index 18fcfef5..003539ea 100644
--- a/src/extension/ui/src/Secrets.ts
+++ b/src/extension/ui/src/Secrets.ts
@@ -1,25 +1,10 @@
// From secrets.yaml
import { v1 } from "@docker/extension-api-client-types";
-import { CatalogItemWithName } from "./components/tile/Tile";
+import { CatalogItemWithName } from "./types/catalog";
+import { Secret, StoredSecret, Policy } from "./types/secrets";
namespace Secrets {
- export type Secret = {
- name: string;
- value: string;
- policies: string[];
- }
-
- export type StoredSecret = {
- name: string;
- policies: string[];
- }
-
- export type Policy = {
- name: string;
- images: string[];
- }
-
export async function getSecrets(client: v1.DockerDesktopClient): Promise {
const response = await client.extension.host?.cli.exec('host-binary', ['list']);
if (!response) {
diff --git a/src/extension/ui/src/Usage.tsx b/src/extension/ui/src/Usage.tsx
index 5e732d59..657bdac9 100644
--- a/src/extension/ui/src/Usage.tsx
+++ b/src/extension/ui/src/Usage.tsx
@@ -1,6 +1,7 @@
/**
* Anonymous tracking event for registry changes
*/
+import { RegistryChangedRecord, ClaudeConfigChangedRecord } from './types/config';
interface Record {
event: string;
@@ -9,28 +10,12 @@ interface Record {
source: string;
};
-export interface RegistryChangedRecord extends Record {
- event: 'registry-changed';
- properties: {
- name: string;
- ref: string;
- action: 'remove' | 'add';
- };
-};
-
-export interface ClaudeConfigChangedRecord extends Record {
- event: 'claude-config-changed';
- properties: {
- action: 'add' | 'remove' | 'write' | 'delete';
- };
-};
-
const eventsQueue: Record[] = [];
let processInterval: NodeJS.Timeout;
-type Event = (RegistryChangedRecord | ClaudeConfigChangedRecord)['event'];
-type Properties = (RegistryChangedRecord | ClaudeConfigChangedRecord)['properties'];
+type Event = 'registry-changed' | 'claude-config-changed';
+type Properties = RegistryChangedRecord['properties'] | ClaudeConfigChangedRecord['properties'];
export const trackEvent = (event: Event, properties: Properties) => {
const record: Record = {
diff --git a/src/extension/ui/src/chatgpt.svg b/src/extension/ui/src/assets/chatgpt.svg
similarity index 100%
rename from src/extension/ui/src/chatgpt.svg
rename to src/extension/ui/src/assets/chatgpt.svg
diff --git a/src/extension/ui/src/claude-ai-icon.svg b/src/extension/ui/src/assets/claude-ai-icon.svg
similarity index 100%
rename from src/extension/ui/src/claude-ai-icon.svg
rename to src/extension/ui/src/assets/claude-ai-icon.svg
diff --git a/src/extension/ui/src/cursor-color.svg b/src/extension/ui/src/assets/cursor-color.svg
similarity index 100%
rename from src/extension/ui/src/cursor-color.svg
rename to src/extension/ui/src/assets/cursor-color.svg
diff --git a/src/extension/ui/src/cursor.svg b/src/extension/ui/src/assets/cursor.svg
similarity index 100%
rename from src/extension/ui/src/cursor.svg
rename to src/extension/ui/src/assets/cursor.svg
diff --git a/src/extension/ui/src/gordon-icon.png b/src/extension/ui/src/assets/gordon-icon.png
similarity index 100%
rename from src/extension/ui/src/gordon-icon.png
rename to src/extension/ui/src/assets/gordon-icon.png
diff --git a/src/extension/ui/src/gordon.png b/src/extension/ui/src/assets/gordon.png
similarity index 100%
rename from src/extension/ui/src/gordon.png
rename to src/extension/ui/src/assets/gordon.png
diff --git a/src/extension/ui/src/assets/toolbox.svg b/src/extension/ui/src/assets/toolbox.svg
new file mode 100644
index 00000000..c853abda
--- /dev/null
+++ b/src/extension/ui/src/assets/toolbox.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/extension/ui/src/components/CatalogGrid.tsx b/src/extension/ui/src/components/CatalogGrid.tsx
index 88d0e26f..63a28aec 100644
--- a/src/extension/ui/src/components/CatalogGrid.tsx
+++ b/src/extension/ui/src/components/CatalogGrid.tsx
@@ -1,13 +1,14 @@
import React, { Suspense, useEffect, useState } from 'react';
-import { IconButton, Alert, AlertTitle, Stack, Button, Typography, FormGroup, FormControlLabel, Dialog, DialogTitle, DialogContent, Checkbox, Badge, BadgeProps, Link, TextField, Tabs, Tab, Tooltip, CircularProgress, Box, Select, MenuItem, Menu, Divider, Icon } from '@mui/material';
-import { CatalogItemWithName } from './tile/Tile';
-import { Archive, ArrowDropDown, Edit, FileCopy, FolderOpenRounded, MoreHoriz, Search, Settings, Sort, SortByAlpha, SwapVert } from '@mui/icons-material';
+import { IconButton, Alert, AlertTitle, Stack, Button, Typography, FormGroup, FormControlLabel, Dialog, DialogTitle, DialogContent, Checkbox, Badge, TextField, Tabs, Tab, CircularProgress, Box, Menu, Divider, Switch, MenuItem } from '@mui/material';
+import { SwapVert, FolderOpenRounded } from '@mui/icons-material';
import { useCatalogContext } from '../context/CatalogContext';
import { useMCPClientContext } from '../context/MCPClientContext';
import { useConfigContext } from '../context/ConfigContext';
import { createDockerDesktopClient } from '@docker/extension-api-client';
import { ExecResult } from '@docker/extension-api-client-types/dist/v0';
import YourClients from './tabs/YourClients';
+import { CatalogItemWithName } from '../types/catalog';
+import { CATALOG_LAYOUT_SX } from '../Constants';
const ToolCatalog = React.lazy(() => import('./tabs/ToolCatalog'));
const YourTools = React.lazy(() => import('./tabs/YourTools'));
@@ -32,7 +33,6 @@ const parseDDVersion = (ddVersion: string) => {
const NEVER_SHOW_AGAIN_KEY = 'registry-sync-never-show-again';
export const CatalogGrid: React.FC = ({
- showSettings,
setConfiguringItem,
}) => {
const {
@@ -47,9 +47,6 @@ export const CatalogGrid: React.FC = ({
const {
mcpClientStates,
- buttonsLoading,
- setButtonsLoading,
- updateMCPClientStates,
isLoading: mcpLoading
} = useMCPClientContext();
@@ -65,6 +62,7 @@ export const CatalogGrid: React.FC = ({
'demo-customized-menu': { anchorEl: null, open: false }
});
const [sort, setSort] = useState<'name-asc' | 'name-desc' | 'date-asc' | 'date-desc'>('date-desc');
+ const [showMine, setShowMine] = useState(false);
const loadDDVersion = async () => {
try {
@@ -110,7 +108,7 @@ export const CatalogGrid: React.FC = ({
const noConfiguredClients = !mcpLoading && !Object.values(mcpClientStates || {}).some(state => state.exists && state.configured);
return (
-
+ <>
- {hasOutOfCatalog && } variant='outlined' color='secondary' onClick={() => {
- client.desktopUI.navigate.viewVolume('docker-prompts')
- }}>registry.yaml} severity="info">
- You have some prompts registered which are not available in the catalog.
- }
- {noConfiguredClients &&
-
- No configured clients detected. Please configure at least one client in the Clients tab.
-
- }
-
- setTab(newValue)} sx={{ width: '90vw', maxWidth: '1000px' }}>
-
-
-
-
-
-
-
-
-
-
-
-
-
- {tab < 2 &&
-
-
- setSearch(e.target.value)} />
-
- {/* Select dropdown icon */}
-
- setOpenMenus({ ...openMenus, 'demo-customized-menu': { anchorEl: e.currentTarget, open: !openMenus['demo-customized-menu'].open } })}
- >
-
-
- }>
- {tab === 0 && (
-
- )}
- {tab === 1 && (
+
+
+
+
+
+ }
+
+
+ }>
+ {tab === 0 && (
+
+ )}
+ {/* {tab === 1 && (
= ({
ddVersion={ddVersion}
config={config || {}}
/>
- )}
- {tab === 2 && ddVersion && (
-
- )}
- {tab === 3 && (
-
- )}
-
-
+ )} */}
+ {tab === 2 && ddVersion && (
+
+ )}
+ {tab === 1 && (
+
+ )}
+
+
+ >
);
};
diff --git a/src/extension/ui/src/components/ConfigurationModal.tsx b/src/extension/ui/src/components/ConfigurationModal.tsx
index ad683dba..bff4b0b4 100644
--- a/src/extension/ui/src/components/ConfigurationModal.tsx
+++ b/src/extension/ui/src/components/ConfigurationModal.tsx
@@ -1,13 +1,15 @@
import { Badge, Box, CircularProgress, Dialog, DialogContent, DialogTitle, IconButton, Stack, Tab, Tabs, TextField, Typography, useTheme } from "@mui/material";
import { LockReset, Save } from "@mui/icons-material";
import { useEffect, useState } from "react";
-import { CatalogItemWithName } from "./tile/Tile";
+import { CatalogItemWithName } from "../types/catalog";
import Secrets from "../Secrets";
import { v1 } from "@docker/extension-api-client-types";
import { githubLightTheme, NodeData, githubDarkTheme, JsonEditor } from "json-edit-react";
import { useCatalogContext } from "../context/CatalogContext";
import { useConfigContext } from "../context/ConfigContext";
-import { DeepObject, mergeDeep } from "../MergeDeep";
+import { mergeDeep } from "../MergeDeep";
+import { DeepObject } from "../types/utils";
+import { Parameter, ParameterArray, ParameterObject, Parameters, ParsedParameter, ParsedParameterArray, ParsedParameterObject, ParsedParameters, Config } from "../types/config";
// Styles for the tab panel
interface TabPanelProps {
@@ -37,41 +39,9 @@ function TabPanel(props: TabPanelProps) {
);
}
-// Define types (moved from PromptConfig.tsx)
+// Define types reference
const types = ['string', 'number', 'boolean', 'array', 'object'] as const;
-export type Parameter = {
- type?: typeof types[number];
- description?: string;
-};
-
-export type ParameterArray = {
- type: 'array';
- items: Parameter;
- description?: string;
-};
-
-export type ParameterObject = {
- type: 'object';
- properties: {
- [key: string]: Parameter;
- };
- description?: string;
-};
-
-export type Parameters = Parameter | ParameterArray | ParameterObject;
-
-export type ParsedParameter = any;
-export type ParsedParameterArray = any[];
-export type ParsedParameterObject = Record;
-export type ParsedParameters = any;
-
-export type Config = {
- name: string;
- description: string;
- parameters: Parameters
-}[];
-
// Given a path in parsed JSON, returns the type of the value at that path from the tile's config
const jsonEditorTypeFilterFunction = ({ path }: NodeData, config: Config[number]['parameters']) => {
if (path.length === 0) {
@@ -264,7 +234,7 @@ const ConfigurationModal = ({
{/* Configuration Tab */}
{hasConfig && (
- {catalogItem.config!.map(config => (
+ {catalogItem.config!.map((config: Config[number]) => (
{
- return
;
-};
-
-export default DarkLogo;
\ No newline at end of file
diff --git a/src/extension/ui/src/components/LightLogo.tsx b/src/extension/ui/src/components/LightLogo.tsx
deleted file mode 100644
index 2a2f97db..00000000
--- a/src/extension/ui/src/components/LightLogo.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-import React from 'react';
-import LightLogoSvg from '../MCP Catalog_light.svg';
-
-const LightLogo: React.FC = () => {
- return
;
-};
-
-export default LightLogo;
\ No newline at end of file
diff --git a/src/extension/ui/src/components/tabs/ToolCatalog.tsx b/src/extension/ui/src/components/tabs/ToolCatalog.tsx
index 8108516d..f089699d 100644
--- a/src/extension/ui/src/components/tabs/ToolCatalog.tsx
+++ b/src/extension/ui/src/components/tabs/ToolCatalog.tsx
@@ -1,12 +1,12 @@
import React from 'react';
import { Card, CardContent, Grid2, IconButton } from '@mui/material';
-import Tile, { CatalogItemWithName } from '../tile/Tile';
+import Tile from '../tile/Index';
import AddIcon from '@mui/icons-material/Add';
-import { Ref } from '../../Refs';
import { v1 } from "@docker/extension-api-client-types";
-import Secrets from '../../Secrets';
-import TileActions from '../tile/TileActions';
+import { CatalogItemWithName } from '../../types/catalog';
+import { Secret } from '../../types/secrets';
import { useCatalogContext } from '../../context/CatalogContext';
+import { CATALOG_LAYOUT_SX } from '../../Constants';
interface ToolCatalogProps {
search: string;
@@ -17,22 +17,26 @@ interface ToolCatalogProps {
register: (item: CatalogItemWithName) => Promise;
unregister: (item: CatalogItemWithName) => Promise;
onSecretChange: (secret: { name: string, value: string }) => Promise;
- secrets: Secrets.Secret[];
+ secrets: Secret[];
registryItems: { [key: string]: { ref: string, config: any } };
setConfiguringItem: (item: CatalogItemWithName) => void;
config: { [key: string]: { [key: string]: any } };
+ showMine: boolean;
}
-const ToolCatalog: React.FC = ({ config, setConfiguringItem, search, catalogItems, client, ddVersion, canRegister, register, unregister, onSecretChange, secrets, registryItems }) => {
- const filteredCatalogItems = catalogItems.filter(item =>
- item.name.toLowerCase().includes(search.toLowerCase())
- );
+const ToolCatalog: React.FC = ({ config, search, catalogItems, client, onSecretChange, secrets, registryItems, showMine }) => {
+
+ const filteredCatalogItems = catalogItems.filter(item => {
+ const isRegistered = registryItems[item.name]?.ref !== undefined;
+ const matchesSearch = item.name.toLowerCase().includes(search.toLowerCase());
+ const hideBecauseItsNotMine = showMine && !isRegistered;
+ return matchesSearch && !hideBecauseItsNotMine;
+ });
return (
-
+
{filteredCatalogItems.map((catalogItem) => {
const expectedKeys = catalogItem.config?.map((c: any) => c.name) || [];
- const hasAllConfig = !catalogItem.config || expectedKeys?.every((c: any) => config[catalogItem.name]?.[c] !== undefined);
const unAssignedConfig = expectedKeys?.filter((c: any) => config[catalogItem.name]?.[c] === undefined);
return (
@@ -41,18 +45,8 @@ const ToolCatalog: React.FC = ({ config, setConfiguringItem, s
registered={registryItems[catalogItem.name]?.ref !== undefined}
onSecretChange={onSecretChange}
secrets={secrets}
- ActionsSlot={}
+ unAssignedConfig={unAssignedConfig}
+ client={client}
/>
)
diff --git a/src/extension/ui/src/components/tabs/YourClients.tsx b/src/extension/ui/src/components/tabs/YourClients.tsx
index 4ef5d6e7..af67bb48 100644
--- a/src/extension/ui/src/components/tabs/YourClients.tsx
+++ b/src/extension/ui/src/components/tabs/YourClients.tsx
@@ -1,16 +1,14 @@
-import { Chip, ListItem, ListItemText, List, Button, Tooltip, CircularProgress, Stack, Typography, Link, AlertTitle, Divider, AccordionSummary, Accordion, AccordionDetails } from "@mui/material";
-import { IconButton } from "@mui/material";
-import { Alert } from "@mui/material";
+import { Chip, ListItem, ListItemText, List, Button, Tooltip, CircularProgress, Typography, Link, Divider, AccordionSummary, Accordion, AccordionDetails, Stack } from "@mui/material";
import { Box } from "@mui/material";
-import { DOCKER_MCP_COMMAND } from "../../Constants";
-import { ContentCopy, LinkOff, LinkRounded, SaveOutlined } from "@mui/icons-material";
-import { MCPClientState } from "../../MCPClients";
+import { DOCKER_MCP_COMMAND, CATALOG_LAYOUT_SX } from "../../Constants";
+import { LinkOff, LinkRounded, SaveOutlined } from "@mui/icons-material";
import { v1 } from "@docker/extension-api-client-types";
-import ClaudeIcon from '../../claude-ai-icon.svg'
-import GordonIcon from '../../gordon-icon.png'
-import CursorIcon from '../../cursor.svg'
-import ChatGPTIcon from '../../chatgpt.svg'
+import ClaudeIcon from '../../assets/claude-ai-icon.svg'
+import GordonIcon from '../../assets/gordon-icon.png'
+import CursorIcon from '../../assets/cursor.svg'
+import ChatGPTIcon from '../../assets/chatgpt.svg'
import { useMCPClientContext } from "../../context/MCPClientContext";
+import { useState } from "react";
type MCPClientSettingsProps = {
client: v1.DockerDesktopClient;
@@ -40,19 +38,26 @@ const MCPClientSettings = ({ client }: MCPClientSettingsProps) => {
return ;
}
+ const [copyButtonText, setCopyButtonText] = useState('Copy');
+
return (
-
- MCP Clients
+
+ Connect to runtimes for your tools
{Object.entries(mcpClientStates).map(([name, mcpClientState]) => (
-
-
- {iconMap[name as keyof typeof iconMap] &&
}
- {name}
- {!mcpClientState.exists && }
- {mcpClientState.exists && }
+
+ {iconMap[name as keyof typeof iconMap] &&
}
+
+
+ {name}
+ {!mcpClientState.exists && }
+ {mcpClientState.exists && }
+
+
+ Connect MCP to {name}
+
@@ -61,7 +66,6 @@ const MCPClientSettings = ({ client }: MCPClientSettingsProps) => {
client.host.openExternal(mcpClientState.client.url)}>{mcpClientState.client.url}
-
Expected Config Path:
{mcpClientState.client.expectedConfigPath?.[client.host.platform as 'win32' | 'darwin' | 'linux'] || 'N/A'}
@@ -173,19 +177,24 @@ const MCPClientSettings = ({ client }: MCPClientSettingsProps) => {
-
-
- Other MCP Clients
+ or
+
+ Other MCP Clients
You can connect other MCP clients to the same server by specifying the following command:
-
- navigator.clipboard.writeText(DOCKER_MCP_COMMAND)}>
-
-
+
+
({ backgroundColor: theme.palette.mode === 'dark' ? theme.palette.background.default : theme.palette.grey[200], padding: 1, borderRadius: 1, fontFamily: 'monospace', whiteSpace: 'nowrap', overflow: 'auto', color: 'text.primary' })}>
{DOCKER_MCP_COMMAND}
+
-
+
);
};
diff --git a/src/extension/ui/src/components/tabs/YourEnvironment.tsx b/src/extension/ui/src/components/tabs/YourEnvironment.tsx
index 673d31b1..5b867d9c 100644
--- a/src/extension/ui/src/components/tabs/YourEnvironment.tsx
+++ b/src/extension/ui/src/components/tabs/YourEnvironment.tsx
@@ -1,10 +1,10 @@
import React from 'react';
import { Alert, Box, Card, CardContent, CardHeader, Grid2, List, ListItem, ListItemText, Stack, Typography } from '@mui/material';
-import Secrets from '../../Secrets';
-import { DD_BUILD_WITH_SECRET_SUPPORT, getUnsupportedSecretMessage } from '../../Constants';
+import { CATALOG_LAYOUT_SX, DD_BUILD_WITH_SECRET_SUPPORT, getUnsupportedSecretMessage } from '../../Constants';
+import { Secret } from '../../types/secrets';
interface YourEnvironmentProps {
- secrets: Secrets.Secret[];
+ secrets: Secret[];
ddVersion: { version: string, build: number };
config: { [key: string]: { [key: string]: any } };
}
@@ -24,7 +24,7 @@ const YourEnvironment: React.FC = ({ secrets, ddVersion, c
The following secrets are available to use in your prompts:
-
+
Docker Secret Management is a new feature in Docker Desktop that allows you to securely inject secrets into your containers. Only the tools which need to access the secrets will be able to access them.
diff --git a/src/extension/ui/src/components/tabs/YourTools.tsx b/src/extension/ui/src/components/tabs/YourTools.tsx
index 0c5f138c..7f97c9c2 100644
--- a/src/extension/ui/src/components/tabs/YourTools.tsx
+++ b/src/extension/ui/src/components/tabs/YourTools.tsx
@@ -1,12 +1,12 @@
import React from 'react';
import { Alert, AlertTitle, Grid2 } from '@mui/material';
-import Tile, { CatalogItemWithName } from '../tile/Tile';
-import Secrets from '../../Secrets';
-import { MCP_POLICY_NAME } from '../../Constants';
-import TileActions from '../tile/TileActions';
+import Tile from '../tile/Index';
+import { CATALOG_LAYOUT_SX, MCP_POLICY_NAME } from '../../Constants';
+import TileActions from '../tile/Bottom';
import { v1 } from '@docker/extension-api-client-types';
import { createDockerDesktopClient } from '@docker/extension-api-client';
-
+import { CatalogItemWithName } from '../../types/catalog';
+import { Secret } from '../../types/secrets';
// Initialize the Docker Desktop client
const client = createDockerDesktopClient();
@@ -17,7 +17,7 @@ interface YourToolsProps {
canRegister: boolean;
unregister: (item: CatalogItemWithName) => Promise;
setConfiguringItem: (item: CatalogItemWithName) => void;
- secrets: Secrets.Secret[];
+ secrets: Secret[];
catalogItems: CatalogItemWithName[];
onSecretChange: (secret: { name: string, value: string }) => Promise;
ddVersion: { version: string, build: number };
@@ -36,7 +36,7 @@ const YourTools: React.FC = ({
config
}) => {
return (
-
+
{Object.entries(registryItems).map(([name, item]) => {
if (!name.toLowerCase().includes(search.toLowerCase())) return null;
const catalogItem = catalogItems.find(c => c.name === name);
@@ -53,20 +53,10 @@ const YourTools: React.FC = ({
{ }}
+ onSecretChange={onSecretChange}
secrets={secrets}
- ActionsSlot={ Promise.resolve()}
- unregister={unregister}
- onSecretChange={onSecretChange}
- secrets={secrets}
- canRegister={canRegister}
- unAssignedConfig={[]}
- />}
+ client={client}
+ unAssignedConfig={unassignedConfig}
/>
);
diff --git a/src/extension/ui/src/components/tile/Bottom.tsx b/src/extension/ui/src/components/tile/Bottom.tsx
new file mode 100644
index 00000000..7dd74d46
--- /dev/null
+++ b/src/extension/ui/src/components/tile/Bottom.tsx
@@ -0,0 +1,30 @@
+import { Box, Chip, Stack, Typography } from "@mui/material";
+import { CatalogItem } from "../../types/catalog";
+import { Hardware, WarningAmberOutlined } from "@mui/icons-material";
+
+type BottomProps = {
+ item: CatalogItem,
+ needsConfiguration: boolean
+}
+
+const Bottom = ({ item, needsConfiguration }: BottomProps) => {
+ return (
+
+ {
+
+ {`${(item.tools || []).length || 1} tool` + ((item.tools || []).length || 1 !== 1 ? 's' : '')}
+
+
+ } color="primary" />}
+ {!item.tools?.length && !!item.prompts && }
+ {!item.tools?.length && !item.prompts && item.resources?.length && }
+ {needsConfiguration &&
+ Needs Configuration
+ }
+
+
+ )
+}
+
+export default Bottom;
\ No newline at end of file
diff --git a/src/extension/ui/src/components/tile/Center.tsx b/src/extension/ui/src/components/tile/Center.tsx
new file mode 100644
index 00000000..22c03c6d
--- /dev/null
+++ b/src/extension/ui/src/components/tile/Center.tsx
@@ -0,0 +1,18 @@
+import { Tooltip, Typography } from "@mui/material";
+import { CatalogItemWithName } from "../../types"
+import { TILE_DESCRIPTION_MAX_LENGTH } from "../../Constants";
+
+type CenterProps = {
+ item: CatalogItemWithName;
+}
+
+export default function Center({ item }: CenterProps) {
+ return (
+
+
+ {item.description?.slice(0, TILE_DESCRIPTION_MAX_LENGTH)}
+ {item.description?.length && item.description.length > TILE_DESCRIPTION_MAX_LENGTH && '...'}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/extension/ui/src/components/tile/Tile.tsx b/src/extension/ui/src/components/tile/Index.tsx
similarity index 55%
rename from src/extension/ui/src/components/tile/Tile.tsx
rename to src/extension/ui/src/components/tile/Index.tsx
index 44097d25..66cec0d3 100644
--- a/src/extension/ui/src/components/tile/Tile.tsx
+++ b/src/extension/ui/src/components/tile/Index.tsx
@@ -1,35 +1,28 @@
-import { CircularProgress, Dialog, DialogContent, DialogTitle, IconButton, Paper, Stack, TextField, Tooltip, Typography } from "@mui/material";
-import { Card, CardActions, CardContent, CardMedia } from "@mui/material";
-import { ReactNode, useEffect, useState } from "react";
-import Secrets from "../../Secrets";
-import { Config } from "../ConfigurationModal";
+import { CircularProgress, Dialog, DialogContent, DialogTitle, Divider, IconButton, Stack, TextField, Typography } from "@mui/material";
+import { Card, CardContent } from "@mui/material";
+import { useEffect, useState } from "react";
+import { CatalogItemWithName } from "../../types/catalog";
import { Save, LockReset } from "@mui/icons-material";
+import Secrets from "../../Secrets";
+import ConfigurationModal from "../ConfigurationModal";
+import { useCatalogContext } from "../../context/CatalogContext";
+import Top from "./Top";
+import Center from "./Center";
+import Bottom from "./Bottom";
+import { Secret } from "../../types";
+import { v1 } from "@docker/extension-api-client-types";
-export interface CatalogItem {
- description?: string;
- icon?: string;
- secrets?: { name: string }[];
- ref: string;
- prompts: number;
- resources: object[];
- tools: object[];
- config?: Config;
-}
-
-export interface CatalogItemWithName extends CatalogItem {
- name: string;
-}
-
-export interface TileProps {
+type TileProps = {
item: CatalogItemWithName;
registered: boolean;
onSecretChange: (secret: { name: string, value: string }) => Promise;
- secrets: Secrets.Secret[];
- ActionsSlot: ReactNode
+ secrets: Secret[];
+ client: v1.DockerDesktopClient;
+ unAssignedConfig: { name: string; assigned: boolean }[];
}
-const Tile = ({ item, registered, onSecretChange, secrets, ActionsSlot }: TileProps) => {
+const Tile = ({ item, registered, onSecretChange, secrets, client, unAssignedConfig }: TileProps) => {
const loadAssignedSecrets = () => {
const assignedSecrets = Secrets.getAssignedSecrets(item, secrets);
setAssignedSecrets(assignedSecrets)
@@ -40,6 +33,20 @@ const Tile = ({ item, registered, onSecretChange, secrets, ActionsSlot }: TilePr
const [changedSecrets, setChangedSecrets] = useState<{ [key: string]: string | undefined }>({})
const [secretLoading, setSecretLoading] = useState(false)
+ const { registryLoading } = useCatalogContext()
+ const { registerCatalogItem, unregisterCatalogItem } = useCatalogContext();
+ const [showConfigModal, setShowConfigModal] = useState(false)
+
+ useEffect(() => {
+ loadAssignedSecrets()
+ }, [secrets])
+
+ if (registryLoading) {
+ return
+ }
+
+ const unAssignedSecrets = assignedSecrets.filter(s => !s.assigned)
+
useEffect(() => {
loadAssignedSecrets()
}, [secrets])
@@ -80,32 +87,31 @@ const Tile = ({ item, registered, onSecretChange, secrets, ActionsSlot }: TilePr
-
-
-
-
-
-
-
- {item.name}
-
-
-
-
- {item.description?.slice(0, 70)}...
-
-
-
-
-
- {ActionsSlot}
-
-
+ setShowConfigModal(false)}
+ catalogItem={item}
+ client={client}
+ />
+ {
+ if ((e.target as HTMLElement).tagName !== 'INPUT') {
+ setShowConfigModal(true)
+ }
+ }} sx={{ height: 140, borderColor: 'divider', borderWidth: 1, borderStyle: 'solid', p: 0, cursor: 'pointer', transition: 'background-color 0.1s ease', '&:hover': { backgroundColor: 'action.hover' } }} >
+
+
+ {
+ if (checked) {
+ registerCatalogItem(item)
+ } else {
+ unregisterCatalogItem(item)
+ }
+ }} item={item} unAssignedConfig={unAssignedConfig} unAssignedSecrets={unAssignedSecrets} registered={registered} />
+
+
+
+
+
>
)
diff --git a/src/extension/ui/src/components/tile/TileActions.tsx b/src/extension/ui/src/components/tile/TileActions.tsx
deleted file mode 100644
index 07c8b29e..00000000
--- a/src/extension/ui/src/components/tile/TileActions.tsx
+++ /dev/null
@@ -1,170 +0,0 @@
-import { Alert, Badge, Box, Chip, CircularProgress, IconButton, Stack, Switch, Tooltip, useTheme } from "@mui/material";
-import { Article, AttachFile, Build, Settings } from "@mui/icons-material";
-import { useEffect, useState } from "react";
-import Secrets from "../../Secrets";
-import { DD_BUILD_WITH_SECRET_SUPPORT, getUnsupportedSecretMessage } from "../../Constants";
-import { Config } from "../ConfigurationModal";
-import { trackEvent } from "../../Usage";
-import ConfigurationModal from "../ConfigurationModal";
-import { v1 } from "@docker/extension-api-client-types";
-import { createDockerDesktopClient } from "@docker/extension-api-client";
-import { useCatalogContext } from "../../context/CatalogContext";
-
-const iconSize = 16;
-
-// Initialize the Docker Desktop client
-const client = createDockerDesktopClient();
-
-export interface CatalogItem {
- description?: string;
- icon?: string;
- secrets?: { name: string }[];
- ref: string;
- prompts: number;
- resources: object[];
- tools: object[];
- config?: Config;
-}
-
-export interface CatalogItemWithName extends CatalogItem {
- name: string;
-}
-
-export interface TileActionsProps {
- setConfiguringItem: (item: CatalogItemWithName) => void;
- item: CatalogItemWithName; // Associated CatalogItemWithName
- canRegister: boolean;
- registered: boolean;
- register: (item: CatalogItemWithName) => Promise;
- unregister: (item: CatalogItemWithName) => Promise;
- onSecretChange: (secret: { name: string, value: string }) => Promise;
- secrets: Secrets.Secret[];
- ddVersion: { version: string, build: number };
- unAssignedConfig: { name: string, assigned: boolean }[];
-}
-
-const TileConfigBadge = ({ children, unAssignedConfig, unAssignedSecrets }: { children: React.ReactNode, unAssignedConfig: { name: string, assigned: boolean }[], unAssignedSecrets: { name: string, assigned: boolean }[] }) => {
- if (unAssignedConfig.length > 0) {
- return
- {children}
-
- }
- if (unAssignedSecrets.length > 0) {
- return
- {children}
-
- }
- return <>{children}>;
-}
-
-const TileActions = ({ item, registered, register, unregister, onSecretChange, secrets, ddVersion, unAssignedConfig }: TileActionsProps) => {
- const loadAssignedSecrets = () => {
- const assignedSecrets = Secrets.getAssignedSecrets(item, secrets);
- setAssignedSecrets(assignedSecrets)
- }
-
- const { registryLoading } = useCatalogContext()
-
- const [isRegistering, setIsRegistering] = useState(false)
- const [localRegistered, setLocalRegistered] = useState(registered)
- const [showConfigModal, setShowConfigModal] = useState(false)
- const [assignedSecrets, setAssignedSecrets] = useState<{ name: string, assigned: boolean }[]>([])
-
- useEffect(() => {
- setLocalRegistered(registered)
- }, [registered])
-
- useEffect(() => {
- loadAssignedSecrets()
- }, [secrets])
-
- if (registryLoading) {
- return
- }
-
- const unAssignedSecrets = assignedSecrets.filter(s => !s.assigned)
-
- const hasAllSecrets = unAssignedSecrets.length === 0
-
- const hasDDVersionWithSecretSupport = ddVersion && ddVersion.build >= DD_BUILD_WITH_SECRET_SUPPORT;
-
- const canBeConfigured = assignedSecrets.length > 0 || item.config
-
- const hasAllConfig = unAssignedConfig.length === 0
-
- const getActionButton = () => {
-
- if (!hasAllSecrets || !hasAllConfig) {
- return
-
- setShowConfigModal(true)}>
-
-
-
-
-
-
-
-
-
-
-
- }
- return
- {canBeConfigured &&
- {
- setShowConfigModal(true)
- }}>
-
-
- }
-
- {
- setIsRegistering(true)
- setLocalRegistered(checked)
-
- try {
- if (checked) {
- await register(item)
- } else {
- await unregister(item)
- }
- } catch (error) {
- // If operation fails, revert the local state
- setLocalRegistered(!checked)
- } finally {
- setIsRegistering(false)
- }
- }}
- />
-
-
- }
- return (
- <>
- {/* Use the new ConfigurationModal component */}
- setShowConfigModal(false)}
- catalogItem={item}
- client={client}
- />
-
-
-
- {!!item.tools?.length && 1 ? 's' : '')} color="primary" />}
- {!item.tools?.length && !!item.prompts && }
- {!item.tools?.length && !item.prompts && item.resources?.length && }
-
-
- {getActionButton()}
-
-
- >
- )
-}
-
-export default TileActions;
\ No newline at end of file
diff --git a/src/extension/ui/src/components/tile/Top.tsx b/src/extension/ui/src/components/tile/Top.tsx
new file mode 100644
index 00000000..d2cd8b65
--- /dev/null
+++ b/src/extension/ui/src/components/tile/Top.tsx
@@ -0,0 +1,56 @@
+import { Settings } from "@mui/icons-material";
+import { Badge, CardMedia, IconButton, Stack, Switch, Tooltip, Typography } from "@mui/material";
+import { getUnsupportedSecretMessage } from "../../Constants";
+import { CatalogItemWithName } from "../../types/catalog";
+
+type TopProps = {
+ unAssignedConfig: { name: string, assigned: boolean }[],
+ onToggleRegister: (checked: boolean) => void,
+ unAssignedSecrets: { name: string, assigned: boolean }[],
+ registered: boolean
+ item: CatalogItemWithName
+}
+
+export default function Top({ item, unAssignedConfig, onToggleRegister, unAssignedSecrets, registered }: TopProps) {
+
+ const hasAllSecrets = unAssignedSecrets.length === 0
+
+ const hasAllConfig = unAssignedConfig.length === 0
+ const getActionButton = () => {
+ if (!hasAllSecrets || !hasAllConfig) {
+ return
+
+
+
+
+
+
+ }
+ return
+
+ {
+ event.stopPropagation()
+ event.preventDefault()
+ onToggleRegister(checked)
+ }}
+ />
+
+
+ }
+ return (
+
+
+
+ {item.name}
+
+ {getActionButton()}
+
+ )
+}
diff --git a/src/extension/ui/src/context/CatalogContext.tsx b/src/extension/ui/src/context/CatalogContext.tsx
index 213948d2..bd3c1da8 100644
--- a/src/extension/ui/src/context/CatalogContext.tsx
+++ b/src/extension/ui/src/context/CatalogContext.tsx
@@ -1,6 +1,6 @@
import React, { createContext, useContext, useState, ReactNode, useEffect } from 'react';
import { v1 } from "@docker/extension-api-client-types";
-import { CatalogItemWithName } from '../components/tile/Tile';
+import { CatalogItemWithName } from '../types/catalog';
import { getRegistry } from '../Registry';
import Secrets from '../Secrets';
import { parse } from 'yaml';
@@ -10,7 +10,7 @@ import { stringify } from 'yaml';
import { ExecResult } from '@docker/extension-api-client-types/dist/v0';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { useConfigContext } from './ConfigContext';
-
+import { Secret } from '../types/secrets';
// Storage keys for each query type
const STORAGE_KEYS = {
secrets: 'docker-catalog-secrets',
@@ -21,7 +21,7 @@ const STORAGE_KEYS = {
interface CatalogContextType {
// State
- secrets: Secrets.Secret[];
+ secrets: Secret[];
catalogItems: CatalogItemWithName[];
registryItems: { [key: string]: { ref: string; config: any } } | undefined;
canRegister: boolean;
diff --git a/src/extension/ui/src/context/ConfigContext.tsx b/src/extension/ui/src/context/ConfigContext.tsx
index 67d4784d..1e792cc1 100644
--- a/src/extension/ui/src/context/ConfigContext.tsx
+++ b/src/extension/ui/src/context/ConfigContext.tsx
@@ -4,7 +4,7 @@ import { getStoredConfig, syncRegistryWithConfig } from '../Registry';
import { POLL_INTERVAL } from '../Constants';
import { escapeJSONForPlatformShell, tryRunImageSync } from '../FileWatcher';
import { stringify } from 'yaml';
-import { ParsedParameters } from '../components/ConfigurationModal';
+import { ParsedParameters } from '../types/config';
import { useQuery, useQueryClient, useMutation } from '@tanstack/react-query';
interface ConfigContextType {
diff --git a/src/extension/ui/src/mcp-clients/MCPTypes.ts b/src/extension/ui/src/mcp-clients/MCPTypes.ts
index ed2fe05e..55b52a4b 100644
--- a/src/extension/ui/src/mcp-clients/MCPTypes.ts
+++ b/src/extension/ui/src/mcp-clients/MCPTypes.ts
@@ -1,15 +1,7 @@
import { DOCKER_MCP_COMMAND } from "../Constants";
-import { v1 } from "@docker/extension-api-client-types";
-export type MCPClient = {
- name: string;
- url: string;
- readConfig: (client: v1.DockerDesktopClient) => Promise<{ content: string | null | undefined, path: string }>; // Reads the config content from the MCP client
- connect: (client: v1.DockerDesktopClient) => Promise; // Connects catalog to the MCP client
- disconnect: (client: v1.DockerDesktopClient) => Promise; // Disconnects catalog from the MCP client
- validateConfig: (content: string) => boolean; // Parses the config content and returns true if it is valid and connected
- expectedConfigPath?: { [key in 'win32' | 'darwin' | 'linux']: string }; // Path to the config file, if applicable
- manualConfigSteps: string[];
-}
+import { MCPClient } from "../types/mcp";
+
+export type { MCPClient };
export const SAMPLE_MCP_CONFIG = {
mcpServers: {
diff --git a/src/extension/ui/src/types/catalog/index.ts b/src/extension/ui/src/types/catalog/index.ts
new file mode 100644
index 00000000..9f5db16b
--- /dev/null
+++ b/src/extension/ui/src/types/catalog/index.ts
@@ -0,0 +1,23 @@
+import { ReactNode } from "react";
+import { Secret } from "../secrets";
+
+/**
+ * Interface for a catalog item
+ */
+export interface CatalogItem {
+ description?: string;
+ icon?: string;
+ secrets?: { name: string }[];
+ ref: string;
+ prompts: number;
+ resources: object[];
+ tools: object[];
+ config?: any; // Configuration type
+}
+
+/**
+ * Interface for a catalog item with a name
+ */
+export interface CatalogItemWithName extends CatalogItem {
+ name: string;
+}
diff --git a/src/extension/ui/src/types/config/index.ts b/src/extension/ui/src/types/config/index.ts
new file mode 100644
index 00000000..f168a048
--- /dev/null
+++ b/src/extension/ui/src/types/config/index.ts
@@ -0,0 +1,95 @@
+/**
+ * Type definition for a basic parameter
+ */
+export type Parameter = {
+ type?: "string" | "number" | "boolean" | "array" | "object";
+ description?: string;
+};
+
+/**
+ * Type definition for an array parameter
+ */
+export type ParameterArray = {
+ type: 'array';
+ items: Parameter;
+ description?: string;
+};
+
+/**
+ * Type definition for an object parameter
+ */
+export type ParameterObject = {
+ type: 'object';
+ properties: {
+ [key: string]: Parameter;
+ };
+ description?: string;
+};
+
+/**
+ * Union type for all parameter types
+ */
+export type Parameters = Parameter | ParameterArray | ParameterObject;
+
+/**
+ * Type for parsed parameters (any value)
+ */
+export type ParsedParameter = any;
+
+/**
+ * Type for parsed parameter arrays
+ */
+export type ParsedParameterArray = any[];
+
+/**
+ * Type for parsed parameter objects
+ */
+export type ParsedParameterObject = Record;
+
+/**
+ * Type for any parsed parameter
+ */
+export type ParsedParameters = any;
+
+/**
+ * Type definition for configuration
+ */
+export type Config = {
+ name: string;
+ description: string;
+ parameters: Parameters
+}[];
+
+/**
+ * Base TrackingRecord interface
+ */
+interface TrackingRecord {
+ event: string;
+ properties: object;
+ event_timestamp?: number;
+ source?: string;
+}
+
+/**
+ * Interface for record reflecting registry changes
+ */
+export interface RegistryChangedRecord extends TrackingRecord {
+ event: 'registry-changed';
+ properties: {
+ name: string;
+ ref: string;
+ action: 'remove' | 'add';
+ };
+}
+
+/**
+ * Interface for record reflecting Claude config changes
+ */
+export interface ClaudeConfigChangedRecord extends TrackingRecord {
+ event: 'claude-config-changed';
+ properties: {
+ action: 'add' | 'remove' | 'write' | 'delete';
+ newvalue?: string;
+ oldvalue?: string;
+ };
+}
\ No newline at end of file
diff --git a/src/extension/ui/src/types/index.ts b/src/extension/ui/src/types/index.ts
new file mode 100644
index 00000000..aa75228e
--- /dev/null
+++ b/src/extension/ui/src/types/index.ts
@@ -0,0 +1,18 @@
+/**
+ * Export all types from the types directory
+ */
+
+// Utility types
+export * from './utils';
+
+// MCP related types
+export * from './mcp';
+
+// Secret related types
+export * from './secrets';
+
+// Configuration related types
+export * from './config';
+
+// Catalog related types
+export * from './catalog';
\ No newline at end of file
diff --git a/src/extension/ui/src/types/mcp/index.ts b/src/extension/ui/src/types/mcp/index.ts
new file mode 100644
index 00000000..600a0d5f
--- /dev/null
+++ b/src/extension/ui/src/types/mcp/index.ts
@@ -0,0 +1,24 @@
+import { v1 } from "@docker/extension-api-client-types";
+
+/**
+ * Type definition for an MCP Client
+ */
+export type MCPClient = {
+ name: string;
+ url: string;
+ readConfig: (client: v1.DockerDesktopClient) => Promise<{ content: string | null | undefined, path: string }>; // Reads the config content from the MCP client
+ connect: (client: v1.DockerDesktopClient) => Promise; // Connects catalog to the MCP client
+ disconnect: (client: v1.DockerDesktopClient) => Promise; // Disconnects catalog from the MCP client
+ validateConfig: (content: string) => boolean; // Parses the config content and returns true if it is valid and connected
+ expectedConfigPath?: { [key in 'win32' | 'darwin' | 'linux']: string }; // Path to the config file, if applicable
+ manualConfigSteps: string[];
+};
+
+/**
+ * Type for the client state
+ */
+export type MCPClientState = {
+ name: string;
+ connected: boolean;
+ path?: string;
+};
\ No newline at end of file
diff --git a/src/extension/ui/src/types/secrets/index.ts b/src/extension/ui/src/types/secrets/index.ts
new file mode 100644
index 00000000..d9987f44
--- /dev/null
+++ b/src/extension/ui/src/types/secrets/index.ts
@@ -0,0 +1,24 @@
+/**
+ * Type definition for a Secret
+ */
+export type Secret = {
+ name: string;
+ value: string;
+ policies: string[];
+};
+
+/**
+ * Type definition for a stored secret
+ */
+export type StoredSecret = {
+ name: string;
+ policies: string[];
+};
+
+/**
+ * Type definition for a policy
+ */
+export type Policy = {
+ name: string;
+ images: string[];
+};
\ No newline at end of file
diff --git a/src/extension/ui/src/types/utils/index.ts b/src/extension/ui/src/types/utils/index.ts
new file mode 100644
index 00000000..285050f7
--- /dev/null
+++ b/src/extension/ui/src/types/utils/index.ts
@@ -0,0 +1,4 @@
+/**
+ * Type for any object with string keys
+ */
+export type DeepObject = { [key: string]: any };
\ No newline at end of file