feat: added tools flag to choose mcpui

This commit is contained in:
Manuel Ganter 2026-01-26 14:20:34 +01:00
parent 973d109859
commit 373b9fc296
No known key found for this signature in database
3 changed files with 247 additions and 28 deletions

183
MCP_UI.md
View file

@ -210,35 +210,182 @@ Each UI resource follows the MCP-UI protocol structure:
- `ui://app-detail/{org}/{name}/{version}` - Single app detail view
- `ui://app-instances-dashboard` - Instance listing dashboard
## Configuration
### How Clients Discover UI Support
Tools that support UI rendering advertise this capability in their metadata via the `tools/list` response:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list",
"result": {
"tools": [
{
"name": "list_apps",
"description": "List all Edge Connect applications matching the specified filter. Supports UI rendering when _meta.ui_standard is set to 'mcpui'.",
"_meta": {
"ui_support": "mcpui"
},
"inputSchema": {...}
},
{
"name": "show_app",
"description": "Retrieve a specific Edge Connect application by its key. Supports UI rendering when _meta.ui_standard is set to 'mcpui'.",
"_meta": {
"ui_support": "mcpui"
},
"inputSchema": {...}
},
{
"name": "list_app_instances",
"description": "List all Edge Connect application instances. Supports UI rendering when _meta.ui_standard is set to 'mcpui'.",
"_meta": {
"ui_support": "mcpui"
},
"inputSchema": {...}
}
]
}
}
```
The `_meta.ui_support` field indicates which UI standard(s) the tool supports. Clients can use this information to decide whether to request UI rendering when calling the tool.
### UI Standard Flag (Per-Call Control)
The server respects the client's UI rendering preferences via the `_meta.ui_standard` field in individual tool call requests. This allows **MCP clients** to control whether UI resources should be rendered on a **per-call basis**.
**Available UI Standards**:
- `"mcpui"` - MCP-UI protocol (renders interactive HTML dashboards)
- `""` (empty string) or omitted - No UI rendering (text-only responses)
- Custom standards can be added in the future
**How Clients Request UI Rendering**:
Clients specify their UI preference in the `_meta` field of the `tools/call` request:
```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "list_apps",
"arguments": {
"region": "EU"
},
"_meta": {
"ui_standard": "mcpui"
}
}
}
```
**Examples**:
**To enable MCP-UI rendering for a specific call**:
```json
{
"method": "tools/call",
"params": {
"name": "list_apps",
"arguments": {...},
"_meta": {
"ui_standard": "mcpui"
}
}
}
```
**To disable UI rendering** (text-only response):
```json
{
"method": "tools/call",
"params": {
"name": "list_apps",
"arguments": {...}
// No _meta field, or _meta.ui_standard omitted/empty
}
}
```
**Server Implementation**:
The server checks the client's `_meta.ui_standard` preference on each tool call:
```go
// Extract client's UI standard preference from tool call _meta
func getUIStandardFromRequest(req *mcp.CallToolRequest) string {
if req.Params == nil || req.Params.Meta == nil {
return ""
}
uiStandard, ok := req.Params.Meta["ui_standard"]
// ... return the string value
}
// Check if client wants UI rendering for this call
if !shouldRenderUI(req, "mcpui") {
// Return text-only response
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: result}},
}, nil, nil
}
// ... proceed with UI generation
```
**Default Behavior**:
- If the client does not include `_meta.ui_standard` in the tool call, the server returns text-only responses
- The three tools (`list_apps`, `show_app`, `list_app_instances`) support UI rendering when requested
- If UI rendering fails, the tools gracefully fall back to text-only responses
- The `shouldRenderUI()` helper function checks the call's `_meta` field before attempting UI generation
**Benefits**:
- **Per-Call Control**: Clients decide on each call whether they want UI rendering
- **No Configuration Required**: Clients don't need to set preferences during initialization
- **Flexibility**: Support multiple UI standards in the future
- **Discovery**: Clients learn about UI support from `tools/list` response
- **Backward Compatibility**: Existing clients without UI support work seamlessly
- **Graceful Degradation**: Always provides text-based fallback
- **Performance**: Avoid UI generation overhead when clients don't request it
## Usage
### Prerequisites
1. **MCP Client Support**: Your MCP client must support UI resources (embedded resources in tool responses)
2. **Edge Connect Configuration**: Server must be properly configured with Edge Connect credentials
3. **Client UI Capability**: Client must include `_meta.ui_standard` field in tool call requests to receive UI resources
### Example Workflow
1. **List Applications**:
```bash
# User asks: "List all Edge Connect applications"
# Server executes list_apps tool
# Returns: Text summary + Interactive HTML dashboard
```
1. **Client Discovers Tools**:
- Client sends `tools/list` request
- Server responds with tool list including `_meta.ui_support` field
- Client sees that `list_apps`, `show_app`, and `list_app_instances` support `"mcpui"`
2. **View Application Details**:
```bash
# User asks: "Show me details for app myorg/myapp:1.0.0"
# Server executes show_app tool
# Returns: JSON data + Interactive detail view
```
2. **List Applications with UI**:
- User asks: "List all Edge Connect applications"
- Client calls `list_apps` with `_meta.ui_standard: "mcpui"`
- Server returns: Text summary + Interactive HTML dashboard
3. **List Instances**:
```bash
# User asks: "Show all application instances"
# Server executes list_app_instances tool
# Returns: Text summary + Interactive dashboard
```
3. **View Application Details with UI**:
- User asks: "Show me details for app myorg/myapp:1.0.0"
- Client calls `show_app` with `_meta.ui_standard: "mcpui"`
- Server returns: JSON data + Interactive detail view
4. **List Instances without UI**:
- User asks: "Show all application instances as text"
- Client calls `list_app_instances` without `_meta.ui_standard`
- Server returns: Text-only response (no UI)
**Key Points**:
- Each tool call can independently request UI rendering or not
- Clients that don't support UI simply omit the `_meta.ui_standard` field
- Clients can mix UI and non-UI calls based on context or user preferences
### Graceful Degradation

View file

@ -14,7 +14,14 @@ This server includes **rich, interactive web-based visualizations** powered by [
- **🔍 Application Detail View** - Comprehensive property display with JSON viewer
- **⚡ Instances Dashboard** - Interactive table with status indicators and instance management
For clients that don't support UI resources, the server gracefully falls back to text-based responses. See [MCP_UI.md](./MCP_UI.md) for full documentation.
**UI Standard Support**: The server respects client preferences for UI rendering on a per-call basis via the `_meta.ui_standard` field:
- Clients include `"_meta": {"ui_standard": "mcpui"}` in `tools/call` requests to receive MCP-UI interactive dashboards
- Clients omit `_meta.ui_standard` or set it to `""` to receive text-only responses
- Tools advertise UI support via `_meta.ui_support` field in `tools/list` responses
- Server supports multiple UI standards for future extensibility
- This gives clients full control on each call whether they want to render UI resources
For clients that don't support UI resources, the server gracefully falls back to text-based responses. See [MCP_UI.md](./MCP_UI.md) for full documentation and configuration details.
### Edge Connect API Tools

View file

@ -117,6 +117,40 @@ func getProtocolFromRequest(req *mcp.CallToolRequest) mcpuiserver.ProtocolType {
return mcpuiserver.ParseProtocolFromInitialize(paramsMap)
}
// getUIStandardFromRequest extracts the ui_standard value from the tool call's _meta field
// Returns empty string if not set or not a string
func getUIStandardFromRequest(req *mcp.CallToolRequest) string {
// Get _meta from the tool call params
if req.Params == nil || req.Params.Meta == nil {
return ""
}
// Look for ui_standard in the _meta field
uiStandard, ok := req.Params.Meta["ui_standard"]
if !ok {
return ""
}
uiStandardStr, ok := uiStandard.(string)
if !ok {
return ""
}
return uiStandardStr
}
// shouldRenderUI checks if UI rendering should be performed based on client's tool call _meta
// Returns true if client's ui_standard is set to the requested standard (e.g., "mcpui")
func shouldRenderUI(req *mcp.CallToolRequest, standard string) bool {
uiStandard := getUIStandardFromRequest(req)
if uiStandard == "" {
return false // No UI standard specified by client, don't render
}
// Check if the client's UI standard matches the requested standard
return uiStandard == standard
}
// filterAppInstances performs client-side partial matching on app instances
func filterAppInstances(instances []v2.AppInstance, organization, instanceName, cloudletOrg, cloudletName, appOrg, appName, appVersion *string) []v2.AppInstance {
if organization == nil && instanceName == nil &&
@ -324,7 +358,10 @@ func registerShowAppTool(s *mcp.Server) {
mcp.AddTool(s, &mcp.Tool{
Name: "show_app",
Description: "Retrieve a specific Edge Connect application by its key.",
Description: "Retrieve a specific Edge Connect application by its key. Supports UI rendering when _meta.ui_standard is set to 'mcpui'.",
Meta: mcp.Meta{
"ui_support": "mcpui",
},
}, func(ctx context.Context, req *mcp.CallToolRequest, a args) (*mcp.CallToolResult, any, error) {
region := config.DefaultRegion
if a.Region != nil {
@ -347,6 +384,14 @@ func registerShowAppTool(s *mcp.Server) {
return nil, nil, fmt.Errorf("failed to serialize app: %w", err)
}
// Check if UI rendering is enabled by the client
if !shouldRenderUI(req, "mcpui") {
// UI rendering is disabled, return text-only response
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: string(appJSON)}},
}, nil, nil
}
// Parse protocol from MCP initialize request
protocol := getProtocolFromRequest(req)
@ -388,7 +433,10 @@ func registerListAppsTool(s *mcp.Server) {
mcp.AddTool(s, &mcp.Tool{
Name: "list_apps",
Description: "List all Edge Connect applications matching the specified filter. Supports partial (substring) matching for all filter fields.",
Description: "List all Edge Connect applications matching the specified filter. Supports partial (substring) matching for all filter fields. Supports UI rendering when _meta.ui_standard is set to 'mcpui'.",
Meta: mcp.Meta{
"ui_support": "mcpui",
},
}, func(ctx context.Context, req *mcp.CallToolRequest, a args) (*mcp.CallToolResult, any, error) {
region := config.DefaultRegion
if a.Region != nil {
@ -417,6 +465,16 @@ func registerListAppsTool(s *mcp.Server) {
return nil, nil, fmt.Errorf("failed to serialize apps: %w", err)
}
result := fmt.Sprintf("Found %d apps:\n%s", len(apps), string(appsJSON))
// Check if UI rendering is enabled by the client
if !shouldRenderUI(req, "mcpui") {
// UI rendering is disabled, return text-only response
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: result}},
}, nil, nil
}
// Parse protocol from MCP initialize request
protocol := getProtocolFromRequest(req)
@ -424,14 +482,11 @@ func registerListAppsTool(s *mcp.Server) {
uiResource, err := createAppListUI(apps, region, protocol)
if err != nil {
// If UI creation fails, just return text content
result := fmt.Sprintf("Found %d apps:\n%s", len(apps), string(appsJSON))
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: result}},
}, nil, nil
}
result := fmt.Sprintf("Found %d apps:\n%s", len(apps), string(appsJSON))
// Convert UIResource to MCP EmbeddedResource
resourceContents := &mcp.ResourceContents{
URI: uiResource.Resource.URI,
@ -745,7 +800,10 @@ func registerListAppInstancesTool(s *mcp.Server) {
mcp.AddTool(s, &mcp.Tool{
Name: "list_app_instances",
Description: "List all Edge Connect application instances matching the specified filter. Supports partial (substring) matching for all filter fields.",
Description: "List all Edge Connect application instances matching the specified filter. Supports partial (substring) matching for all filter fields. Supports UI rendering when _meta.ui_standard is set to 'mcpui'.",
Meta: mcp.Meta{
"ui_support": "mcpui",
},
}, func(ctx context.Context, req *mcp.CallToolRequest, a args) (*mcp.CallToolResult, any, error) {
region := config.DefaultRegion
if a.Region != nil {
@ -783,6 +841,16 @@ func registerListAppInstancesTool(s *mcp.Server) {
return nil, nil, fmt.Errorf("failed to serialize app instances: %w", err)
}
result := fmt.Sprintf("Found %d app instances:\n%s", len(appInsts), string(appInstsJSON))
// Check if UI rendering is enabled by the client
if !shouldRenderUI(req, "mcpui") {
// UI rendering is disabled, return text-only response
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: result}},
}, nil, nil
}
// Parse protocol from MCP initialize request
protocol := getProtocolFromRequest(req)
@ -790,14 +858,11 @@ func registerListAppInstancesTool(s *mcp.Server) {
uiResource, err := createAppInstanceListUI(appInsts, region, protocol)
if err != nil {
// If UI creation fails, just return text content
result := fmt.Sprintf("Found %d app instances:\n%s", len(appInsts), string(appInstsJSON))
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: result}},
}, nil, nil
}
result := fmt.Sprintf("Found %d app instances:\n%s", len(appInsts), string(appInstsJSON))
// Convert UIResource to MCP EmbeddedResource
resourceContents := &mcp.ResourceContents{
URI: uiResource.Resource.URI,