Files
ezbookkeeping/pkg/mcp/handler.go
T
2025-07-06 03:02:19 +08:00

188 lines
6.0 KiB
Go

package mcp
import (
"reflect"
"github.com/invopop/jsonschema"
"github.com/mayswind/ezbookkeeping/pkg/core"
"github.com/mayswind/ezbookkeeping/pkg/errs"
"github.com/mayswind/ezbookkeeping/pkg/services"
"github.com/mayswind/ezbookkeeping/pkg/settings"
)
// MCPAvailableServices holds the services available for MCP tools
type MCPAvailableServices interface {
GetTransactionService() *services.TransactionService
GetTransactionCategoryService() *services.TransactionCategoryService
GetTransactionTagService() *services.TransactionTagService
GetAccountService() *services.AccountService
GetUserService() *services.UserService
}
// MCPToolHandler defines the MCP tool handler
type MCPToolHandler[T MCPTextContent | MCPImageContent | MCPAudioContent | MCPResourceLink | MCPEmbeddedResource] interface {
// Description returns the description of the MCP tool
Description() string
// InputType returns the input type for the MCP tool request
InputType() reflect.Type
// OutputType returns the output type for the MCP tool response
OutputType() reflect.Type
// Handle processes the MCP call tool request and returns the response
Handle(*core.WebContext, *MCPCallToolRequest, *settings.Config, MCPAvailableServices) ([]*T, *errs.Error)
}
// GetAllMCPToolInfos returns all available MCP tool information
func GetAllMCPToolInfos() []*MCPTool {
toolInfos := make([]*MCPTool, 0)
for name, handler := range mcpTextContentTools {
toolInfos = append(toolInfos, getMCPToolInfo(name, handler))
}
for name, handler := range mcpImageContentTools {
toolInfos = append(toolInfos, getMCPToolInfo(name, handler))
}
for name, handler := range mcpAudioContentTools {
toolInfos = append(toolInfos, getMCPToolInfo(name, handler))
}
for name, handler := range mcpResourceLinkTools {
toolInfos = append(toolInfos, getMCPToolInfo(name, handler))
}
for name, handler := range mcpEmbeddedResourceTools {
toolInfos = append(toolInfos, getMCPToolInfo(name, handler))
}
return toolInfos
}
// MCPToolHandle handles the MCP tool request based on the tool name
func MCPToolHandle(c *core.WebContext, callToolReq *MCPCallToolRequest, currentConfig *settings.Config, services MCPAvailableServices) (any, *errs.Error) {
if handler, exists := mcpTextContentTools[callToolReq.Name]; exists {
return mcpTextContentToolHandle(c, handler, currentConfig, services, callToolReq)
}
if handler, exists := mcpImageContentTools[callToolReq.Name]; exists {
return mcpImageContentToolHandle(c, handler, currentConfig, services, callToolReq)
}
if handler, exists := mcpAudioContentTools[callToolReq.Name]; exists {
return mcpAudioContentToolHandle(c, handler, currentConfig, services, callToolReq)
}
if handler, exists := mcpResourceLinkTools[callToolReq.Name]; exists {
return mcpResourceLinkToolHandle(c, handler, currentConfig, services, callToolReq)
}
if handler, exists := mcpEmbeddedResourceTools[callToolReq.Name]; exists {
return mcpEmbeddedResourceToolHandle(c, handler, currentConfig, services, callToolReq)
}
return nil, errs.ErrApiNotFound
}
func mcpTextContentToolHandle(c *core.WebContext, handler MCPToolHandler[MCPTextContent], currentConfig *settings.Config, services MCPAvailableServices, callToolReq *MCPCallToolRequest) (any, *errs.Error) {
result, err := handler.Handle(c, callToolReq, currentConfig, services)
if err != nil {
return nil, errs.Or(err, errs.ErrOperationFailed)
}
callToolResp := MCPCallToolResponse[MCPTextContent]{
Content: result,
IsError: false,
}
return callToolResp, nil
}
func mcpImageContentToolHandle(c *core.WebContext, handler MCPToolHandler[MCPImageContent], currentConfig *settings.Config, services MCPAvailableServices, callToolReq *MCPCallToolRequest) (any, *errs.Error) {
result, err := handler.Handle(c, callToolReq, currentConfig, services)
if err != nil {
return nil, errs.Or(err, errs.ErrOperationFailed)
}
callToolResp := MCPCallToolResponse[MCPImageContent]{
Content: result,
IsError: false,
}
return callToolResp, nil
}
func mcpAudioContentToolHandle(c *core.WebContext, handler MCPToolHandler[MCPAudioContent], currentConfig *settings.Config, services MCPAvailableServices, callToolReq *MCPCallToolRequest) (any, *errs.Error) {
result, err := handler.Handle(c, callToolReq, currentConfig, services)
if err != nil {
return nil, errs.Or(err, errs.ErrOperationFailed)
}
callToolResp := MCPCallToolResponse[MCPAudioContent]{
Content: result,
IsError: false,
}
return callToolResp, nil
}
func mcpResourceLinkToolHandle(c *core.WebContext, handler MCPToolHandler[MCPResourceLink], currentConfig *settings.Config, services MCPAvailableServices, callToolReq *MCPCallToolRequest) (any, *errs.Error) {
result, err := handler.Handle(c, callToolReq, currentConfig, services)
if err != nil {
return nil, errs.Or(err, errs.ErrOperationFailed)
}
callToolResp := MCPCallToolResponse[MCPResourceLink]{
Content: result,
IsError: false,
}
return callToolResp, nil
}
func mcpEmbeddedResourceToolHandle(c *core.WebContext, handler MCPToolHandler[MCPEmbeddedResource], currentConfig *settings.Config, services MCPAvailableServices, callToolReq *MCPCallToolRequest) (any, *errs.Error) {
result, err := handler.Handle(c, callToolReq, currentConfig, services)
if err != nil {
return nil, errs.Or(err, errs.ErrOperationFailed)
}
callToolResp := MCPCallToolResponse[MCPEmbeddedResource]{
Content: result,
IsError: false,
}
return callToolResp, nil
}
func getMCPToolInfo[T MCPTextContent | MCPImageContent | MCPAudioContent | MCPResourceLink | MCPEmbeddedResource](name string, handler MCPToolHandler[T]) *MCPTool {
mcpTool := &MCPTool{
Name: name,
Description: handler.Description(),
}
schemeGenerator := jsonschema.Reflector{
Anonymous: true,
DoNotReference: true,
ExpandedStruct: true,
}
if handler.InputType() != nil {
schema := schemeGenerator.ReflectFromType(handler.InputType())
mcpTool.InputSchema = schema
}
if handler.OutputType() != nil {
schema := schemeGenerator.ReflectFromType(handler.OutputType())
mcpTool.OutputSchema = schema
}
return mcpTool
}