diff --git a/docs/feature-specs/dynamic_servers.md b/docs/feature-specs/dynamic_servers.md
index 46bfa824..91a53ad0 100644
--- a/docs/feature-specs/dynamic_servers.md
+++ b/docs/feature-specs/dynamic_servers.md
@@ -158,6 +158,24 @@ When servers are added dynamically, the system automatically:
The `mcp-add` tool ensures server name uniqueness by:
- Checking existing server names before adding
+
+## ChatGPT Tool Manager UI
+
+When the `dynamic-tools` feature flag is enabled, the gateway now publishes an interactive ChatGPT widget that orchestrates `mcp-find`, `mcp-add`, and `mcp-remove`. The component:
+
+- Renders catalog search results with descriptions, required secrets, and server metadata.
+- Shows the current set of enabled servers and highlights whether each result is active.
+- Initiates tool calls directly from the UI to add or remove servers and reflects the outcome in real time.
+
+The widget bundle lives in `ui/tool-manager/` and is embedded into the gateway at build time. To make changes:
+
+```bash
+cd ui/tool-manager
+npm install # first time only
+npm run build # rebuilds the bundle and copies it to pkg/gateway/ui/embedded/
+```
+
+Running `npm run build` updates both `ui/tool-manager/dist/tool-manager.js` (for development) and the embedded asset at `pkg/gateway/ui/embedded/tool-manager.js`. Re-start the gateway after rebuilding so the new UI is served to ChatGPT.
- Only appending new servers if not already present
- Maintaining the original order of servers
@@ -224,4 +242,4 @@ Potential future improvements include:
- Server dependency management
- Configuration validation
- Server status monitoring
-- Rollback capabilities
\ No newline at end of file
+- Rollback capabilities
diff --git a/pkg/gateway/dynamic_mcps.go b/pkg/gateway/dynamic_mcps.go
index 93d4ca74..04dd94d6 100644
--- a/pkg/gateway/dynamic_mcps.go
+++ b/pkg/gateway/dynamic_mcps.go
@@ -29,7 +29,16 @@ import (
func (g *Gateway) createMcpFindTool(configuration Configuration) *ToolRegistration {
tool := &mcp.Tool{
Name: "mcp-find",
+ Title: "Search MCP Catalog",
Description: "Find MCP servers in the current catalog by name or description. Returns matching servers with their details.",
+ Meta: mcp.Meta{
+ "openai/outputTemplate": toolManagerResourceURI,
+ "openai/widgetAccessible": true,
+ "openai/toolInvocation/invoking": "Searching the MCP catalog…",
+ "openai/toolInvocation/invoked": "Search complete.",
+ "openai/toolInvocation/failed": "Catalog search failed.",
+ "openai/toolInvocation/description": "Searches the MCP catalog and renders results in the Tool Manager UI.",
+ },
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
@@ -44,6 +53,7 @@ func (g *Gateway) createMcpFindTool(configuration Configuration) *ToolRegistrati
},
Required: []string{"query"},
},
+ OutputSchema: toolManagerOutputSchema,
}
handler := func(_ context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -148,53 +158,43 @@ func (g *Gateway) createMcpFindTool(configuration Configuration) *ToolRegistrati
}
}
+ totalMatches := len(matches)
+
// Limit results
if len(matches) > params.Limit {
matches = matches[:params.Limit]
}
- // Format results
- var results []map[string]any
+ activeServers := g.activeServerNames()
+ serverSummaries := make([]toolManagerServer, 0, len(matches))
for _, match := range matches {
- serverInfo := map[string]any{
- "name": match.Name,
- }
-
- if match.Server.Description != "" {
- serverInfo["description"] = match.Server.Description
- }
+ summary := summarizeServer(match.Name, match.Server)
+ summary.IsActive = slices.Contains(activeServers, match.Name)
+ serverSummaries = append(serverSummaries, summary)
+ }
- if len(match.Server.Secrets) > 0 {
- var secrets []string
- for _, secret := range match.Server.Secrets {
- secrets = append(secrets, secret.Name)
+ message := fmt.Sprintf("Found %d server(s) matching %q.", totalMatches, params.Query)
+ if len(serverSummaries) > 0 {
+ var topNames []string
+ for idx, server := range serverSummaries {
+ if idx >= 5 {
+ break
}
- serverInfo["required_secrets"] = secrets
+ topNames = append(topNames, server.Name)
}
-
- if len(match.Server.Config) > 0 {
- serverInfo["config_schema"] = match.Server.Config
+ if len(topNames) > 0 {
+ message = fmt.Sprintf("%s Top matches: %s.", message, strings.Join(topNames, ", "))
}
-
- serverInfo["long_lived"] = match.Server.LongLived
-
- results = append(results, serverInfo)
}
- response := map[string]any{
- "query": params.Query,
- "total_matches": len(results),
- "servers": results,
- }
+ payload := newToolManagerPayload("mcp-find", "success", message)
+ payload.Query = params.Query
+ payload.Limit = params.Limit
+ payload.TotalMatches = totalMatches
+ payload.Results = serverSummaries
+ payload.LastAction = newToolManagerAction("find", "success", message, "")
- responseBytes, err := json.Marshal(response)
- if err != nil {
- return nil, fmt.Errorf("failed to marshal response: %w", err)
- }
-
- return &mcp.CallToolResult{
- Content: []mcp.Content{&mcp.TextContent{Text: string(responseBytes)}},
- }, nil
+ return g.buildToolManagerResult(payload, message), nil
}
return &ToolRegistration{
@@ -214,7 +214,16 @@ type ServerMatch struct {
func (g *Gateway) createMcpAddTool(clientConfig *clientConfig) *ToolRegistration {
tool := &mcp.Tool{
Name: "mcp-add",
+ Title: "Add MCP Server",
Description: "Add a new MCP server to the session. The server must exist in the catalog.",
+ Meta: mcp.Meta{
+ "openai/outputTemplate": toolManagerResourceURI,
+ "openai/widgetAccessible": true,
+ "openai/toolInvocation/invoking": "Adding the server to your session…",
+ "openai/toolInvocation/invoked": "Server added.",
+ "openai/toolInvocation/failed": "Failed to add the server.",
+ "openai/toolInvocation/description": "Enables an MCP server from the catalog and reloads configuration.",
+ },
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
@@ -225,10 +234,10 @@ func (g *Gateway) createMcpAddTool(clientConfig *clientConfig) *ToolRegistration
},
Required: []string{"name"},
},
+ OutputSchema: toolManagerOutputSchema,
}
handler := func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- // Parse parameters
var params struct {
Name string `json:"name"`
}
@@ -252,36 +261,42 @@ func (g *Gateway) createMcpAddTool(clientConfig *clientConfig) *ToolRegistration
serverName := strings.TrimSpace(params.Name)
- // Check if server exists in catalog
+ payload := newToolManagerPayload("mcp-add", "info", "")
+ payload.Server = &toolManagerServer{Name: serverName}
+
serverConfig, _, found := g.configuration.Find(serverName)
if !found {
- return &mcp.CallToolResult{
- Content: []mcp.Content{&mcp.TextContent{
- Text: fmt.Sprintf("Error: Server '%s' not found in catalog. Use mcp-find to search for available servers.", serverName),
- }},
- }, nil
- }
-
- // Append the new server to the current serverNames if not already present
- found = false
- for _, existing := range g.configuration.serverNames {
- if existing == serverName {
- found = true
+ message := fmt.Sprintf("Error: Server '%s' not found in catalog. Use mcp-find to search for available servers.", serverName)
+ payload.Status = "error"
+ payload.Message = message
+ payload.Error = message
+ payload.LastAction = newToolManagerAction("add", "error", message, serverName)
+ return g.buildToolManagerResult(payload, message), nil
+ }
+
+ serverSummary := summarizeServer(serverName, serverConfig.Spec)
+ serverSummary.IsActive = true
+ payload.Server = &serverSummary
+ payload.Results = []toolManagerServer{serverSummary}
+
+ existing := false
+ for _, name := range g.configuration.serverNames {
+ if name == serverName {
+ existing = true
break
}
}
- if !found {
+ if !existing {
g.configuration.serverNames = append(g.configuration.serverNames, serverName)
}
- // Fetch updated secrets for the new server list
if g.configurator != nil {
if fbc, ok := g.configurator.(*FileBasedConfiguration); ok {
- updatedSecrets, err := fbc.readDockerDesktopSecrets(ctx, g.configuration.servers, g.configuration.serverNames)
- if err == nil {
+ updatedSecrets, secretsErr := fbc.readDockerDesktopSecrets(ctx, g.configuration.servers, g.configuration.serverNames)
+ if secretsErr == nil {
g.configuration.secrets = updatedSecrets
} else {
- log("Warning: Failed to update secrets:", err)
+ log("Warning: Failed to update secrets:", secretsErr)
}
}
}
@@ -290,20 +305,22 @@ func (g *Gateway) createMcpAddTool(clientConfig *clientConfig) *ToolRegistration
return nil, fmt.Errorf("failed to reload configuration: %w", err)
}
- // Register DCR client and start OAuth provider if this is a remote OAuth server
+ respond := func(status, message string) (*mcp.CallToolResult, error) {
+ payload.Status = status
+ payload.Message = message
+ payload.LastAction = newToolManagerAction("add", status, message, serverName)
+ return g.buildToolManagerResult(payload, message), nil
+ }
+
if g.McpOAuthDcrEnabled && serverConfig.Spec.IsRemoteOAuthServer() {
- // Register DCR client with DD so user can authorize
if err := oauth.RegisterProviderForLazySetup(ctx, serverName); err != nil {
logf("Warning: Failed to register OAuth provider for %s: %v", serverName, err)
}
- // Start provider
g.startProvider(ctx, serverName)
- // Check if current serverSession supports elicitations
if req.Session.InitializeParams().Capabilities != nil && req.Session.InitializeParams().Capabilities.Elicitation != nil {
- // Elicit a response from the client asking whether to open a browser for authorization
- elicitResult, err := req.Session.Elicit(ctx, &mcp.ElicitParams{
+ elicitResult, elicitErr := req.Session.Elicit(ctx, &mcp.ElicitParams{
Message: fmt.Sprintf("Would you like to open a browser to authorize the '%s' server?", serverName),
RequestedSchema: &jsonschema.Schema{
Type: "object",
@@ -316,16 +333,14 @@ func (g *Gateway) createMcpAddTool(clientConfig *clientConfig) *ToolRegistration
Required: []string{"authorize"},
},
})
- if err != nil {
- logf("Warning: Failed to elicit authorization response for %s: %v", serverName, err)
+ if elicitErr != nil {
+ logf("Warning: Failed to elicit authorization response for %s: %v", serverName, elicitErr)
} else if elicitResult.Action == "accept" && elicitResult.Content != nil {
- // Check if user authorized
if authorize, ok := elicitResult.Content["authorize"].(bool); ok && authorize {
- // User agreed to authorize, call the OAuth authorize function
client := desktop.NewAuthClient()
- authResponse, err := client.PostOAuthApp(ctx, serverName, "", false)
- if err != nil {
- logf("Warning: Failed to start OAuth flow for %s: %v", serverName, err)
+ authResponse, authErr := client.PostOAuthApp(ctx, serverName, "", false)
+ if authErr != nil {
+ logf("Warning: Failed to start OAuth flow for %s: %v", serverName, authErr)
} else if authResponse.BrowserURL != "" {
logf("Opening browser for authentication: %s", authResponse.BrowserURL)
} else {
@@ -334,40 +349,22 @@ func (g *Gateway) createMcpAddTool(clientConfig *clientConfig) *ToolRegistration
}
}
- return &mcp.CallToolResult{
- Content: []mcp.Content{&mcp.TextContent{
- Text: fmt.Sprintf("Successfully added server '%s'. Authorization completed.", serverName),
- }},
- }, nil
+ return respond("success", fmt.Sprintf("Successfully added server '%s'. Authorization completed.", serverName))
}
- // Client doesn't support elicitations, get the login link and include it in the response
client := desktop.NewAuthClient()
- // Set context flag to enable disableAutoOpen parameter
ctxWithFlag := context.WithValue(ctx, contextkeys.OAuthInterceptorEnabledKey, true)
- authResponse, err := client.PostOAuthApp(ctxWithFlag, serverName, "", true)
- if err != nil {
- logf("Warning: Failed to get OAuth URL for %s: %v", serverName, err)
+ authResponse, authErr := client.PostOAuthApp(ctxWithFlag, serverName, "", true)
+ if authErr != nil {
+ logf("Warning: Failed to get OAuth URL for %s: %v", serverName, authErr)
} else if authResponse.BrowserURL != "" {
- return &mcp.CallToolResult{
- Content: []mcp.Content{&mcp.TextContent{
- Text: fmt.Sprintf("Successfully added server '%s'. To authorize this server, please open the following URL in your browser:\n\n%s", serverName, authResponse.BrowserURL),
- }},
- }, nil
+ return respond("success", fmt.Sprintf("Successfully added server '%s'. To authorize this server, please open the following URL in your browser:\n\n%s", serverName, authResponse.BrowserURL))
}
- return &mcp.CallToolResult{
- Content: []mcp.Content{&mcp.TextContent{
- Text: fmt.Sprintf("Successfully added server '%s'. You will need to authorize this server with: docker mcp oauth authorize %s", serverName, serverName),
- }},
- }, nil
+ return respond("success", fmt.Sprintf("Successfully added server '%s'. You will need to authorize this server with: docker mcp oauth authorize %s", serverName, serverName))
}
- return &mcp.CallToolResult{
- Content: []mcp.Content{&mcp.TextContent{
- Text: fmt.Sprintf("Successfully added server '%s'. Assume that it is fully configured and ready to use.", serverName),
- }},
- }, nil
+ return respond("success", fmt.Sprintf("Successfully added server '%s'. Assume that it is fully configured and ready to use.", serverName))
}
return &ToolRegistration{
@@ -380,7 +377,16 @@ func (g *Gateway) createMcpAddTool(clientConfig *clientConfig) *ToolRegistration
func (g *Gateway) createMcpRemoveTool() *ToolRegistration {
tool := &mcp.Tool{
Name: "mcp-remove",
+ Title: "Remove MCP Server",
Description: "Remove an MCP server from the registry and reload the configuration. This will disable the server.",
+ Meta: mcp.Meta{
+ "openai/outputTemplate": toolManagerResourceURI,
+ "openai/widgetAccessible": true,
+ "openai/toolInvocation/invoking": "Removing the server from your session…",
+ "openai/toolInvocation/invoked": "Server removed.",
+ "openai/toolInvocation/failed": "Failed to remove the server.",
+ "openai/toolInvocation/description": "Disables an MCP server and reloads configuration.",
+ },
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
@@ -391,6 +397,7 @@ func (g *Gateway) createMcpRemoveTool() *ToolRegistration {
},
Required: []string{"name"},
},
+ OutputSchema: toolManagerOutputSchema,
}
handler := func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -418,15 +425,15 @@ func (g *Gateway) createMcpRemoveTool() *ToolRegistration {
serverName := strings.TrimSpace(params.Name)
- // Remove the server from the current serverNames
+ payload := newToolManagerPayload("mcp-remove", "info", "")
+ payload.Server = &toolManagerServer{Name: serverName}
+
updatedServerNames := slices.DeleteFunc(slices.Clone(g.configuration.serverNames), func(name string) bool {
return name == serverName
})
-
- // Update the current configuration state
+ wasActive := len(updatedServerNames) != len(g.configuration.serverNames)
g.configuration.serverNames = updatedServerNames
- // Stop OAuth provider if this is an OAuth server
if g.McpOAuthDcrEnabled {
g.stopProvider(serverName)
}
@@ -435,11 +442,28 @@ func (g *Gateway) createMcpRemoveTool() *ToolRegistration {
return nil, fmt.Errorf("failed to remove server configuration: %w", err)
}
- return &mcp.CallToolResult{
- Content: []mcp.Content{&mcp.TextContent{
- Text: fmt.Sprintf("Successfully removed server '%s'.", serverName),
- }},
- }, nil
+ if serverDetails, ok := g.configuration.servers[serverName]; ok {
+ serverSummary := summarizeServer(serverName, serverDetails)
+ serverSummary.IsActive = false
+ payload.Server = &serverSummary
+ payload.Results = []toolManagerServer{serverSummary}
+ }
+
+ var status string
+ var message string
+ if wasActive {
+ status = "success"
+ message = fmt.Sprintf("Successfully removed server '%s'.", serverName)
+ } else {
+ status = "info"
+ message = fmt.Sprintf("Server '%s' was not active. No changes were required.", serverName)
+ }
+
+ payload.Status = status
+ payload.Message = message
+ payload.LastAction = newToolManagerAction("remove", status, message, serverName)
+
+ return g.buildToolManagerResult(payload, message), nil
}
return &ToolRegistration{
diff --git a/pkg/gateway/reload.go b/pkg/gateway/reload.go
index 53c35e53..c83436e2 100644
--- a/pkg/gateway/reload.go
+++ b/pkg/gateway/reload.go
@@ -73,25 +73,33 @@ func (g *Gateway) reloadConfiguration(ctx context.Context, configuration Configu
if g.DynamicTools {
log("- Adding internal tools (dynamic-tools feature enabled)")
+ dynamicCaps := g.ensureDynamicCapabilities()
+ g.registerToolManagerResource()
+
// Add mcp-find tool
mcpFindTool := g.createMcpFindTool(configuration)
g.mcpServer.AddTool(mcpFindTool.Tool, mcpFindTool.Handler)
+ dynamicCaps.ToolNames = append(dynamicCaps.ToolNames, mcpFindTool.Tool.Name)
// Add mcp-add tool
mcpAddTool := g.createMcpAddTool(clientConfig)
g.mcpServer.AddTool(mcpAddTool.Tool, mcpAddTool.Handler)
+ dynamicCaps.ToolNames = append(dynamicCaps.ToolNames, mcpAddTool.Tool.Name)
// Add mcp-remove tool
mcpRemoveTool := g.createMcpRemoveTool()
g.mcpServer.AddTool(mcpRemoveTool.Tool, mcpRemoveTool.Handler)
+ dynamicCaps.ToolNames = append(dynamicCaps.ToolNames, mcpRemoveTool.Tool.Name)
// Add mcp-registry-import tool
mcpRegistryImportTool := g.createMcpRegistryImportTool(configuration, clientConfig)
g.mcpServer.AddTool(mcpRegistryImportTool.Tool, mcpRegistryImportTool.Handler)
+ dynamicCaps.ToolNames = append(dynamicCaps.ToolNames, mcpRegistryImportTool.Tool.Name)
// Add mcp-config-set tool
mcpConfigSetTool := g.createMcpConfigSetTool(clientConfig)
g.mcpServer.AddTool(mcpConfigSetTool.Tool, mcpConfigSetTool.Handler)
+ dynamicCaps.ToolNames = append(dynamicCaps.ToolNames, mcpConfigSetTool.Tool.Name)
log(" > mcp-find: tool for finding MCP servers in the catalog")
log(" > mcp-add: tool for adding MCP servers to the registry")
diff --git a/pkg/gateway/tool_manager_ui.go b/pkg/gateway/tool_manager_ui.go
new file mode 100644
index 00000000..67058b9c
--- /dev/null
+++ b/pkg/gateway/tool_manager_ui.go
@@ -0,0 +1,285 @@
+package gateway
+
+import (
+ "context"
+ "fmt"
+ "slices"
+ "strings"
+ "time"
+
+ _ "embed"
+
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+
+ "github.com/docker/mcp-gateway/pkg/catalog"
+)
+
+const (
+ dynamicToolsCapabilityKey = "__dynamic_tools__"
+
+ toolManagerView = "mcp-tool-manager"
+ toolManagerResourceURI = "ui://widget/mcp-tool-manager.html"
+ toolManagerResourceName = "mcp-tool-manager-ui"
+ toolManagerResourceMIMEType = "text/html+skybridge"
+)
+
+//go:embed ui/embedded/tool-manager.js
+var toolManagerBundle string
+
+var toolManagerOutputSchema = map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "view": map[string]any{"type": "string"},
+ "status": map[string]any{"type": "string"},
+ "message": map[string]any{"type": "string"},
+ "query": map[string]any{"type": "string"},
+ "limit": map[string]any{"type": "integer"},
+ "totalMatches": map[string]any{"type": "integer"},
+ "activeServers": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
+ "results": map[string]any{
+ "type": "array",
+ "items": map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "name": map[string]any{"type": "string"},
+ "description": map[string]any{"type": "string"},
+ "type": map[string]any{"type": "string"},
+ "remoteUrl": map[string]any{"type": "string"},
+ "image": map[string]any{"type": "string"},
+ "longLived": map[string]any{"type": "boolean"},
+ "requiredSecrets": map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
+ "isActive": map[string]any{"type": "boolean"},
+ },
+ },
+ },
+ "server": map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "name": map[string]any{"type": "string"},
+ },
+ },
+ "lastAction": map[string]any{
+ "type": "object",
+ "properties": map[string]any{
+ "type": map[string]any{"type": "string"},
+ "status": map[string]any{"type": "string"},
+ "message": map[string]any{"type": "string"},
+ "server": map[string]any{"type": "string"},
+ },
+ },
+ "timestamp": map[string]any{"type": "string"},
+ "error": map[string]any{"type": "string"},
+ },
+}
+
+type toolManagerServer struct {
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+ Type string `json:"type,omitempty"`
+ RemoteURL string `json:"remoteUrl,omitempty"`
+ Image string `json:"image,omitempty"`
+ LongLived bool `json:"longLived,omitempty"`
+ RequiredSecrets []string `json:"requiredSecrets,omitempty"`
+ ConfigSchema []any `json:"configSchema,omitempty"`
+ Tools []toolManagerServerTool `json:"tools,omitempty"`
+ OAuthProviders []string `json:"oauthProviders,omitempty"`
+ IsActive bool `json:"isActive,omitempty"`
+}
+
+type toolManagerServerTool struct {
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+}
+
+type toolManagerAction struct {
+ Type string `json:"type"`
+ Status string `json:"status"`
+ Message string `json:"message,omitempty"`
+ Server string `json:"server,omitempty"`
+}
+
+type toolManagerPayload struct {
+ View string `json:"view"`
+ SourceTool string `json:"sourceTool"`
+ Status string `json:"status"`
+ Message string `json:"message,omitempty"`
+ Query string `json:"query,omitempty"`
+ Limit int `json:"limit,omitempty"`
+ TotalMatches int `json:"totalMatches,omitempty"`
+ ActiveServers []string `json:"activeServers"`
+ Results []toolManagerServer `json:"results,omitempty"`
+ Server *toolManagerServer `json:"server,omitempty"`
+ LastAction *toolManagerAction `json:"lastAction,omitempty"`
+ Timestamp string `json:"timestamp"`
+ Error string `json:"error,omitempty"`
+}
+
+func ensureToolManagerBundle() string {
+ escaped := strings.ReplaceAll(toolManagerBundle, "", "<\\/script>")
+ return fmt.Sprintf(`
`, escaped)
+}
+
+func (g *Gateway) ensureDynamicCapabilities() *ServerCapabilities {
+ if g.serverCapabilities[dynamicToolsCapabilityKey] == nil {
+ g.serverCapabilities[dynamicToolsCapabilityKey] = &ServerCapabilities{}
+ }
+ return g.serverCapabilities[dynamicToolsCapabilityKey]
+}
+
+func (g *Gateway) registerToolManagerResource() {
+ resource := &mcp.Resource{
+ Name: toolManagerResourceName,
+ URI: toolManagerResourceURI,
+ Title: "MCP Tool Manager",
+ Description: "Interactive UI for searching, adding, and removing MCP servers in the gateway.",
+ MIMEType: toolManagerResourceMIMEType,
+ Meta: mcp.Meta{
+ "openai/widgetDescription": "Interactive UI for searching the MCP catalog, enabling servers, and removing them from the session.",
+ },
+ }
+
+ g.mcpServer.AddResource(resource, func(ctx context.Context, req *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
+ return &mcp.ReadResourceResult{
+ Contents: []*mcp.ResourceContents{
+ {
+ URI: toolManagerResourceURI,
+ MIMEType: toolManagerResourceMIMEType,
+ Text: ensureToolManagerBundle(),
+ },
+ },
+ }, nil
+ })
+
+ caps := g.ensureDynamicCapabilities()
+ caps.ResourceURIs = append(caps.ResourceURIs, toolManagerResourceURI)
+}
+
+func (g *Gateway) activeServerNames() []string {
+ seen := map[string]struct{}{}
+ var names []string
+ for _, name := range g.configuration.serverNames {
+ name = strings.TrimSpace(name)
+ if name == "" {
+ continue
+ }
+ if _, exists := seen[name]; exists {
+ continue
+ }
+ seen[name] = struct{}{}
+ names = append(names, name)
+ }
+ slices.Sort(names)
+ return names
+}
+
+func summarizeServer(name string, server catalog.Server) toolManagerServer {
+ summary := toolManagerServer{
+ Name: name,
+ Description: server.Description,
+ Type: server.Type,
+ Image: server.Image,
+ LongLived: server.LongLived,
+ }
+
+ if server.Remote.URL != "" {
+ summary.RemoteURL = server.Remote.URL
+ } else if server.SSEEndpoint != "" {
+ summary.RemoteURL = server.SSEEndpoint
+ }
+
+ if len(server.Secrets) > 0 {
+ secrets := make([]string, 0, len(server.Secrets))
+ for _, secret := range server.Secrets {
+ if secret.Name != "" {
+ secrets = append(secrets, secret.Name)
+ }
+ }
+ summary.RequiredSecrets = secrets
+ }
+
+ if len(server.Tools) > 0 {
+ tools := make([]toolManagerServerTool, 0, len(server.Tools))
+ for _, tool := range server.Tools {
+ tools = append(tools, toolManagerServerTool{
+ Name: tool.Name,
+ Description: tool.Description,
+ })
+ }
+ summary.Tools = tools
+ }
+
+ if len(server.Config) > 0 {
+ summary.ConfigSchema = server.Config
+ }
+
+ if server.OAuth != nil && len(server.OAuth.Providers) > 0 {
+ providers := make([]string, 0, len(server.OAuth.Providers))
+ for _, provider := range server.OAuth.Providers {
+ if provider.Provider != "" {
+ providers = append(providers, provider.Provider)
+ }
+ }
+ summary.OAuthProviders = providers
+ }
+
+ return summary
+}
+
+func newToolManagerPayload(sourceTool, status, message string) toolManagerPayload {
+ if status == "" {
+ status = "info"
+ }
+ payload := toolManagerPayload{
+ View: toolManagerView,
+ SourceTool: sourceTool,
+ Status: status,
+ Message: message,
+ ActiveServers: []string{},
+ Timestamp: time.Now().UTC().Format(time.RFC3339),
+ }
+ return payload
+}
+
+func newToolManagerAction(actionType, status, message, server string) *toolManagerAction {
+ if actionType == "" {
+ actionType = "find"
+ }
+ if status != "error" {
+ status = "success"
+ }
+ return &toolManagerAction{
+ Type: actionType,
+ Status: status,
+ Message: message,
+ Server: server,
+ }
+}
+
+func (g *Gateway) buildToolManagerResult(payload toolManagerPayload, contentMessage string) *mcp.CallToolResult {
+ payload.ActiveServers = g.activeServerNames()
+ if payload.LastAction == nil && payload.SourceTool != "" {
+ actionType := "find"
+ switch payload.SourceTool {
+ case "mcp-add":
+ actionType = "add"
+ case "mcp-remove":
+ actionType = "remove"
+ }
+ serverName := ""
+ if payload.Server != nil {
+ serverName = payload.Server.Name
+ }
+ payload.LastAction = newToolManagerAction(actionType, payload.Status, payload.Message, serverName)
+ }
+
+ result := &mcp.CallToolResult{
+ Meta: mcp.Meta{
+ "openai/outputTemplate": toolManagerResourceURI,
+ },
+ Content: []mcp.Content{
+ &mcp.TextContent{Text: contentMessage},
+ },
+ StructuredContent: payload,
+ }
+ return result
+}
diff --git a/pkg/gateway/ui/embedded/tool-manager.js b/pkg/gateway/ui/embedded/tool-manager.js
new file mode 100644
index 00000000..2ba8f263
--- /dev/null
+++ b/pkg/gateway/ui/embedded/tool-manager.js
@@ -0,0 +1,255 @@
+var Xc=Object.create;var tu=Object.defineProperty;var Zc=Object.getOwnPropertyDescriptor;var qc=Object.getOwnPropertyNames;var Jc=Object.getPrototypeOf,bc=Object.prototype.hasOwnProperty;var Ve=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var ef=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let l of qc(t))!bc.call(e,l)&&l!==n&&tu(e,l,{get:()=>t[l],enumerable:!(r=Zc(t,l))||r.enumerable});return e};var rr=(e,t,n)=>(n=e!=null?Xc(Jc(e)):{},ef(t||!e||!e.__esModule?tu(n,"default",{value:e,enumerable:!0}):n,e));var pu=Ve(N=>{"use strict";var fn=Symbol.for("react.element"),tf=Symbol.for("react.portal"),nf=Symbol.for("react.fragment"),rf=Symbol.for("react.strict_mode"),lf=Symbol.for("react.profiler"),of=Symbol.for("react.provider"),uf=Symbol.for("react.context"),sf=Symbol.for("react.forward_ref"),af=Symbol.for("react.suspense"),cf=Symbol.for("react.memo"),ff=Symbol.for("react.lazy"),nu=Symbol.iterator;function df(e){return e===null||typeof e!="object"?null:(e=nu&&e[nu]||e["@@iterator"],typeof e=="function"?e:null)}var ou={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},iu=Object.assign,uu={};function Dt(e,t,n){this.props=e,this.context=t,this.refs=uu,this.updater=n||ou}Dt.prototype.isReactComponent={};Dt.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};Dt.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function su(){}su.prototype=Dt.prototype;function Ml(e,t,n){this.props=e,this.context=t,this.refs=uu,this.updater=n||ou}var Ol=Ml.prototype=new su;Ol.constructor=Ml;iu(Ol,Dt.prototype);Ol.isPureReactComponent=!0;var ru=Array.isArray,au=Object.prototype.hasOwnProperty,Dl={current:null},cu={key:!0,ref:!0,__self:!0,__source:!0};function fu(e,t,n){var r,l={},o=null,i=null;if(t!=null)for(r in t.ref!==void 0&&(i=t.ref),t.key!==void 0&&(o=""+t.key),t)au.call(t,r)&&!cu.hasOwnProperty(r)&&(l[r]=t[r]);var u=arguments.length-2;if(u===1)l.children=n;else if(1{"use strict";mu.exports=pu()});var _u=Ve(R=>{"use strict";function Ul(e,t){var n=e.length;e.push(t);e:for(;0>>1,l=e[r];if(0>>1;rsr(u,n))ssr(c,u)?(e[r]=c,e[s]=n,r=s):(e[r]=u,e[i]=n,r=i);else if(ssr(c,n))e[r]=c,e[s]=n,r=s;else break e}}return t}function sr(e,t){var n=e.sortIndex-t.sortIndex;return n!==0?n:e.id-t.id}typeof performance=="object"&&typeof performance.now=="function"?(gu=performance,R.unstable_now=function(){return gu.now()}):(Fl=Date,vu=Fl.now(),R.unstable_now=function(){return Fl.now()-vu});var gu,Fl,vu,De=[],qe=[],hf=1,ye=null,J=3,fr=!1,wt=!1,pn=!1,wu=typeof setTimeout=="function"?setTimeout:null,Su=typeof clearTimeout=="function"?clearTimeout:null,hu=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function Vl(e){for(var t=xe(qe);t!==null;){if(t.callback===null)cr(qe);else if(t.startTime<=e)cr(qe),t.sortIndex=t.expirationTime,Ul(De,t);else break;t=xe(qe)}}function $l(e){if(pn=!1,Vl(e),!wt)if(xe(De)!==null)wt=!0,Bl(Wl);else{var t=xe(qe);t!==null&&Hl($l,t.startTime-e)}}function Wl(e,t){wt=!1,pn&&(pn=!1,Su(mn),mn=-1),fr=!0;var n=J;try{for(Vl(t),ye=xe(De);ye!==null&&(!(ye.expirationTime>t)||e&&!Cu());){var r=ye.callback;if(typeof r=="function"){ye.callback=null,J=ye.priorityLevel;var l=r(ye.expirationTime<=t);t=R.unstable_now(),typeof l=="function"?ye.callback=l:ye===xe(De)&&cr(De),Vl(t)}else cr(De);ye=xe(De)}if(ye!==null)var o=!0;else{var i=xe(qe);i!==null&&Hl($l,i.startTime-t),o=!1}return o}finally{ye=null,J=n,fr=!1}}var dr=!1,ar=null,mn=-1,ku=5,Eu=-1;function Cu(){return!(R.unstable_now()-Eue||125r?(e.sortIndex=n,Ul(qe,e),xe(De)===null&&e===xe(qe)&&(pn?(Su(mn),mn=-1):pn=!0,Hl($l,n-r))):(e.sortIndex=l,Ul(De,e),wt||fr||(wt=!0,Bl(Wl))),e};R.unstable_shouldYield=Cu;R.unstable_wrapCallback=function(e){var t=J;return function(){var n=J;J=t;try{return e.apply(this,arguments)}finally{J=n}}}});var Tu=Ve((Fp,xu)=>{"use strict";xu.exports=_u()});var Lc=Ve(he=>{"use strict";var yf=ur(),ge=Tu();function h(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),mo=Object.prototype.hasOwnProperty,wf=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Nu={},Pu={};function Sf(e){return mo.call(Pu,e)?!0:mo.call(Nu,e)?!1:wf.test(e)?Pu[e]=!0:(Nu[e]=!0,!1)}function kf(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Ef(e,t,n,r){if(t===null||typeof t>"u"||kf(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function ie(e,t,n,r,l,o,i){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=l,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=o,this.removeEmptyString=i}var q={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){q[e]=new ie(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];q[t]=new ie(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){q[e]=new ie(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){q[e]=new ie(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){q[e]=new ie(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){q[e]=new ie(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){q[e]=new ie(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){q[e]=new ie(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){q[e]=new ie(e,5,!1,e.toLowerCase(),null,!1,!1)});var ii=/[\-:]([a-z])/g;function ui(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(ii,ui);q[t]=new ie(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(ii,ui);q[t]=new ie(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(ii,ui);q[t]=new ie(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){q[e]=new ie(e,1,!1,e.toLowerCase(),null,!1,!1)});q.xlinkHref=new ie("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){q[e]=new ie(e,1,!1,e.toLowerCase(),null,!0,!0)});function si(e,t,n,r){var l=q.hasOwnProperty(t)?q[t]:null;(l!==null?l.type!==0:r||!(2u||l[i]!==o[u]){var s=`
+`+l[i].replace(" at new "," at ");return e.displayName&&s.includes("")&&(s=s.replace("",e.displayName)),s}while(1<=i&&0<=u);break}}}finally{Kl=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?Cn(e):""}function Cf(e){switch(e.tag){case 5:return Cn(e.type);case 16:return Cn("Lazy");case 13:return Cn("Suspense");case 19:return Cn("SuspenseList");case 0:case 2:case 15:return e=Yl(e.type,!1),e;case 11:return e=Yl(e.type.render,!1),e;case 1:return e=Yl(e.type,!0),e;default:return""}}function yo(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case jt:return"Fragment";case At:return"Portal";case go:return"Profiler";case ai:return"StrictMode";case vo:return"Suspense";case ho:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Is:return(e.displayName||"Context")+".Consumer";case Ds:return(e._context.displayName||"Context")+".Provider";case ci:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case fi:return t=e.displayName||null,t!==null?t:yo(e.type)||"Memo";case be:t=e._payload,e=e._init;try{return yo(e(t))}catch{}}return null}function _f(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return yo(t);case 8:return t===ai?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function pt(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function As(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function xf(e){var t=As(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var l=n.get,o=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return l.call(this)},set:function(i){r=""+i,o.call(this,i)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(i){r=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function mr(e){e._valueTracker||(e._valueTracker=xf(e))}function js(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=As(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function Wr(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function wo(e,t){var n=t.checked;return U({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Lu(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=pt(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Us(e,t){t=t.checked,t!=null&&si(e,"checked",t,!1)}function So(e,t){Us(e,t);var n=pt(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?ko(e,t.type,n):t.hasOwnProperty("defaultValue")&&ko(e,t.type,pt(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Ru(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function ko(e,t,n){(t!=="number"||Wr(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var _n=Array.isArray;function Xt(e,t,n,r){if(e=e.options,t){t={};for(var l=0;l"+t.valueOf().toString()+"",t=gr.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function An(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Nn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Tf=["Webkit","ms","Moz","O"];Object.keys(Nn).forEach(function(e){Tf.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Nn[t]=Nn[e]})});function Bs(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Nn.hasOwnProperty(e)&&Nn[e]?(""+t).trim():t+"px"}function Hs(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,l=Bs(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,l):e[n]=l}}var Nf=U({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function _o(e,t){if(t){if(Nf[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(h(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(h(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(h(61))}if(t.style!=null&&typeof t.style!="object")throw Error(h(62))}}function xo(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var To=null;function di(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var No=null,Zt=null,qt=null;function Du(e){if(e=tr(e)){if(typeof No!="function")throw Error(h(280));var t=e.stateNode;t&&(t=vl(t),No(e.stateNode,e.type,t))}}function Qs(e){Zt?qt?qt.push(e):qt=[e]:Zt=e}function Ks(){if(Zt){var e=Zt,t=qt;if(qt=Zt=null,Du(e),t)for(e=0;e>>=0,e===0?32:31-(jf(e)/Uf|0)|0}var vr=64,hr=4194304;function xn(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Kr(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,l=e.suspendedLanes,o=e.pingedLanes,i=n&268435455;if(i!==0){var u=i&~l;u!==0?r=xn(u):(o&=i,o!==0&&(r=xn(o)))}else i=n&~l,i!==0?r=xn(i):o!==0&&(r=xn(o));if(r===0)return 0;if(t!==0&&t!==r&&!(t&l)&&(l=r&-r,o=t&-t,l>=o||l===16&&(o&4194240)!==0))return t;if(r&4&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function bn(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-Le(t),e[t]=n}function Bf(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=zn),Bu=" ",Hu=!1;function da(e,t){switch(e){case"keyup":return hd.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function pa(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Ut=!1;function wd(e,t){switch(e){case"compositionend":return pa(t);case"keypress":return t.which!==32?null:(Hu=!0,Bu);case"textInput":return e=t.data,e===Bu&&Hu?null:e;default:return null}}function Sd(e,t){if(Ut)return e==="compositionend"||!Si&&da(e,t)?(e=ca(),Or=hi=rt=null,Ut=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=Yu(n)}}function ha(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?ha(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function ya(){for(var e=window,t=Wr();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=Wr(e.document)}return t}function ki(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function zd(e){var t=ya(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&ha(n.ownerDocument.documentElement,n)){if(r!==null&&ki(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var l=n.textContent.length,o=Math.min(r.start,l);r=r.end===void 0?o:Math.min(r.end,l),!e.extend&&o>r&&(l=r,r=o,o=l),l=Gu(n,o);var i=Gu(n,r);l&&i&&(e.rangeCount!==1||e.anchorNode!==l.node||e.anchorOffset!==l.offset||e.focusNode!==i.node||e.focusOffset!==i.offset)&&(t=t.createRange(),t.setStart(l.node,l.offset),e.removeAllRanges(),o>r?(e.addRange(t),e.extend(i.node,i.offset)):(t.setEnd(i.node,i.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Vt=null,Oo=null,Rn=null,Do=!1;function Xu(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;Do||Vt==null||Vt!==Wr(r)||(r=Vt,"selectionStart"in r&&ki(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Rn&&Bn(Rn,r)||(Rn=r,r=Xr(Oo,"onSelect"),0Bt||(e.current=Vo[Bt],Vo[Bt]=null,Bt--)}function M(e,t){Bt++,Vo[Bt]=e.current,e.current=t}var mt={},ne=vt(mt),ae=vt(!1),Nt=mt;function nn(e,t){var n=e.type.contextTypes;if(!n)return mt;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var l={},o;for(o in n)l[o]=t[o];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=l),l}function ce(e){return e=e.childContextTypes,e!=null}function qr(){D(ae),D(ne)}function ls(e,t,n){if(ne.current!==mt)throw Error(h(168));M(ne,t),M(ae,n)}function Na(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var l in r)if(!(l in t))throw Error(h(108,_f(e)||"Unknown",l));return U({},n,r)}function Jr(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||mt,Nt=ne.current,M(ne,e),M(ae,ae.current),!0}function os(e,t,n){var r=e.stateNode;if(!r)throw Error(h(169));n?(e=Na(e,t,Nt),r.__reactInternalMemoizedMergedChildContext=e,D(ae),D(ne),M(ne,e)):D(ae),M(ae,n)}var We=null,hl=!1,lo=!1;function Pa(e){We===null?We=[e]:We.push(e)}function Ud(e){hl=!0,Pa(e)}function ht(){if(!lo&&We!==null){lo=!0;var e=0,t=L;try{var n=We;for(L=1;e>=i,l-=i,Be=1<<32-Le(t)+l|n<T?(B=_,_=null):B=_.sibling;var P=g(f,_,d[T],p);if(P===null){_===null&&(_=B);break}e&&_&&P.alternate===null&&t(f,_),a=o(P,a,T),E===null?y=P:E.sibling=P,E=P,_=B}if(T===d.length)return n(f,_),I&&St(f,T),y;if(_===null){for(;TT?(B=_,_=null):B=_.sibling;var Oe=g(f,_,P.value,p);if(Oe===null){_===null&&(_=B);break}e&&_&&Oe.alternate===null&&t(f,_),a=o(Oe,a,T),E===null?y=Oe:E.sibling=Oe,E=Oe,_=B}if(P.done)return n(f,_),I&&St(f,T),y;if(_===null){for(;!P.done;T++,P=d.next())P=v(f,P.value,p),P!==null&&(a=o(P,a,T),E===null?y=P:E.sibling=P,E=P);return I&&St(f,T),y}for(_=r(f,_);!P.done;T++,P=d.next())P=w(_,f,T,P.value,p),P!==null&&(e&&P.alternate!==null&&_.delete(P.key===null?T:P.key),a=o(P,a,T),E===null?y=P:E.sibling=P,E=P);return e&&_.forEach(function(Ll){return t(f,Ll)}),I&&St(f,T),y}function F(f,a,d,p){if(typeof d=="object"&&d!==null&&d.type===jt&&d.key===null&&(d=d.props.children),typeof d=="object"&&d!==null){switch(d.$$typeof){case pr:e:{for(var y=d.key,E=a;E!==null;){if(E.key===y){if(y=d.type,y===jt){if(E.tag===7){n(f,E.sibling),a=l(E,d.props.children),a.return=f,f=a;break e}}else if(E.elementType===y||typeof y=="object"&&y!==null&&y.$$typeof===be&&ss(y)===E.type){n(f,E.sibling),a=l(E,d.props),a.ref=wn(f,E,d),a.return=f,f=a;break e}n(f,E);break}else t(f,E);E=E.sibling}d.type===jt?(a=Tt(d.props.children,f.mode,p,d.key),a.return=f,f=a):(p=$r(d.type,d.key,d.props,null,f.mode,p),p.ref=wn(f,a,d),p.return=f,f=p)}return i(f);case At:e:{for(E=d.key;a!==null;){if(a.key===E)if(a.tag===4&&a.stateNode.containerInfo===d.containerInfo&&a.stateNode.implementation===d.implementation){n(f,a.sibling),a=l(a,d.children||[]),a.return=f,f=a;break e}else{n(f,a);break}else t(f,a);a=a.sibling}a=po(d,f.mode,p),a.return=f,f=a}return i(f);case be:return E=d._init,F(f,a,E(d._payload),p)}if(_n(d))return S(f,a,d,p);if(gn(d))return C(f,a,d,p);Pr(f,d)}return typeof d=="string"&&d!==""||typeof d=="number"?(d=""+d,a!==null&&a.tag===6?(n(f,a.sibling),a=l(a,d),a.return=f,f=a):(n(f,a),a=fo(d,f.mode,p),a.return=f,f=a),i(f)):n(f,a)}return F}var ln=Ma(!0),Oa=Ma(!1),tl=vt(null),nl=null,Kt=null,xi=null;function Ti(){xi=Kt=nl=null}function Ni(e){var t=tl.current;D(tl),e._currentValue=t}function Bo(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function bt(e,t){nl=e,xi=Kt=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&t&&(se=!0),e.firstContext=null)}function Ce(e){var t=e._currentValue;if(xi!==e)if(e={context:e,memoizedValue:t,next:null},Kt===null){if(nl===null)throw Error(h(308));Kt=e,nl.dependencies={lanes:0,firstContext:e}}else Kt=Kt.next=e;return t}var Ct=null;function Pi(e){Ct===null?Ct=[e]:Ct.push(e)}function Da(e,t,n,r){var l=t.interleaved;return l===null?(n.next=n,Pi(t)):(n.next=l.next,l.next=n),t.interleaved=n,Ge(e,r)}function Ge(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var et=!1;function zi(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Ia(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function Qe(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function at(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,z&2){var l=r.pending;return l===null?t.next=t:(t.next=l.next,l.next=t),r.pending=t,Ge(e,n)}return l=r.interleaved,l===null?(t.next=t,Pi(r)):(t.next=l.next,l.next=t),r.interleaved=t,Ge(e,n)}function Ir(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,mi(e,n)}}function as(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var l=null,o=null;if(n=n.firstBaseUpdate,n!==null){do{var i={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};o===null?l=o=i:o=o.next=i,n=n.next}while(n!==null);o===null?l=o=t:o=o.next=t}else l=o=t;n={baseState:r.baseState,firstBaseUpdate:l,lastBaseUpdate:o,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function rl(e,t,n,r){var l=e.updateQueue;et=!1;var o=l.firstBaseUpdate,i=l.lastBaseUpdate,u=l.shared.pending;if(u!==null){l.shared.pending=null;var s=u,c=s.next;s.next=null,i===null?o=c:i.next=c,i=s;var m=e.alternate;m!==null&&(m=m.updateQueue,u=m.lastBaseUpdate,u!==i&&(u===null?m.firstBaseUpdate=c:u.next=c,m.lastBaseUpdate=s))}if(o!==null){var v=l.baseState;i=0,m=c=s=null,u=o;do{var g=u.lane,w=u.eventTime;if((r&g)===g){m!==null&&(m=m.next={eventTime:w,lane:0,tag:u.tag,payload:u.payload,callback:u.callback,next:null});e:{var S=e,C=u;switch(g=t,w=n,C.tag){case 1:if(S=C.payload,typeof S=="function"){v=S.call(w,v,g);break e}v=S;break e;case 3:S.flags=S.flags&-65537|128;case 0:if(S=C.payload,g=typeof S=="function"?S.call(w,v,g):S,g==null)break e;v=U({},v,g);break e;case 2:et=!0}}u.callback!==null&&u.lane!==0&&(e.flags|=64,g=l.effects,g===null?l.effects=[u]:g.push(u))}else w={eventTime:w,lane:g,tag:u.tag,payload:u.payload,callback:u.callback,next:null},m===null?(c=m=w,s=v):m=m.next=w,i|=g;if(u=u.next,u===null){if(u=l.shared.pending,u===null)break;g=u,u=g.next,g.next=null,l.lastBaseUpdate=g,l.shared.pending=null}}while(!0);if(m===null&&(s=v),l.baseState=s,l.firstBaseUpdate=c,l.lastBaseUpdate=m,t=l.shared.interleaved,t!==null){l=t;do i|=l.lane,l=l.next;while(l!==t)}else o===null&&(l.shared.lanes=0);Lt|=i,e.lanes=i,e.memoizedState=v}}function cs(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=io.transition;io.transition={};try{e(!1),t()}finally{L=n,io.transition=r}}function Ja(){return _e().memoizedState}function Bd(e,t,n){var r=ft(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},ba(e))ec(t,n);else if(n=Da(e,t,n,r),n!==null){var l=oe();Re(n,e,r,l),tc(n,t,r)}}function Hd(e,t,n){var r=ft(e),l={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(ba(e))ec(t,l);else{var o=e.alternate;if(e.lanes===0&&(o===null||o.lanes===0)&&(o=t.lastRenderedReducer,o!==null))try{var i=t.lastRenderedState,u=o(i,n);if(l.hasEagerState=!0,l.eagerState=u,Me(u,i)){var s=t.interleaved;s===null?(l.next=l,Pi(t)):(l.next=s.next,s.next=l),t.interleaved=l;return}}catch{}finally{}n=Da(e,t,l,r),n!==null&&(l=oe(),Re(n,e,r,l),tc(n,t,r))}}function ba(e){var t=e.alternate;return e===j||t!==null&&t===j}function ec(e,t){Mn=ol=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function tc(e,t,n){if(n&4194240){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,mi(e,n)}}var il={readContext:Ce,useCallback:b,useContext:b,useEffect:b,useImperativeHandle:b,useInsertionEffect:b,useLayoutEffect:b,useMemo:b,useReducer:b,useRef:b,useState:b,useDebugValue:b,useDeferredValue:b,useTransition:b,useMutableSource:b,useSyncExternalStore:b,useId:b,unstable_isNewReconciler:!1},Qd={readContext:Ce,useCallback:function(e,t){return Fe().memoizedState=[e,t===void 0?null:t],e},useContext:Ce,useEffect:ds,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,Ar(4194308,4,Ya.bind(null,t,e),n)},useLayoutEffect:function(e,t){return Ar(4194308,4,e,t)},useInsertionEffect:function(e,t){return Ar(4,2,e,t)},useMemo:function(e,t){var n=Fe();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Fe();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Bd.bind(null,j,e),[r.memoizedState,e]},useRef:function(e){var t=Fe();return e={current:e},t.memoizedState=e},useState:fs,useDebugValue:Ai,useDeferredValue:function(e){return Fe().memoizedState=e},useTransition:function(){var e=fs(!1),t=e[0];return e=Wd.bind(null,e[1]),Fe().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=j,l=Fe();if(I){if(n===void 0)throw Error(h(407));n=n()}else{if(n=t(),G===null)throw Error(h(349));zt&30||Ua(r,t,n)}l.memoizedState=n;var o={value:n,getSnapshot:t};return l.queue=o,ds($a.bind(null,r,o,e),[e]),r.flags|=2048,qn(9,Va.bind(null,r,o,n,t),void 0,null),n},useId:function(){var e=Fe(),t=G.identifierPrefix;if(I){var n=He,r=Be;n=(r&~(1<<32-Le(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Xn++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=i.createElement(n,{is:r.is}):(e=i.createElement(n),n==="select"&&(i=e,r.multiple?i.multiple=!0:r.size&&(i.size=r.size))):e=i.createElementNS(e,n),e[Ae]=t,e[Kn]=r,fc(e,t,!1,!1),t.stateNode=e;e:{switch(i=xo(n,r),n){case"dialog":O("cancel",e),O("close",e),l=r;break;case"iframe":case"object":case"embed":O("load",e),l=r;break;case"video":case"audio":for(l=0;lsn&&(t.flags|=128,r=!0,Sn(o,!1),t.lanes=4194304)}else{if(!r)if(e=ll(i),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Sn(o,!0),o.tail===null&&o.tailMode==="hidden"&&!i.alternate&&!I)return ee(t),null}else 2*$()-o.renderingStartTime>sn&&n!==1073741824&&(t.flags|=128,r=!0,Sn(o,!1),t.lanes=4194304);o.isBackwards?(i.sibling=t.child,t.child=i):(n=o.last,n!==null?n.sibling=i:t.child=i,o.last=i)}return o.tail!==null?(t=o.tail,o.rendering=t,o.tail=t.sibling,o.renderingStartTime=$(),t.sibling=null,n=A.current,M(A,r?n&1|2:n&1),t):(ee(t),null);case 22:case 23:return Bi(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&t.mode&1?de&1073741824&&(ee(t),t.subtreeFlags&6&&(t.flags|=8192)):ee(t),null;case 24:return null;case 25:return null}throw Error(h(156,t.tag))}function bd(e,t){switch(Ci(t),t.tag){case 1:return ce(t.type)&&qr(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return on(),D(ae),D(ne),Mi(),e=t.flags,e&65536&&!(e&128)?(t.flags=e&-65537|128,t):null;case 5:return Ri(t),null;case 13:if(D(A),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(h(340));rn()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return D(A),null;case 4:return on(),null;case 10:return Ni(t.type._context),null;case 22:case 23:return Bi(),null;case 24:return null;default:return null}}var Lr=!1,te=!1,ep=typeof WeakSet=="function"?WeakSet:Set,k=null;function Yt(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){V(e,t,r)}else n.current=null}function Jo(e,t,n){try{n()}catch(r){V(e,t,r)}}var Cs=!1;function tp(e,t){if(Io=Yr,e=ya(),ki(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var l=r.anchorOffset,o=r.focusNode;r=r.focusOffset;try{n.nodeType,o.nodeType}catch{n=null;break e}var i=0,u=-1,s=-1,c=0,m=0,v=e,g=null;t:for(;;){for(var w;v!==n||l!==0&&v.nodeType!==3||(u=i+l),v!==o||r!==0&&v.nodeType!==3||(s=i+r),v.nodeType===3&&(i+=v.nodeValue.length),(w=v.firstChild)!==null;)g=v,v=w;for(;;){if(v===e)break t;if(g===n&&++c===l&&(u=i),g===o&&++m===r&&(s=i),(w=v.nextSibling)!==null)break;v=g,g=v.parentNode}v=w}n=u===-1||s===-1?null:{start:u,end:s}}else n=null}n=n||{start:0,end:0}}else n=null;for(Fo={focusedElem:e,selectionRange:n},Yr=!1,k=t;k!==null;)if(t=k,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,k=e;else for(;k!==null;){t=k;try{var S=t.alternate;if(t.flags&1024)switch(t.tag){case 0:case 11:case 15:break;case 1:if(S!==null){var C=S.memoizedProps,F=S.memoizedState,f=t.stateNode,a=f.getSnapshotBeforeUpdate(t.elementType===t.type?C:Ne(t.type,C),F);f.__reactInternalSnapshotBeforeUpdate=a}break;case 3:var d=t.stateNode.containerInfo;d.nodeType===1?d.textContent="":d.nodeType===9&&d.documentElement&&d.removeChild(d.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(h(163))}}catch(p){V(t,t.return,p)}if(e=t.sibling,e!==null){e.return=t.return,k=e;break}k=t.return}return S=Cs,Cs=!1,S}function On(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var l=r=r.next;do{if((l.tag&e)===e){var o=l.destroy;l.destroy=void 0,o!==void 0&&Jo(t,n,o)}l=l.next}while(l!==r)}}function Sl(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function bo(e){var t=e.ref;if(t!==null){var n=e.stateNode;switch(e.tag){case 5:e=n;break;default:e=n}typeof t=="function"?t(e):t.current=e}}function mc(e){var t=e.alternate;t!==null&&(e.alternate=null,mc(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[Ae],delete t[Kn],delete t[Uo],delete t[Ad],delete t[jd])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function gc(e){return e.tag===5||e.tag===3||e.tag===4}function _s(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||gc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function ei(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=Zr));else if(r!==4&&(e=e.child,e!==null))for(ei(e,t,n),e=e.sibling;e!==null;)ei(e,t,n),e=e.sibling}function ti(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ti(e,t,n),e=e.sibling;e!==null;)ti(e,t,n),e=e.sibling}var X=null,Pe=!1;function Je(e,t,n){for(n=n.child;n!==null;)vc(e,t,n),n=n.sibling}function vc(e,t,n){if(je&&typeof je.onCommitFiberUnmount=="function")try{je.onCommitFiberUnmount(dl,n)}catch{}switch(n.tag){case 5:te||Yt(n,t);case 6:var r=X,l=Pe;X=null,Je(e,t,n),X=r,Pe=l,X!==null&&(Pe?(e=X,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):X.removeChild(n.stateNode));break;case 18:X!==null&&(Pe?(e=X,n=n.stateNode,e.nodeType===8?ro(e.parentNode,n):e.nodeType===1&&ro(e,n),$n(e)):ro(X,n.stateNode));break;case 4:r=X,l=Pe,X=n.stateNode.containerInfo,Pe=!0,Je(e,t,n),X=r,Pe=l;break;case 0:case 11:case 14:case 15:if(!te&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){l=r=r.next;do{var o=l,i=o.destroy;o=o.tag,i!==void 0&&(o&2||o&4)&&Jo(n,t,i),l=l.next}while(l!==r)}Je(e,t,n);break;case 1:if(!te&&(Yt(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(u){V(n,t,u)}Je(e,t,n);break;case 21:Je(e,t,n);break;case 22:n.mode&1?(te=(r=te)||n.memoizedState!==null,Je(e,t,n),te=r):Je(e,t,n);break;default:Je(e,t,n)}}function xs(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new ep),t.forEach(function(r){var l=cp.bind(null,e,r);n.has(r)||(n.add(r),r.then(l,l))})}}function Te(e,t){var n=t.deletions;if(n!==null)for(var r=0;rl&&(l=i),r&=~o}if(r=l,r=$()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*rp(r/1960))-r,10e?16:e,lt===null)var r=!1;else{if(e=lt,lt=null,al=0,z&6)throw Error(h(331));var l=z;for(z|=4,k=e.current;k!==null;){var o=k,i=o.child;if(k.flags&16){var u=o.deletions;if(u!==null){for(var s=0;s$()-$i?xt(e,0):Vi|=n),fe(e,t)}function _c(e,t){t===0&&(e.mode&1?(t=hr,hr<<=1,!(hr&130023424)&&(hr=4194304)):t=1);var n=oe();e=Ge(e,t),e!==null&&(bn(e,t,n),fe(e,n))}function ap(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),_c(e,n)}function cp(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,l=e.memoizedState;l!==null&&(n=l.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(h(314))}r!==null&&r.delete(t),_c(e,n)}var xc;xc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||ae.current)se=!0;else{if(!(e.lanes&n)&&!(t.flags&128))return se=!1,qd(e,t,n);se=!!(e.flags&131072)}else se=!1,I&&t.flags&1048576&&za(t,el,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;jr(e,t),e=t.pendingProps;var l=nn(t,ne.current);bt(t,n),l=Di(null,t,r,e,l,n);var o=Ii();return t.flags|=1,typeof l=="object"&&l!==null&&typeof l.render=="function"&&l.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,ce(r)?(o=!0,Jr(t)):o=!1,t.memoizedState=l.state!==null&&l.state!==void 0?l.state:null,zi(t),l.updater=wl,t.stateNode=l,l._reactInternals=t,Qo(t,r,e,n),t=Go(null,t,r,!0,o,n)):(t.tag=0,I&&o&&Ei(t),le(null,t,l,n),t=t.child),t;case 16:r=t.elementType;e:{switch(jr(e,t),e=t.pendingProps,l=r._init,r=l(r._payload),t.type=r,l=t.tag=dp(r),e=Ne(r,e),l){case 0:t=Yo(null,t,r,e,n);break e;case 1:t=Ss(null,t,r,e,n);break e;case 11:t=ys(null,t,r,e,n);break e;case 14:t=ws(null,t,r,Ne(r.type,e),n);break e}throw Error(h(306,r,""))}return t;case 0:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ne(r,l),Yo(e,t,r,l,n);case 1:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ne(r,l),Ss(e,t,r,l,n);case 3:e:{if(sc(t),e===null)throw Error(h(387));r=t.pendingProps,o=t.memoizedState,l=o.element,Ia(e,t),rl(t,r,null,n);var i=t.memoizedState;if(r=i.element,o.isDehydrated)if(o={element:r,isDehydrated:!1,cache:i.cache,pendingSuspenseBoundaries:i.pendingSuspenseBoundaries,transitions:i.transitions},t.updateQueue.baseState=o,t.memoizedState=o,t.flags&256){l=un(Error(h(423)),t),t=ks(e,t,r,n,l);break e}else if(r!==l){l=un(Error(h(424)),t),t=ks(e,t,r,n,l);break e}else for(pe=st(t.stateNode.containerInfo.firstChild),me=t,I=!0,ze=null,n=Oa(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(rn(),r===l){t=Xe(e,t,n);break e}le(e,t,r,n)}t=t.child}return t;case 5:return Fa(t),e===null&&Wo(t),r=t.type,l=t.pendingProps,o=e!==null?e.memoizedProps:null,i=l.children,Ao(r,l)?i=null:o!==null&&Ao(r,o)&&(t.flags|=32),uc(e,t),le(e,t,i,n),t.child;case 6:return e===null&&Wo(t),null;case 13:return ac(e,t,n);case 4:return Li(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=ln(t,null,r,n):le(e,t,r,n),t.child;case 11:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ne(r,l),ys(e,t,r,l,n);case 7:return le(e,t,t.pendingProps,n),t.child;case 8:return le(e,t,t.pendingProps.children,n),t.child;case 12:return le(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,l=t.pendingProps,o=t.memoizedProps,i=l.value,M(tl,r._currentValue),r._currentValue=i,o!==null)if(Me(o.value,i)){if(o.children===l.children&&!ae.current){t=Xe(e,t,n);break e}}else for(o=t.child,o!==null&&(o.return=t);o!==null;){var u=o.dependencies;if(u!==null){i=o.child;for(var s=u.firstContext;s!==null;){if(s.context===r){if(o.tag===1){s=Qe(-1,n&-n),s.tag=2;var c=o.updateQueue;if(c!==null){c=c.shared;var m=c.pending;m===null?s.next=s:(s.next=m.next,m.next=s),c.pending=s}}o.lanes|=n,s=o.alternate,s!==null&&(s.lanes|=n),Bo(o.return,n,t),u.lanes|=n;break}s=s.next}}else if(o.tag===10)i=o.type===t.type?null:o.child;else if(o.tag===18){if(i=o.return,i===null)throw Error(h(341));i.lanes|=n,u=i.alternate,u!==null&&(u.lanes|=n),Bo(i,n,t),i=o.sibling}else i=o.child;if(i!==null)i.return=o;else for(i=o;i!==null;){if(i===t){i=null;break}if(o=i.sibling,o!==null){o.return=i.return,i=o;break}i=i.return}o=i}le(e,t,l.children,n),t=t.child}return t;case 9:return l=t.type,r=t.pendingProps.children,bt(t,n),l=Ce(l),r=r(l),t.flags|=1,le(e,t,r,n),t.child;case 14:return r=t.type,l=Ne(r,t.pendingProps),l=Ne(r.type,l),ws(e,t,r,l,n);case 15:return oc(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,l=t.pendingProps,l=t.elementType===r?l:Ne(r,l),jr(e,t),t.tag=1,ce(r)?(e=!0,Jr(t)):e=!1,bt(t,n),nc(t,r,l),Qo(t,r,l,n),Go(null,t,r,!0,e,n);case 19:return cc(e,t,n);case 22:return ic(e,t,n)}throw Error(h(156,t.tag))};function Tc(e,t){return bs(e,t)}function fp(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function ke(e,t,n,r){return new fp(e,t,n,r)}function Qi(e){return e=e.prototype,!(!e||!e.isReactComponent)}function dp(e){if(typeof e=="function")return Qi(e)?1:0;if(e!=null){if(e=e.$$typeof,e===ci)return 11;if(e===fi)return 14}return 2}function dt(e,t){var n=e.alternate;return n===null?(n=ke(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function $r(e,t,n,r,l,o){var i=2;if(r=e,typeof e=="function")Qi(e)&&(i=1);else if(typeof e=="string")i=5;else e:switch(e){case jt:return Tt(n.children,l,o,t);case ai:i=8,l|=8;break;case go:return e=ke(12,n,t,l|2),e.elementType=go,e.lanes=o,e;case vo:return e=ke(13,n,t,l),e.elementType=vo,e.lanes=o,e;case ho:return e=ke(19,n,t,l),e.elementType=ho,e.lanes=o,e;case Fs:return El(n,l,o,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case Ds:i=10;break e;case Is:i=9;break e;case ci:i=11;break e;case fi:i=14;break e;case be:i=16,r=null;break e}throw Error(h(130,e==null?e:typeof e,""))}return t=ke(i,n,t,l),t.elementType=e,t.type=r,t.lanes=o,t}function Tt(e,t,n,r){return e=ke(7,e,r,t),e.lanes=n,e}function El(e,t,n,r){return e=ke(22,e,r,t),e.elementType=Fs,e.lanes=n,e.stateNode={isHidden:!1},e}function fo(e,t,n){return e=ke(6,e,null,t),e.lanes=n,e}function po(e,t,n){return t=ke(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function pp(e,t,n,r,l){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Xl(0),this.expirationTimes=Xl(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Xl(0),this.identifierPrefix=r,this.onRecoverableError=l,this.mutableSourceEagerHydrationData=null}function Ki(e,t,n,r,l,o,i,u,s){return e=new pp(e,t,n,u,s),t===1?(t=1,o===!0&&(t|=8)):t=0,o=ke(3,null,null,t),e.current=o,o.stateNode=e,o.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},zi(o),e}function mp(e,t,n){var r=3{"use strict";function Rc(){if(!(typeof __REACT_DEVTOOLS_GLOBAL_HOOK__>"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(Rc)}catch(e){console.error(e)}}Rc(),Mc.exports=Lc()});var Ic=Ve(Zi=>{"use strict";var Dc=Oc();Zi.createRoot=Dc.createRoot,Zi.hydrateRoot=Dc.hydrateRoot;var Up});var Ac=Ve(Nl=>{"use strict";var wp=ur(),Sp=Symbol.for("react.element"),kp=Symbol.for("react.fragment"),Ep=Object.prototype.hasOwnProperty,Cp=wp.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,_p={key:!0,ref:!0,__self:!0,__source:!0};function Fc(e,t,n){var r,l={},o=null,i=null;n!==void 0&&(o=""+n),t.key!==void 0&&(o=""+t.key),t.ref!==void 0&&(i=t.ref);for(r in t)Ep.call(t,r)&&!_p.hasOwnProperty(r)&&(l[r]=t[r]);if(e&&e.defaultProps)for(r in t=e.defaultProps,t)l[r]===void 0&&(l[r]=t[r]);return{$$typeof:Sp,type:e,key:o,ref:i,props:l,_owner:Cp.current}}Nl.Fragment=kp;Nl.jsx=Fc;Nl.jsxs=Fc});var qi=Ve((Wp,jc)=>{"use strict";jc.exports=Ac()});var W=rr(ur(),1),Qc=rr(Ic(),1),x=rr(qi(),1),Uc="openai:set_globals",Vc="mcp-tool-manager-style",Kc="mcp-tool-manager-root",Yc=10,zl={status:"info",activeServers:[],message:"Search the catalog to find MCP servers you can enable."};function xp(){if(typeof document>"u"||document.getElementById(Vc))return;let e=document.createElement("style");e.id=Vc,e.textContent=`
+ :root {
+ color-scheme: light dark;
+ font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
+ }
+ #${Kc} {
+ height: 100%;
+ }
+ .mcp-tool-manager {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ height: 100%;
+ box-sizing: border-box;
+ padding: 1.25rem;
+ background: transparent;
+ color: var(--mcp-tool-manager-fg, inherit);
+ }
+ .mcp-tool-manager header h1 {
+ margin: 0;
+ font-size: 1.25rem;
+ font-weight: 600;
+ }
+ .mcp-tool-manager header p {
+ margin: 0.25rem 0 0;
+ color: var(--mcp-tool-manager-muted, rgba(120, 120, 120, 0.9));
+ font-size: 0.95rem;
+ line-height: 1.4;
+ }
+ .mcp-status {
+ padding: 0.75rem 1rem;
+ border-radius: 0.75rem;
+ font-size: 0.95rem;
+ line-height: 1.3;
+ }
+ .mcp-status.success {
+ background: rgba(16, 185, 129, 0.14);
+ color: rgba(6, 95, 70, 0.95);
+ }
+ .mcp-status.error {
+ background: rgba(248, 113, 113, 0.18);
+ color: rgba(153, 27, 27, 0.95);
+ }
+ .mcp-status.info {
+ background: rgba(59, 130, 246, 0.16);
+ color: rgba(30, 64, 175, 0.95);
+ }
+ .mcp-search-card {
+ border: 1px solid rgba(120, 120, 120, 0.2);
+ border-radius: 1rem;
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ background: color-mix(in srgb, currentColor 4%, transparent);
+ box-shadow: 0 2px 8px rgba(15, 15, 15, 0.04);
+ }
+ .mcp-search-card form {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ }
+ .mcp-search-row {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ }
+ .mcp-search-row input[type="text"] {
+ flex: 1 1 220px;
+ padding: 0.55rem 0.75rem;
+ border-radius: 0.65rem;
+ border: 1px solid rgba(120, 120, 120, 0.35);
+ font-size: 0.95rem;
+ background: rgba(255, 255, 255, 0.04);
+ color: inherit;
+ }
+ .mcp-search-row button {
+ padding: 0.55rem 1rem;
+ border-radius: 0.65rem;
+ border: none;
+ font-weight: 600;
+ font-size: 0.95rem;
+ background: rgba(59, 130, 246, 0.92);
+ color: #fff;
+ cursor: pointer;
+ }
+ .mcp-search-row button[disabled],
+ .mcp-action-button[disabled] {
+ opacity: 0.55;
+ cursor: not-allowed;
+ }
+ .mcp-active-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+ }
+ .mcp-chip {
+ padding: 0.35rem 0.65rem;
+ border-radius: 999px;
+ font-size: 0.85rem;
+ background: rgba(59, 130, 246, 0.12);
+ color: rgba(37, 99, 235, 0.95);
+ }
+ .mcp-card-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 1rem;
+ }
+ .mcp-card {
+ border: 1px solid rgba(120, 120, 120, 0.18);
+ border-radius: 1rem;
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ background: color-mix(in srgb, currentColor 3%, transparent);
+ box-shadow: 0 2px 6px rgba(15, 15, 15, 0.04);
+ }
+ .mcp-card h2 {
+ margin: 0;
+ font-size: 1.05rem;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+ }
+ .mcp-badge {
+ font-size: 0.75rem;
+ font-weight: 600;
+ padding: 0.2rem 0.45rem;
+ border-radius: 999px;
+ background: rgba(16, 185, 129, 0.18);
+ color: rgba(6, 95, 70, 0.9);
+ }
+ .mcp-card p {
+ margin: 0;
+ font-size: 0.9rem;
+ line-height: 1.45;
+ color: var(--mcp-tool-manager-muted, rgba(120, 120, 120, 0.9));
+ }
+ .mcp-card dl {
+ margin: 0;
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 0.35rem 0.75rem;
+ font-size: 0.85rem;
+ }
+ .mcp-card dt {
+ font-weight: 600;
+ color: rgba(100, 100, 100, 0.9);
+ }
+ .mcp-card dd {
+ margin: 0;
+ color: inherit;
+ }
+ .mcp-card .tool-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ margin-top: 0.25rem;
+ }
+ .mcp-card .tool-list span {
+ font-size: 0.8rem;
+ color: rgba(120, 120, 120, 0.9);
+ }
+ .mcp-card-actions {
+ margin-top: auto;
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ }
+ .mcp-action-button {
+ flex: 0 0 auto;
+ border-radius: 0.6rem;
+ border: none;
+ padding: 0.5rem 0.9rem;
+ font-size: 0.9rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: transform 0.1s ease;
+ }
+ .mcp-action-button.primary {
+ background: rgba(16, 185, 129, 0.9);
+ color: white;
+ }
+ .mcp-action-button.secondary {
+ background: rgba(248, 113, 113, 0.9);
+ color: white;
+ }
+ .mcp-empty {
+ font-size: 0.9rem;
+ color: var(--mcp-tool-manager-muted, rgba(120, 120, 120, 0.9));
+ padding: 0.5rem 0;
+ }
+ .mcp-footer {
+ font-size: 0.75rem;
+ color: rgba(120, 120, 120, 0.75);
+ text-align: right;
+ }
+ `,document.head.appendChild(e)}function Gc(e){return(0,W.useSyncExternalStore)(t=>{let n=r=>{let l=r;l.detail?.globals&&e in l.detail.globals&&t()};return window.addEventListener(Uc,n,{passive:!0}),()=>window.removeEventListener(Uc,n)},()=>window.openai?.[e],()=>{})}function Tp(){return Gc("toolOutput")}function Np(e){let t=Gc("widgetState"),[n,r]=(0,W.useState)(()=>t??(typeof e=="function"?e():e??null));(0,W.useEffect)(()=>{t!==void 0&&r(t)},[t]);let l=(0,W.useCallback)(o=>{r(i=>{let u=typeof o=="function"?o(i):o;return u!=null&&window.openai?.setWidgetState?.(u),u})},[]);return[n,l]}function eu(e){if(!e||e.length===0)return[];let t=new Set;return e.forEach(n=>{n&&t.add(n)}),Array.from(t).sort((n,r)=>n.localeCompare(r))}function $c(e){if(!e||typeof e!="object")return{name:""};let t=e,n=l=>{if(Array.isArray(l))return l.map(o=>String(o)).filter(Boolean)},r=Array.isArray(t.tools)?t.tools.map(l=>{if(!l||typeof l!="object")return null;let o=l,i=typeof o.name=="string"?o.name:void 0;return i?{name:i,description:typeof o.description=="string"?o.description:void 0}:null}).filter(Boolean):void 0;return{name:typeof t.name=="string"?t.name:"",description:typeof t.description=="string"?t.description:void 0,type:typeof t.type=="string"?t.type:void 0,remoteUrl:typeof t.remoteUrl=="string"?t.remoteUrl:typeof t.remote_url=="string"?t.remote_url:void 0,image:typeof t.image=="string"?t.image:void 0,longLived:typeof t.longLived=="boolean"?t.longLived:typeof t.long_lived=="boolean"?t.long_lived:void 0,requiredSecrets:n(t.requiredSecrets)??n(t.required_secrets)??n(t.requiredSecretsNames),configSchema:t.configSchema??t.config_schema,tools:r,oauthProviders:n(t.oauthProviders)??n(t.oauth_providers)}}function Pp(e){if(!e||typeof e!="object")return;let t=e,n=typeof t.type=="string"?t.type:"",r=n==="add"||n==="remove"||n==="find"?n:"find",o=(typeof t.status=="string"?t.status:"")==="error"?"error":"success";return{type:r,status:o,message:typeof t.message=="string"?t.message:void 0,server:typeof t.server=="string"?t.server:void 0}}function yt(e){if(e?.content){for(let t of e.content)if(typeof t?.text=="string"&&t.text.trim().length>0)return t.text.trim()}}function Ji(e){if(!e)return null;let t=e.structuredContent,n=t&&typeof t=="object"&&!Array.isArray(t)?t:null;if(!n){let c=yt(e)??void 0;return{...zl,status:e.isError?"error":"info",message:c}}let r=typeof n.status=="string"?n.status:void 0,l=Array.isArray(n.results)?n.results.map(c=>$c(c)):void 0,o=eu(Array.isArray(n.activeServers)?n.activeServers:Array.isArray(n.active_servers)?n.active_servers:void 0),i=n.server&&typeof n.server=="object"?$c(n.server):void 0,u=Pp(n.lastAction),s=typeof n.message=="string"?n.message:typeof n.info=="string"?n.info:void 0;return s||(s=yt(e)),{view:typeof n.view=="string"?n.view:void 0,sourceTool:typeof n.sourceTool=="string"?n.sourceTool:void 0,status:r??(e.isError?"error":void 0),message:s,query:typeof n.query=="string"?n.query:void 0,limit:typeof n.limit=="number"?n.limit:void 0,totalMatches:typeof n.totalMatches=="number"?n.totalMatches:void 0,activeServers:o,results:l,server:i,lastAction:u,timestamp:typeof n.timestamp=="string"?n.timestamp:void 0,error:typeof n.error=="string"?n.error:void 0}}function Pl(e){let t=e??zl,n=eu(t.activeServers),r=new Set(n),l=(t.results??[]).map(i=>({...i,isActive:r.has(i.name)})),o=t.server?{...t.server,isActive:r.has(t.server.name)}:void 0;return{...t,activeServers:n,results:l,server:o}}function Wc(e,t,n){let r="find";return e.tool==="mcp-add"?r="add":e.tool==="mcp-remove"&&(r="remove"),{type:r,server:e.server,status:t==="error"?"error":"success",message:n}}function Bc(e,t,n){let r=e??zl;if(!t){let o=n.status??r.status??"info",i=n.message??r.message;return Pl({...r,status:o,message:i,lastAction:Wc(n,o,i)})}let l={...r,...t,activeServers:t.activeServers&&t.activeServers.length>0?eu(t.activeServers):r.activeServers,results:t.results??r.results,query:t.query??r.query,limit:t.limit??r.limit??Yc};return l.status=t.status??n.status??r.status??"info",l.message=t.message??n.message??r.message,t.lastAction||(l.lastAction=Wc(n,l.status??"info",l.message)),Pl(l)}function zp(e){if(!e)return"";try{return JSON.stringify({structuredContent:e.structuredContent??null,isError:!!e.isError,message:yt(e)??null})}catch{return String(Date.now())}}function Lp(e,t,n){if(t.message)return t.message;if(e==="mcp-find"){let r=t.message??n.query;return r?`Showing results for \u201C${r}\u201D.`:"Showing search results."}return e==="mcp-add"?t.server?`Added \u201C${t.server}\u201D.`:"Server added.":e==="mcp-remove"?t.server?`Removed \u201C${t.server}\u201D.`:"Server removed.":"Operation completed."}function bi(e){return e instanceof Error?e.message:typeof e=="string"?e:"Something went wrong. Please try again."}var Rp=()=>{let e=Tp(),t=(0,W.useMemo)(()=>zp(e),[e]),[n,r]=Np(()=>Ji(window.openai?.toolOutput)??zl),l=(0,W.useMemo)(()=>Pl(n),[n]),[o,i]=(0,W.useState)(()=>l.query??""),[u,s]=(0,W.useState)(null),[c,m]=(0,W.useState)(null),[v,g]=(0,W.useState)(!1);(0,W.useEffect)(()=>{l.query!==void 0&&i(l.query)},[l.query]),(0,W.useEffect)(()=>{if(!e)return;let p=Ji(e),E={tool:p?.sourceTool==="mcp-add"||p?.sourceTool==="mcp-remove"||p?.sourceTool==="mcp-find"?p.sourceTool:"mcp-find",server:p?.server?.name??p?.lastAction?.server,message:p?.message??yt(e)??void 0,status:p?.status??(e.isError?"error":void 0)};r(_=>Bc(_,p,E))},[r,e,t]);let w=(0,W.useCallback)(async(p,y,E)=>{if(!window.openai?.callTool)throw new Error("Tool calling is unavailable in this context.");let _=await window.openai.callTool(p,y),T=Ji(_),B=T?.status??(_.isError?"error":E.status??"success"),P=Pl(n),Oe=T?.message??yt(_)??Lp(p,E,P);return r(Ll=>Bc(Ll,T,{...E,tool:p,message:Oe,status:B})),_},[r,n]),S=(0,W.useCallback)(async p=>{p?.preventDefault();let y=o.trim();if(!y){m("Enter a name, description, or keyword to search the catalog.");return}m(null),g(!0);try{let E=await w("mcp-find",{query:y,limit:l.limit??Yc},{tool:"mcp-find",message:`Showing results for \u201C${y}\u201D.`,status:"success"});E.isError&&m(yt(E)??"The search tool returned an error.")}catch(E){m(bi(E))}finally{g(!1)}},[l.limit,w,o]),C=(0,W.useCallback)(async p=>{m(null),s(`add:${p}`);try{let y=await w("mcp-add",{name:p},{tool:"mcp-add",server:p,message:`Added \u201C${p}\u201D.`,status:"success"});y.isError&&m(yt(y)??`Failed to add \u201C${p}\u201D.`)}catch(y){m(bi(y))}finally{s(null)}},[w]),F=(0,W.useCallback)(async p=>{m(null),s(`remove:${p}`);try{let y=await w("mcp-remove",{name:p},{tool:"mcp-remove",server:p,message:`Removed \u201C${p}\u201D.`,status:"info"});y.isError&&m(yt(y)??`Failed to remove \u201C${p}\u201D.`)}catch(y){m(bi(y))}finally{s(null)}},[w]),f=l.activeServers,a=l.results??[],d=l.status==="error"?"error":l.status==="success"?"success":"info";return(0,x.jsxs)("div",{className:"mcp-tool-manager",role:"application",children:[(0,x.jsxs)("header",{children:[(0,x.jsx)("h1",{children:"MCP Tool Manager"}),(0,x.jsx)("p",{children:"Search the catalog, add servers you need, and disable ones you no longer use."})]}),(l.message||c)&&(0,x.jsx)("div",{className:`mcp-status ${c?"error":d}`,role:"status",children:c??l.message}),(0,x.jsxs)("section",{className:"mcp-search-card","aria-label":"Search catalog",children:[(0,x.jsxs)("form",{onSubmit:S,children:[(0,x.jsx)("label",{htmlFor:"mcp-search-input",children:"Search catalog servers"}),(0,x.jsxs)("div",{className:"mcp-search-row",children:[(0,x.jsx)("input",{id:"mcp-search-input",type:"text",placeholder:"Search by server name, capability, or description",value:o,onChange:p=>i(p.target.value),autoComplete:"off",disabled:v}),(0,x.jsx)("button",{type:"submit",disabled:v,children:v?"Searching\u2026":"Search"})]})]}),(0,x.jsxs)("div",{children:[(0,x.jsx)("strong",{children:"Active servers:"}),f.length===0?(0,x.jsx)("span",{className:"mcp-empty",children:" None enabled yet."}):(0,x.jsx)("div",{className:"mcp-active-list","aria-live":"polite",children:f.map(p=>(0,x.jsx)("span",{className:"mcp-chip",children:p},p))})]})]}),(0,x.jsx)("section",{"aria-label":"Search results",children:a.length===0?(0,x.jsx)("div",{className:"mcp-empty",children:l.query?`No servers matched \u201C${l.query}\u201D. Try a different keyword.`:"Start by searching to see available servers you can enable."}):(0,x.jsx)("div",{className:"mcp-card-list",children:a.map(p=>{let y=u===`add:${p.name}`,E=u===`remove:${p.name}`;return(0,x.jsxs)("article",{className:"mcp-card","aria-live":"polite",children:[(0,x.jsxs)("h2",{children:[p.name,p.isActive&&(0,x.jsx)("span",{className:"mcp-badge",children:"Active"})]}),(0,x.jsx)("p",{children:p.description??"No description provided in the catalog."}),(0,x.jsxs)("dl",{children:[p.type&&(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)("dt",{children:"Type"}),(0,x.jsx)("dd",{children:p.type})]}),p.remoteUrl&&(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)("dt",{children:"Endpoint"}),(0,x.jsx)("dd",{children:p.remoteUrl})]}),p.image&&(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)("dt",{children:"Image"}),(0,x.jsx)("dd",{children:p.image})]}),typeof p.longLived=="boolean"&&(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)("dt",{children:"Lifecycle"}),(0,x.jsx)("dd",{children:p.longLived?"Long-lived":"Ephemeral"})]}),p.requiredSecrets&&p.requiredSecrets.length>0&&(0,x.jsxs)(x.Fragment,{children:[(0,x.jsx)("dt",{children:"Secrets"}),(0,x.jsx)("dd",{children:p.requiredSecrets.join(", ")})]})]}),p.tools&&p.tools.length>0&&(0,x.jsxs)("div",{className:"tool-list",children:[(0,x.jsx)("strong",{children:"Tools"}),p.tools.slice(0,3).map(_=>(0,x.jsxs)("span",{children:[_.name,_.description?` \u2014 ${_.description}`:""]},_.name)),p.tools.length>3&&(0,x.jsxs)("span",{children:["+",p.tools.length-3," more tools"]})]}),(0,x.jsxs)("div",{className:"mcp-card-actions",children:[(0,x.jsx)("button",{type:"button",className:"mcp-action-button primary",onClick:()=>C(p.name),disabled:p.isActive||u!==null,children:y?"Adding\u2026":p.isActive?"Already active":"Add server"}),(0,x.jsx)("button",{type:"button",className:"mcp-action-button secondary",onClick:()=>F(p.name),disabled:!p.isActive||u!==null,children:E?"Removing\u2026":"Remove server"})]})]},p.name)})})}),(0,x.jsx)("footer",{className:"mcp-footer","aria-live":"polite",children:l.timestamp?`Last updated ${new Date(l.timestamp).toLocaleString()}`:null})]})};function Hc(){xp();let e=document.getElementById(Kc);if(!e){console.error("MCP Tool Manager UI failed to mount: root element not found.");return}if(e.dataset.mounted==="true")return;e.dataset.mounted="true",(0,Qc.createRoot)(e).render((0,x.jsx)(Rp,{}))}typeof window<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>Hc(),{once:!0}):Hc());
+/*! Bundled license information:
+
+react/cjs/react.production.min.js:
+ (**
+ * @license React
+ * react.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *)
+
+scheduler/cjs/scheduler.production.min.js:
+ (**
+ * @license React
+ * scheduler.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *)
+
+react-dom/cjs/react-dom.production.min.js:
+ (**
+ * @license React
+ * react-dom.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *)
+
+react/cjs/react-jsx-runtime.production.min.js:
+ (**
+ * @license React
+ * react-jsx-runtime.production.min.js
+ *
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ *)
+*/
+//# sourceMappingURL=tool-manager.js.map
diff --git a/ui/tool-manager/package-lock.json b/ui/tool-manager/package-lock.json
new file mode 100644
index 00000000..11253cdd
--- /dev/null
+++ b/ui/tool-manager/package-lock.json
@@ -0,0 +1,552 @@
+{
+ "name": "mcp-tool-manager-ui",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "mcp-tool-manager-ui",
+ "version": "0.1.0",
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "esbuild": "^0.24.0",
+ "typescript": "^5.6.3"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
+ "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
+ "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
+ "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
+ "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
+ "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
+ "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
+ "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
+ "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
+ "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
+ "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
+ "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
+ "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
+ "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
+ "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
+ "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
+ "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
+ "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
+ "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
+ "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
+ "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
+ "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
+ "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
+ "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.24.2",
+ "@esbuild/android-arm": "0.24.2",
+ "@esbuild/android-arm64": "0.24.2",
+ "@esbuild/android-x64": "0.24.2",
+ "@esbuild/darwin-arm64": "0.24.2",
+ "@esbuild/darwin-x64": "0.24.2",
+ "@esbuild/freebsd-arm64": "0.24.2",
+ "@esbuild/freebsd-x64": "0.24.2",
+ "@esbuild/linux-arm": "0.24.2",
+ "@esbuild/linux-arm64": "0.24.2",
+ "@esbuild/linux-ia32": "0.24.2",
+ "@esbuild/linux-loong64": "0.24.2",
+ "@esbuild/linux-mips64el": "0.24.2",
+ "@esbuild/linux-ppc64": "0.24.2",
+ "@esbuild/linux-riscv64": "0.24.2",
+ "@esbuild/linux-s390x": "0.24.2",
+ "@esbuild/linux-x64": "0.24.2",
+ "@esbuild/netbsd-arm64": "0.24.2",
+ "@esbuild/netbsd-x64": "0.24.2",
+ "@esbuild/openbsd-arm64": "0.24.2",
+ "@esbuild/openbsd-x64": "0.24.2",
+ "@esbuild/sunos-x64": "0.24.2",
+ "@esbuild/win32-arm64": "0.24.2",
+ "@esbuild/win32-ia32": "0.24.2",
+ "@esbuild/win32-x64": "0.24.2"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ }
+ }
+}
diff --git a/ui/tool-manager/package.json b/ui/tool-manager/package.json
new file mode 100644
index 00000000..672ee6c3
--- /dev/null
+++ b/ui/tool-manager/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "mcp-tool-manager-ui",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "build": "esbuild src/index.tsx --bundle --format=esm --target=es2020 --jsx=automatic --minify --outfile=dist/tool-manager.js --log-level=info --sourcemap && mkdir -p ../../pkg/gateway/ui/embedded && cp dist/tool-manager.js ../../pkg/gateway/ui/embedded/tool-manager.js",
+ "typecheck": "tsc --noEmit"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "esbuild": "^0.24.0",
+ "typescript": "^5.6.3"
+ }
+}
diff --git a/ui/tool-manager/src/index.tsx b/ui/tool-manager/src/index.tsx
new file mode 100644
index 00000000..5ad5a4e1
--- /dev/null
+++ b/ui/tool-manager/src/index.tsx
@@ -0,0 +1,956 @@
+import React, {
+ useCallback,
+ useEffect,
+ useMemo,
+ useState,
+ useSyncExternalStore,
+ type SetStateAction,
+} from "react";
+import { createRoot } from "react-dom/client";
+
+type ToolName = "mcp-find" | "mcp-add" | "mcp-remove";
+type WidgetStatus = "success" | "error" | "info";
+
+type ToolContent = {
+ type?: string;
+ text?: string;
+ [key: string]: unknown;
+};
+
+type CallToolResponse = {
+ content?: ToolContent[];
+ structuredContent?: unknown;
+ isError?: boolean;
+ _meta?: Record;
+};
+
+type ToolManagerAction = {
+ type: "find" | "add" | "remove";
+ status: "success" | "error";
+ message?: string;
+ server?: string;
+};
+
+type ToolManagerServer = {
+ name: string;
+ description?: string;
+ type?: string;
+ remoteUrl?: string;
+ image?: string;
+ longLived?: boolean;
+ requiredSecrets?: string[];
+ configSchema?: unknown;
+ tools?: { name: string; description?: string }[];
+ oauthProviders?: string[];
+ isActive?: boolean;
+};
+
+type ToolManagerState = {
+ view?: string;
+ sourceTool?: string;
+ status?: WidgetStatus;
+ message?: string;
+ query?: string;
+ limit?: number;
+ totalMatches?: number;
+ activeServers: string[];
+ results?: ToolManagerServer[];
+ server?: ToolManagerServer;
+ lastAction?: ToolManagerAction;
+ timestamp?: string;
+ error?: string;
+};
+
+type OpenAiGlobals = {
+ toolOutput?: CallToolResponse | null;
+ widgetState?: ToolManagerState | null;
+ theme?: "light" | "dark";
+ displayMode?: "inline" | "fullscreen" | "pip";
+ maxHeight?: number;
+ locale?: string;
+ userAgent?: { device?: { type?: string } };
+ safeArea?: { insets: { top: number; right: number; bottom: number; left: number } };
+};
+
+type OpenAiAPI = {
+ callTool?: (name: string, args: Record) => Promise;
+ sendFollowUpMessage?: (args: { prompt: string }) => Promise;
+ openExternal?: (payload: { href: string }) => void;
+ requestDisplayMode?: (args: { mode: "pip" | "inline" | "fullscreen" }) => Promise<{ mode: string }>;
+ setWidgetState?: (state: unknown) => Promise;
+ setLayout?: (layout: unknown) => Promise;
+};
+
+type OpenAiHost = OpenAiAPI & OpenAiGlobals;
+
+declare global {
+ interface Window {
+ openai?: OpenAiHost;
+ }
+
+ interface WindowEventMap {
+ [SET_GLOBALS_EVENT_TYPE]: SetGlobalsEvent;
+ }
+}
+
+type SetGlobalsEvent = CustomEvent<{
+ globals: Partial;
+}>;
+
+const SET_GLOBALS_EVENT_TYPE = "openai:set_globals";
+const STYLE_TAG_ID = "mcp-tool-manager-style";
+const ROOT_ELEMENT_ID = "mcp-tool-manager-root";
+const DEFAULT_LIMIT = 10;
+
+const EMPTY_STATE: ToolManagerState = {
+ status: "info",
+ activeServers: [],
+ message: "Search the catalog to find MCP servers you can enable.",
+};
+
+function injectStyles(): void {
+ if (typeof document === "undefined") {
+ return;
+ }
+ if (document.getElementById(STYLE_TAG_ID)) {
+ return;
+ }
+ const style = document.createElement("style");
+ style.id = STYLE_TAG_ID;
+ style.textContent = `
+ :root {
+ color-scheme: light dark;
+ font-family: "Inter", "Segoe UI", system-ui, -apple-system, sans-serif;
+ }
+ #${ROOT_ELEMENT_ID} {
+ height: 100%;
+ }
+ .mcp-tool-manager {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ height: 100%;
+ box-sizing: border-box;
+ padding: 1.25rem;
+ background: transparent;
+ color: var(--mcp-tool-manager-fg, inherit);
+ }
+ .mcp-tool-manager header h1 {
+ margin: 0;
+ font-size: 1.25rem;
+ font-weight: 600;
+ }
+ .mcp-tool-manager header p {
+ margin: 0.25rem 0 0;
+ color: var(--mcp-tool-manager-muted, rgba(120, 120, 120, 0.9));
+ font-size: 0.95rem;
+ line-height: 1.4;
+ }
+ .mcp-status {
+ padding: 0.75rem 1rem;
+ border-radius: 0.75rem;
+ font-size: 0.95rem;
+ line-height: 1.3;
+ }
+ .mcp-status.success {
+ background: rgba(16, 185, 129, 0.14);
+ color: rgba(6, 95, 70, 0.95);
+ }
+ .mcp-status.error {
+ background: rgba(248, 113, 113, 0.18);
+ color: rgba(153, 27, 27, 0.95);
+ }
+ .mcp-status.info {
+ background: rgba(59, 130, 246, 0.16);
+ color: rgba(30, 64, 175, 0.95);
+ }
+ .mcp-search-card {
+ border: 1px solid rgba(120, 120, 120, 0.2);
+ border-radius: 1rem;
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ background: color-mix(in srgb, currentColor 4%, transparent);
+ box-shadow: 0 2px 8px rgba(15, 15, 15, 0.04);
+ }
+ .mcp-search-card form {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ }
+ .mcp-search-row {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ }
+ .mcp-search-row input[type="text"] {
+ flex: 1 1 220px;
+ padding: 0.55rem 0.75rem;
+ border-radius: 0.65rem;
+ border: 1px solid rgba(120, 120, 120, 0.35);
+ font-size: 0.95rem;
+ background: rgba(255, 255, 255, 0.04);
+ color: inherit;
+ }
+ .mcp-search-row button {
+ padding: 0.55rem 1rem;
+ border-radius: 0.65rem;
+ border: none;
+ font-weight: 600;
+ font-size: 0.95rem;
+ background: rgba(59, 130, 246, 0.92);
+ color: #fff;
+ cursor: pointer;
+ }
+ .mcp-search-row button[disabled],
+ .mcp-action-button[disabled] {
+ opacity: 0.55;
+ cursor: not-allowed;
+ }
+ .mcp-active-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+ }
+ .mcp-chip {
+ padding: 0.35rem 0.65rem;
+ border-radius: 999px;
+ font-size: 0.85rem;
+ background: rgba(59, 130, 246, 0.12);
+ color: rgba(37, 99, 235, 0.95);
+ }
+ .mcp-card-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ gap: 1rem;
+ }
+ .mcp-card {
+ border: 1px solid rgba(120, 120, 120, 0.18);
+ border-radius: 1rem;
+ padding: 1rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ background: color-mix(in srgb, currentColor 3%, transparent);
+ box-shadow: 0 2px 6px rgba(15, 15, 15, 0.04);
+ }
+ .mcp-card h2 {
+ margin: 0;
+ font-size: 1.05rem;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+ }
+ .mcp-badge {
+ font-size: 0.75rem;
+ font-weight: 600;
+ padding: 0.2rem 0.45rem;
+ border-radius: 999px;
+ background: rgba(16, 185, 129, 0.18);
+ color: rgba(6, 95, 70, 0.9);
+ }
+ .mcp-card p {
+ margin: 0;
+ font-size: 0.9rem;
+ line-height: 1.45;
+ color: var(--mcp-tool-manager-muted, rgba(120, 120, 120, 0.9));
+ }
+ .mcp-card dl {
+ margin: 0;
+ display: grid;
+ grid-template-columns: auto 1fr;
+ gap: 0.35rem 0.75rem;
+ font-size: 0.85rem;
+ }
+ .mcp-card dt {
+ font-weight: 600;
+ color: rgba(100, 100, 100, 0.9);
+ }
+ .mcp-card dd {
+ margin: 0;
+ color: inherit;
+ }
+ .mcp-card .tool-list {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+ margin-top: 0.25rem;
+ }
+ .mcp-card .tool-list span {
+ font-size: 0.8rem;
+ color: rgba(120, 120, 120, 0.9);
+ }
+ .mcp-card-actions {
+ margin-top: auto;
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ }
+ .mcp-action-button {
+ flex: 0 0 auto;
+ border-radius: 0.6rem;
+ border: none;
+ padding: 0.5rem 0.9rem;
+ font-size: 0.9rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition: transform 0.1s ease;
+ }
+ .mcp-action-button.primary {
+ background: rgba(16, 185, 129, 0.9);
+ color: white;
+ }
+ .mcp-action-button.secondary {
+ background: rgba(248, 113, 113, 0.9);
+ color: white;
+ }
+ .mcp-empty {
+ font-size: 0.9rem;
+ color: var(--mcp-tool-manager-muted, rgba(120, 120, 120, 0.9));
+ padding: 0.5rem 0;
+ }
+ .mcp-footer {
+ font-size: 0.75rem;
+ color: rgba(120, 120, 120, 0.75);
+ text-align: right;
+ }
+ `;
+ document.head.appendChild(style);
+}
+
+function useOpenAiGlobal(key: K): OpenAiGlobals[K] | undefined {
+ return useSyncExternalStore(
+ (onChange) => {
+ const handler = (event: Event): void => {
+ const custom = event as SetGlobalsEvent;
+ if (custom.detail?.globals && key in custom.detail.globals) {
+ onChange();
+ }
+ };
+ window.addEventListener(SET_GLOBALS_EVENT_TYPE, handler, { passive: true });
+ return () => window.removeEventListener(SET_GLOBALS_EVENT_TYPE, handler);
+ },
+ () => window.openai?.[key],
+ () => undefined,
+ );
+}
+
+function useToolOutput(): CallToolResponse | null | undefined {
+ return useOpenAiGlobal("toolOutput");
+}
+
+function useWidgetState(
+ defaultState?: T | (() => T | null) | null,
+): readonly [T | null, (state: SetStateAction) => void] {
+ const widgetStateFromWindow = useOpenAiGlobal("widgetState") as T | null | undefined;
+ const [widgetState, setWidgetStateInternal] = useState(() => {
+ if (widgetStateFromWindow != null) {
+ return widgetStateFromWindow;
+ }
+ if (typeof defaultState === "function") {
+ return (defaultState as () => T | null)();
+ }
+ return defaultState ?? null;
+ });
+
+ useEffect(() => {
+ if (widgetStateFromWindow !== undefined) {
+ setWidgetStateInternal(widgetStateFromWindow);
+ }
+ }, [widgetStateFromWindow]);
+
+ const setWidgetState = useCallback(
+ (value: SetStateAction) => {
+ setWidgetStateInternal((prev) => {
+ const next = typeof value === "function" ? (value as (prevState: T | null) => T | null)(prev) : value;
+ if (next != null) {
+ void window.openai?.setWidgetState?.(next);
+ }
+ return next;
+ });
+ },
+ [],
+ );
+
+ return [widgetState, setWidgetState] as const;
+}
+
+function dedupeStrings(values: string[] | undefined): string[] {
+ if (!values || values.length === 0) {
+ return [];
+ }
+ const seen = new Set();
+ values.forEach((value) => {
+ if (value) {
+ seen.add(value);
+ }
+ });
+ return Array.from(seen).sort((a, b) => a.localeCompare(b));
+}
+
+function normalizeServer(value: unknown): ToolManagerServer {
+ if (!value || typeof value !== "object") {
+ return { name: "" };
+ }
+ const data = value as Record;
+ const toStringArray = (input: unknown): string[] | undefined => {
+ if (!Array.isArray(input)) {
+ return undefined;
+ }
+ return input.map((item) => String(item)).filter(Boolean);
+ };
+ const tools = Array.isArray(data.tools)
+ ? data.tools
+ .map((tool) => {
+ if (!tool || typeof tool !== "object") {
+ return null;
+ }
+ const record = tool as Record;
+ const name = typeof record.name === "string" ? record.name : undefined;
+ if (!name) {
+ return null;
+ }
+ return {
+ name,
+ description: typeof record.description === "string" ? record.description : undefined,
+ };
+ })
+ .filter(Boolean) as { name: string; description?: string }[]
+ : undefined;
+
+ return {
+ name: typeof data.name === "string" ? data.name : "",
+ description: typeof data.description === "string" ? data.description : undefined,
+ type: typeof data.type === "string" ? data.type : undefined,
+ remoteUrl:
+ typeof (data.remoteUrl as string) === "string"
+ ? (data.remoteUrl as string)
+ : typeof (data.remote_url as string) === "string"
+ ? (data.remote_url as string)
+ : undefined,
+ image: typeof data.image === "string" ? data.image : undefined,
+ longLived:
+ typeof (data.longLived as boolean) === "boolean"
+ ? (data.longLived as boolean)
+ : typeof (data.long_lived as boolean) === "boolean"
+ ? (data.long_lived as boolean)
+ : undefined,
+ requiredSecrets:
+ toStringArray(data.requiredSecrets) ??
+ toStringArray(data.required_secrets) ??
+ toStringArray(data.requiredSecretsNames),
+ configSchema: data.configSchema ?? data.config_schema,
+ tools,
+ oauthProviders:
+ toStringArray(data.oauthProviders) ?? toStringArray((data.oauth_providers as string[] | undefined)),
+ };
+}
+
+function normalizeAction(value: unknown): ToolManagerAction | undefined {
+ if (!value || typeof value !== "object") {
+ return undefined;
+ }
+ const data = value as Record;
+ const rawType = typeof data.type === "string" ? data.type : "";
+ const type: ToolManagerAction["type"] =
+ rawType === "add" || rawType === "remove" || rawType === "find" ? rawType : "find";
+ const rawStatus = typeof data.status === "string" ? data.status : "";
+ const status: ToolManagerAction["status"] = rawStatus === "error" ? "error" : "success";
+ return {
+ type,
+ status,
+ message: typeof data.message === "string" ? data.message : undefined,
+ server: typeof data.server === "string" ? data.server : undefined,
+ };
+}
+
+function extractFirstText(response: CallToolResponse | null | undefined): string | undefined {
+ if (!response?.content) {
+ return undefined;
+ }
+ for (const item of response.content) {
+ if (typeof item?.text === "string" && item.text.trim().length > 0) {
+ return item.text.trim();
+ }
+ }
+ return undefined;
+}
+
+function parseCallToolResponse(response: CallToolResponse | null | undefined): ToolManagerState | null {
+ if (!response) {
+ return null;
+ }
+
+ const structured = response.structuredContent;
+ const base =
+ structured && typeof structured === "object" && !Array.isArray(structured)
+ ? (structured as Record)
+ : null;
+
+ if (!base) {
+ const message = extractFirstText(response) ?? undefined;
+ return {
+ ...EMPTY_STATE,
+ status: response.isError ? "error" : "info",
+ message,
+ };
+ }
+
+ const status = typeof base.status === "string" ? (base.status as WidgetStatus) : undefined;
+ const results = Array.isArray(base.results)
+ ? (base.results.map((item) => normalizeServer(item)) as ToolManagerServer[])
+ : undefined;
+
+ const activeServers = dedupeStrings(
+ Array.isArray(base.activeServers)
+ ? (base.activeServers as string[])
+ : Array.isArray(base.active_servers)
+ ? (base.active_servers as string[])
+ : undefined,
+ );
+
+ const server =
+ base.server && typeof base.server === "object" ? normalizeServer(base.server as Record) : undefined;
+
+ const lastAction = normalizeAction(base.lastAction);
+
+ let message: string | undefined =
+ typeof base.message === "string" ? base.message : typeof base.info === "string" ? base.info : undefined;
+ if (!message) {
+ message = extractFirstText(response);
+ }
+
+ return {
+ view: typeof base.view === "string" ? base.view : undefined,
+ sourceTool: typeof base.sourceTool === "string" ? base.sourceTool : undefined,
+ status: status ?? (response.isError ? "error" : undefined),
+ message,
+ query: typeof base.query === "string" ? base.query : undefined,
+ limit: typeof base.limit === "number" ? base.limit : undefined,
+ totalMatches: typeof base.totalMatches === "number" ? base.totalMatches : undefined,
+ activeServers,
+ results,
+ server,
+ lastAction,
+ timestamp: typeof base.timestamp === "string" ? base.timestamp : undefined,
+ error: typeof base.error === "string" ? base.error : undefined,
+ };
+}
+
+function decorateState(state: ToolManagerState | null): ToolManagerState {
+ const base = state ?? EMPTY_STATE;
+ const activeServers = dedupeStrings(base.activeServers);
+ const activeSet = new Set(activeServers);
+ const results = (base.results ?? []).map((server) => ({
+ ...server,
+ isActive: activeSet.has(server.name),
+ }));
+ const server = base.server ? { ...base.server, isActive: activeSet.has(base.server.name) } : undefined;
+ return {
+ ...base,
+ activeServers,
+ results,
+ server,
+ };
+}
+
+type MergeContext = {
+ tool: ToolName;
+ server?: string;
+ message?: string;
+ status?: WidgetStatus;
+};
+
+function actionForContext(ctx: MergeContext, status: WidgetStatus, message?: string): ToolManagerAction {
+ let type: ToolManagerAction["type"] = "find";
+ if (ctx.tool === "mcp-add") {
+ type = "add";
+ } else if (ctx.tool === "mcp-remove") {
+ type = "remove";
+ }
+ return {
+ type,
+ server: ctx.server,
+ status: status === "error" ? "error" : "success",
+ message,
+ };
+}
+
+function mergeState(
+ prev: ToolManagerState | null,
+ next: ToolManagerState | null,
+ ctx: MergeContext,
+): ToolManagerState {
+ const base = prev ?? EMPTY_STATE;
+ if (!next) {
+ const status = ctx.status ?? base.status ?? "info";
+ const message = ctx.message ?? base.message;
+ return decorateState({
+ ...base,
+ status,
+ message,
+ lastAction: actionForContext(ctx, status, message),
+ });
+ }
+
+ const merged: ToolManagerState = {
+ ...base,
+ ...next,
+ activeServers:
+ next.activeServers && next.activeServers.length > 0 ? dedupeStrings(next.activeServers) : base.activeServers,
+ results: next.results ?? base.results,
+ query: next.query ?? base.query,
+ limit: next.limit ?? base.limit ?? DEFAULT_LIMIT,
+ };
+
+ merged.status = next.status ?? ctx.status ?? base.status ?? "info";
+ merged.message = next.message ?? ctx.message ?? base.message;
+
+ if (!next.lastAction) {
+ merged.lastAction = actionForContext(ctx, merged.status ?? "info", merged.message);
+ }
+
+ return decorateState(merged);
+}
+
+function toolOutputSignature(result: CallToolResponse | null | undefined): string {
+ if (!result) {
+ return "";
+ }
+ try {
+ return JSON.stringify({
+ structuredContent: result.structuredContent ?? null,
+ isError: Boolean(result.isError),
+ message: extractFirstText(result) ?? null,
+ });
+ } catch {
+ return String(Date.now());
+ }
+}
+
+function formatFallbackMessage(tool: ToolName, ctx: MergeContext, state: ToolManagerState): string {
+ if (ctx.message) {
+ return ctx.message;
+ }
+ if (tool === "mcp-find") {
+ const term = ctx.message ?? state.query;
+ return term ? `Showing results for “${term}”.` : "Showing search results.";
+ }
+ if (tool === "mcp-add") {
+ return ctx.server ? `Added “${ctx.server}”.` : "Server added.";
+ }
+ if (tool === "mcp-remove") {
+ return ctx.server ? `Removed “${ctx.server}”.` : "Server removed.";
+ }
+ return "Operation completed.";
+}
+
+function formatError(error: unknown): string {
+ if (error instanceof Error) {
+ return error.message;
+ }
+ if (typeof error === "string") {
+ return error;
+ }
+ return "Something went wrong. Please try again.";
+}
+
+const App: React.FC = () => {
+ const toolOutput = useToolOutput();
+ const outputSignature = useMemo(() => toolOutputSignature(toolOutput), [toolOutput]);
+ const [state, setState] = useWidgetState(() => {
+ const parsed = parseCallToolResponse(window.openai?.toolOutput);
+ return parsed ?? EMPTY_STATE;
+ });
+
+ const decorated = useMemo(() => decorateState(state), [state]);
+ const [query, setQuery] = useState(() => decorated.query ?? "");
+ const [pendingAction, setPendingAction] = useState(null);
+ const [inlineError, setInlineError] = useState(null);
+ const [isSearching, setIsSearching] = useState(false);
+
+ useEffect(() => {
+ if (decorated.query !== undefined) {
+ setQuery(decorated.query);
+ }
+ }, [decorated.query]);
+
+ useEffect(() => {
+ if (!toolOutput) {
+ return;
+ }
+ const parsed = parseCallToolResponse(toolOutput);
+ const sourceTool =
+ parsed?.sourceTool === "mcp-add" || parsed?.sourceTool === "mcp-remove" || parsed?.sourceTool === "mcp-find"
+ ? (parsed.sourceTool as ToolName)
+ : ("mcp-find" satisfies ToolName);
+ const ctx: MergeContext = {
+ tool: sourceTool,
+ server: parsed?.server?.name ?? parsed?.lastAction?.server,
+ message: parsed?.message ?? extractFirstText(toolOutput) ?? undefined,
+ status: parsed?.status ?? (toolOutput.isError ? "error" : undefined),
+ };
+ setState((prev) => mergeState(prev, parsed, ctx));
+ }, [setState, toolOutput, outputSignature]);
+
+ const invokeTool = useCallback(
+ async (tool: ToolName, args: Record, ctx: MergeContext) => {
+ if (!window.openai?.callTool) {
+ throw new Error("Tool calling is unavailable in this context.");
+ }
+ const response = await window.openai.callTool(tool, args);
+ const parsed = parseCallToolResponse(response);
+ const status: WidgetStatus =
+ parsed?.status ?? (response.isError ? "error" : ctx.status ?? "success");
+ const baseState = decorateState(state);
+ const message = parsed?.message ?? extractFirstText(response) ?? formatFallbackMessage(tool, ctx, baseState);
+ setState((prev) => mergeState(prev, parsed, { ...ctx, tool, message, status }));
+ return response;
+ },
+ [setState, state],
+ );
+
+ const handleSearch = useCallback(
+ async (event?: React.FormEvent) => {
+ event?.preventDefault();
+ const term = query.trim();
+ if (!term) {
+ setInlineError("Enter a name, description, or keyword to search the catalog.");
+ return;
+ }
+ setInlineError(null);
+ setIsSearching(true);
+ try {
+ const response = await invokeTool(
+ "mcp-find",
+ { query: term, limit: decorated.limit ?? DEFAULT_LIMIT },
+ { tool: "mcp-find", message: `Showing results for “${term}”.`, status: "success" },
+ );
+ if (response.isError) {
+ setInlineError(extractFirstText(response) ?? "The search tool returned an error.");
+ }
+ } catch (error) {
+ setInlineError(formatError(error));
+ } finally {
+ setIsSearching(false);
+ }
+ },
+ [decorated.limit, invokeTool, query],
+ );
+
+ const handleAdd = useCallback(
+ async (server: string) => {
+ setInlineError(null);
+ setPendingAction(`add:${server}`);
+ try {
+ const response = await invokeTool(
+ "mcp-add",
+ { name: server },
+ { tool: "mcp-add", server, message: `Added “${server}”.`, status: "success" },
+ );
+ if (response.isError) {
+ setInlineError(extractFirstText(response) ?? `Failed to add “${server}”.`);
+ }
+ } catch (error) {
+ setInlineError(formatError(error));
+ } finally {
+ setPendingAction(null);
+ }
+ },
+ [invokeTool],
+ );
+
+ const handleRemove = useCallback(
+ async (server: string) => {
+ setInlineError(null);
+ setPendingAction(`remove:${server}`);
+ try {
+ const response = await invokeTool(
+ "mcp-remove",
+ { name: server },
+ { tool: "mcp-remove", server, message: `Removed “${server}”.`, status: "info" },
+ );
+ if (response.isError) {
+ setInlineError(extractFirstText(response) ?? `Failed to remove “${server}”.`);
+ }
+ } catch (error) {
+ setInlineError(formatError(error));
+ } finally {
+ setPendingAction(null);
+ }
+ },
+ [invokeTool],
+ );
+
+ const activeServers = decorated.activeServers;
+ const results = decorated.results ?? [];
+ const statusClass =
+ decorated.status === "error" ? "error" : decorated.status === "success" ? "success" : "info";
+
+ return (
+
+
+
+ {(decorated.message || inlineError) && (
+
+ {inlineError ?? decorated.message}
+
+ )}
+
+
+
+
+
Active servers:
+ {activeServers.length === 0 ? (
+
None enabled yet.
+ ) : (
+
+ {activeServers.map((name) => (
+
+ {name}
+
+ ))}
+
+ )}
+
+
+
+
+ {results.length === 0 ? (
+
+ {decorated.query
+ ? `No servers matched “${decorated.query}”. Try a different keyword.`
+ : "Start by searching to see available servers you can enable."}
+
+ ) : (
+
+ {results.map((server) => {
+ const isAddPending = pendingAction === `add:${server.name}`;
+ const isRemovePending = pendingAction === `remove:${server.name}`;
+ return (
+
+
+ {server.name}
+ {server.isActive && Active }
+
+ {server.description ?? "No description provided in the catalog."}
+
+ {server.type && (
+ <>
+ Type
+ {server.type}
+ >
+ )}
+ {server.remoteUrl && (
+ <>
+ Endpoint
+ {server.remoteUrl}
+ >
+ )}
+ {server.image && (
+ <>
+ Image
+ {server.image}
+ >
+ )}
+ {typeof server.longLived === "boolean" && (
+ <>
+ Lifecycle
+ {server.longLived ? "Long-lived" : "Ephemeral"}
+ >
+ )}
+ {server.requiredSecrets && server.requiredSecrets.length > 0 && (
+ <>
+ Secrets
+ {server.requiredSecrets.join(", ")}
+ >
+ )}
+
+ {server.tools && server.tools.length > 0 && (
+
+ Tools
+ {server.tools.slice(0, 3).map((tool) => (
+
+ {tool.name}
+ {tool.description ? ` — ${tool.description}` : ""}
+
+ ))}
+ {server.tools.length > 3 && +{server.tools.length - 3} more tools }
+
+ )}
+
+ handleAdd(server.name)}
+ disabled={server.isActive || pendingAction !== null}
+ >
+ {isAddPending ? "Adding…" : server.isActive ? "Already active" : "Add server"}
+
+ handleRemove(server.name)}
+ disabled={!server.isActive || pendingAction !== null}
+ >
+ {isRemovePending ? "Removing…" : "Remove server"}
+
+
+
+ );
+ })}
+
+ )}
+
+
+
+ {decorated.timestamp ? `Last updated ${new Date(decorated.timestamp).toLocaleString()}` : null}
+
+
+ );
+};
+
+function mount(): void {
+ injectStyles();
+ const container = document.getElementById(ROOT_ELEMENT_ID);
+ if (!container) {
+ console.error("MCP Tool Manager UI failed to mount: root element not found.");
+ return;
+ }
+ if (container.dataset.mounted === "true") {
+ return;
+ }
+ container.dataset.mounted = "true";
+ const root = createRoot(container);
+ root.render( );
+}
+
+if (typeof window !== "undefined") {
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", () => mount(), { once: true });
+ } else {
+ mount();
+ }
+}
diff --git a/ui/tool-manager/tsconfig.json b/ui/tool-manager/tsconfig.json
new file mode 100644
index 00000000..ac37e4ee
--- /dev/null
+++ b/ui/tool-manager/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "jsx": "react-jsx",
+ "strict": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "allowJs": false,
+ "forceConsistentCasingInFileNames": true,
+ "types": ["dom", "dom.iterable", "es2020"]
+ },
+ "include": ["src"]
+}