package api import ( "encoding/json" "github.com/gin-gonic/gin" "github.com/mayswind/ezbookkeeping/pkg/core" "github.com/mayswind/ezbookkeeping/pkg/errs" "github.com/mayswind/ezbookkeeping/pkg/mcp" "github.com/mayswind/ezbookkeeping/pkg/services" "github.com/mayswind/ezbookkeeping/pkg/settings" ) const mcpServerName = "ezBookkeeping-mcp" // ModelContextProtocolAPI represents model context protocol api type ModelContextProtocolAPI struct { ApiUsingConfig transactions *services.TransactionService transactionCategories *services.TransactionCategoryService transactionTags *services.TransactionTagService accounts *services.AccountService users *services.UserService userCustomExchangeRates *services.UserCustomExchangeRatesService } // Initialize a model context protocol api singleton instance var ( ModelContextProtocols = &ModelContextProtocolAPI{ ApiUsingConfig: ApiUsingConfig{ container: settings.Container, }, transactions: services.Transactions, transactionCategories: services.TransactionCategories, transactionTags: services.TransactionTags, accounts: services.Accounts, users: services.Users, userCustomExchangeRates: services.UserCustomExchangeRates, } ) // InitializeHandler returns the initialize response for model context protocol func (a *ModelContextProtocolAPI) InitializeHandler(c *core.WebContext, jsonRPCRequest *core.JSONRPCRequest) (any, *errs.Error) { var initRequest mcp.MCPInitializeRequest if jsonRPCRequest.Params != nil { if err := json.Unmarshal(jsonRPCRequest.Params, &initRequest); err != nil { return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) } } else { return nil, errs.ErrIncompleteOrIncorrectSubmission } protocolVersion := mcp.MCPProtocolVersion(initRequest.ProtocolVersion) _, exists := mcp.SupportedMCPVersion[protocolVersion] if !exists { protocolVersion = mcp.LatestSupportedMCPVersion } initResp := mcp.MCPInitializeResponse{ ProtocolVersion: string(protocolVersion), Capabilities: &mcp.MCPCapabilities{ Tools: &mcp.MCPToolCapabilities{ ListChanged: false, }, }, ServerInfo: &mcp.MCPImplementation{ Name: mcpServerName, Title: a.CurrentConfig().AppName, Version: settings.Version, }, } return initResp, nil } // ListResourcesHandler returns the list of resources for model context protocol func (a *ModelContextProtocolAPI) ListResourcesHandler(c *core.WebContext, jsonRPCRequest *core.JSONRPCRequest) (any, *errs.Error) { listResourcesResp := mcp.MCPListResourcesResponse{ Resources: make([]*mcp.MCPResource, 0), } return listResourcesResp, nil } // ReadResourceHandler returns the resource details for a specific resource in model context protocol func (a *ModelContextProtocolAPI) ReadResourceHandler(c *core.WebContext, jsonRPCRequest *core.JSONRPCRequest) (any, *errs.Error) { var readResourceReq mcp.MCPReadResourceRequest if jsonRPCRequest.Params != nil { if err := json.Unmarshal(jsonRPCRequest.Params, &readResourceReq); err != nil { return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) } } else { return nil, errs.ErrIncompleteOrIncorrectSubmission } return nil, errs.ErrApiNotFound } // ListToolsHandler returns the list of tools for model context protocol func (a *ModelContextProtocolAPI) ListToolsHandler(c *core.WebContext, jsonRPCRequest *core.JSONRPCRequest) (any, *errs.Error) { mcpVersion := a.getMCPVersion(c) toolsInfo := mcp.Container.GetMCPTools() finalToolsInfos := make([]*mcp.MCPTool, len(toolsInfo)) for i := 0; i < len(toolsInfo); i++ { finalToolsInfos[i] = &mcp.MCPTool{ Name: toolsInfo[i].Name, InputSchema: toolsInfo[i].InputSchema, Title: toolsInfo[i].Title, Description: toolsInfo[i].Description, } if mcpVersion >= string(mcp.ToolResultStructuredContentMinVersion) { finalToolsInfos[i].OutputSchema = toolsInfo[i].OutputSchema } } listToolsResp := mcp.MCPListToolsResponse{ Tools: finalToolsInfos, } return listToolsResp, nil } // CallToolHandler returns the result of calling a specific tool for model context protocol func (a *ModelContextProtocolAPI) CallToolHandler(c *core.WebContext, jsonRPCRequest *core.JSONRPCRequest) (any, *errs.Error) { var callToolReq mcp.MCPCallToolRequest if jsonRPCRequest.Params != nil { if err := json.Unmarshal(jsonRPCRequest.Params, &callToolReq); err != nil { return nil, errs.NewIncompleteOrIncorrectSubmissionError(err) } } else { return nil, errs.ErrIncompleteOrIncorrectSubmission } result, err := mcp.Container.HandleTool(c, &callToolReq, a.CurrentConfig(), a) if err != nil { return nil, err } return result, nil } // PingHandler return the ping response for model context protocol func (a *ModelContextProtocolAPI) PingHandler(c *core.WebContext, jsonRPCRequest *core.JSONRPCRequest) (any, *errs.Error) { return gin.H{}, nil } // GetTransactionService implements the MCPAvailableServices interface func (a *ModelContextProtocolAPI) GetTransactionService() *services.TransactionService { return a.transactions } // GetUserCustomExchangeRatesService implements the MCPAvailableServices interface func (a *ModelContextProtocolAPI) GetTransactionCategoryService() *services.TransactionCategoryService { return a.transactionCategories } // GetTransactionTagService implements the MCPAvailableServices interface func (a *ModelContextProtocolAPI) GetTransactionTagService() *services.TransactionTagService { return a.transactionTags } // GetAccountService implements the MCPAvailableServices interface func (a *ModelContextProtocolAPI) GetAccountService() *services.AccountService { return a.accounts } // GetUserCustomExchangeRatesService implements the MCPAvailableServices interface func (a *ModelContextProtocolAPI) GetUserService() *services.UserService { return a.users } // getMCPVersion returns the MCP protocol version from the request header func (a *ModelContextProtocolAPI) getMCPVersion(c *core.WebContext) string { return c.GetHeader(mcp.MCPProtocolVersionHeaderName) }