-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Expand file tree
/
Copy pathui_capability.go
More file actions
78 lines (70 loc) · 3.08 KB
/
Copy pathui_capability.go
File metadata and controls
78 lines (70 loc) · 3.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package github
import (
"context"
ghcontext "github.com/github/github-mcp-server/pkg/context"
"github.com/modelcontextprotocol/go-sdk/mcp"
)
// mcpAppsExtensionKey is the capability extension key that clients use to
// advertise MCP Apps UI support.
const mcpAppsExtensionKey = "io.modelcontextprotocol/ui"
// MCPAppMIMEType is the MIME type for MCP App UI resources.
const MCPAppMIMEType = "text/html;profile=mcp-app"
// clientSupportsUI reports whether the MCP client that sent this request
// supports MCP Apps UI rendering.
// It checks the context first (set by HTTP/stateless servers from stored
// session capabilities), then falls back to the go-sdk Session (for stdio).
func clientSupportsUI(ctx context.Context, req *mcp.CallToolRequest) bool {
// Check context first (works for HTTP/stateless servers)
if supported, ok := ghcontext.HasUISupport(ctx); ok {
return supported
}
// Fall back to go-sdk session (works for stdio/stateful servers)
if req != nil && req.Session != nil {
params := req.Session.InitializeParams()
if params != nil && params.Capabilities != nil {
_, hasUI := params.Capabilities.Extensions[mcpAppsExtensionKey]
return hasUI
}
}
return false
}
// uiSubmitted reports whether the call is itself an MCP App form submission.
// The form re-invokes its tool with _ui_submitted=true; such calls must execute
// rather than re-render the form.
func uiSubmitted(args map[string]any) bool {
submitted, _ := OptionalParam[bool](args, "_ui_submitted")
return submitted
}
// hasNonFormParams reports whether the call carries any parameter the tool's MCP
// App form cannot represent (anything outside formParams). Such calls must
// bypass the form and execute directly so the supplied values aren't silently
// dropped. formParams is the set of parameters the form collects and re-sends
// on submit.
func hasNonFormParams(args map[string]any, formParams map[string]struct{}) bool {
for key, value := range args {
if value == nil {
continue
}
if _, ok := formParams[key]; !ok {
return true
}
}
return false
}
// shouldDeferToForm is the single source of truth for the show/defer decision
// shared by the form-backed write tools (create_pull_request,
// update_pull_request, issue_write). It reports whether a call should be handed
// off to its MCP App form instead of executing now: defer only when MCP Apps
// are enabled, form deferral has not been disabled, the client can render UI,
// the call is not itself a form submission, and every supplied parameter can
// be represented by the form (formParams is the tool's form-parameter
// allowlist). When it returns false the handler executes directly; the host may
// still render the tool's view, which renders the result rather than an input
// form.
func shouldDeferToForm(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args map[string]any, formParams map[string]struct{}) bool {
return deps.IsFeatureEnabled(ctx, MCPAppsFeatureFlag) &&
!deps.IsFeatureEnabled(ctx, MCPAppsDisableFormDeferralFeatureFlag) &&
clientSupportsUI(ctx, req) &&
!uiSubmitted(args) &&
!hasNonFormParams(args, formParams)
}