chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
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, 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) &&
|
||||
clientSupportsUI(ctx, req) &&
|
||||
!uiSubmitted(args) &&
|
||||
!hasNonFormParams(args, formParams)
|
||||
}
|
||||
Reference in New Issue
Block a user