Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions internal/mcp/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,12 @@ func renderAppHTML(raw string) string {
return strings.Replace(raw, appCommonMarker, "<script>"+appCommonJS+"</script>", 1)
}

// addToolWithMeta wraps mcp.AddTool with rate limiting, audit logging, and
// tool filtering — same as addTool but also sets Meta on the tool definition.
// This is used for MCP App tools that need _meta.ui.resourceUri.
func (s *Server) addToolWithMeta(name, description string, meta mcp.Meta, handler func(ctx context.Context, req *mcp.CallToolRequest, args struct{}) (*mcp.CallToolResult, any, error)) {
// addToolWithMetaTyped wraps mcp.AddTool with rate limiting, audit logging,
// tool filtering, and Meta support — the typed-input variant of
// addToolWithMeta for MCP Apps that accept parameters (service filters,
// time ranges, target IDs, etc.). The In type parameter is used by the SDK
// to auto-derive the tool's JSON input schema.
func addToolWithMetaTyped[In any](s *Server, name, description string, meta mcp.Meta, handler func(ctx context.Context, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, any, error)) {
if !s.toolFilter.isAllowed(name) {
return
}
Expand All @@ -93,7 +95,7 @@ func (s *Server) addToolWithMeta(name, description string, meta mcp.Meta, handle
Meta: meta,
}

wrappedHandler := func(ctx context.Context, req *mcp.CallToolRequest, args struct{}) (*mcp.CallToolResult, any, error) {
wrapped := func(ctx context.Context, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, any, error) {
if err := s.limiter.allow(); err != nil {
s.auditLog.log(name, req.Params.Arguments, false, err.Error())
return errorResult(err.Error()), nil, nil
Expand All @@ -118,18 +120,30 @@ func (s *Server) addToolWithMeta(name, description string, meta mcp.Meta, handle
return result, out, err
}

mcp.AddTool(s.mcpServer, tool, wrappedHandler)
mcp.AddTool(s.mcpServer, tool, wrapped)
s.toolNames = append(s.toolNames, name)
}
Comment thread
cursor[bot] marked this conversation as resolved.

// addToolWithMeta is the no-args convenience wrapper for addToolWithMetaTyped.
// Kept as a method on *Server for call-site ergonomics (s.addToolWithMeta(...))
// since no-args App tools are the common case.
func (s *Server) addToolWithMeta(name, description string, meta mcp.Meta, handler func(ctx context.Context, req *mcp.CallToolRequest, args struct{}) (*mcp.CallToolResult, any, error)) {
addToolWithMetaTyped[struct{}](s, name, description, meta, handler)
}

// --- MCP App registration entry points ---

// registerAppTools registers the tool half of every MCP App in appSpecs.
// registerAppTools registers the tool half of every MCP App in appSpecs,
// plus any apps with typed inputs that live outside the no-args slice.
// Each tool carries _meta.ui.resourceUri pointing at its matching ui:// resource.
func (s *Server) registerAppTools() {
for _, spec := range appSpecs {
s.registerAppTool(spec)
}
// Apps that accept parameters are registered individually because they
// need typed handler generics. Each call registers both tool and resource
// (the resource half is no different from a no-args app).
s.registerInsightsView()
}

// registerAppResources registers the ui:// resource half of every MCP App.
Expand Down
Loading
Loading