mirror of
https://github.com/mayswind/ezbookkeeping.git
synced 2026-05-20 01:34:24 +08:00
add query accounts / transaction categories / transaction tags mcp handler
This commit is contained in:
@@ -69,6 +69,9 @@ func InitializeMCPHandlers(config *settings.Config) error {
|
|||||||
mcpTools: make([]*MCPTool, 0),
|
mcpTools: make([]*MCPTool, 0),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerMCPTextContentToolHandler(container, MCPQueryAllAccountsToolHandler)
|
||||||
|
registerMCPTextContentToolHandler(container, MCPQueryAllTransactionCategoriesToolHandler)
|
||||||
|
registerMCPTextContentToolHandler(container, MCPQueryAllTransactionTagsToolHandler)
|
||||||
registerMCPTextContentToolHandler(container, MCPQueryLatestExchangeRatesToolHandler)
|
registerMCPTextContentToolHandler(container, MCPQueryLatestExchangeRatesToolHandler)
|
||||||
|
|
||||||
Container = container
|
Container = container
|
||||||
|
|||||||
@@ -0,0 +1,146 @@
|
|||||||
|
package mcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MCPQueryAllAccountsResponse represents the response structure for querying accounts
|
||||||
|
type MCPQueryAllAccountsResponse struct {
|
||||||
|
CashAccounts []string `json:"cashAccounts,omitempty" jsonschema_description:"List of cash account names"`
|
||||||
|
CheckingAccounts []string `json:"checkingAccounts,omitempty" jsonschema_description:"List of checking account names"`
|
||||||
|
SavingsAccounts []string `json:"savingsAccounts,omitempty" jsonschema_description:"List of savings account names"`
|
||||||
|
CreditCardAccounts []string `json:"creditCardAccounts,omitempty" jsonschema_description:"List of credit card account names"`
|
||||||
|
VirtualAccounts []string `json:"virtualAccounts,omitempty" jsonschema_description:"List of virtual account names"`
|
||||||
|
DebtAccounts []string `json:"debtAccounts,omitempty" jsonschema_description:"List of debt account names"`
|
||||||
|
ReceivableAccounts []string `json:"receivableAccounts,omitempty" jsonschema_description:"List of receivable account names"`
|
||||||
|
CertificateOfDepositAccounts []string `json:"certificateOfDepositAccounts,omitempty" jsonschema_description:"List of certificate of deposit account names"`
|
||||||
|
InvestmentAccounts []string `json:"investmentAccounts,omitempty" jsonschema_description:"List of investment account names"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type mcpQueryAllAccountsToolHandler struct{}
|
||||||
|
|
||||||
|
var MCPQueryAllAccountsToolHandler = &mcpQueryAllAccountsToolHandler{}
|
||||||
|
|
||||||
|
// Name returns the name of the MCP tool
|
||||||
|
func (h *mcpQueryAllAccountsToolHandler) Name() string {
|
||||||
|
return "query_all_accounts"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description returns the description of the MCP tool
|
||||||
|
func (h *mcpQueryAllAccountsToolHandler) Description() string {
|
||||||
|
return "Query all accounts for the current user in ezBookkeeping."
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputType returns the input type for the MCP tool request
|
||||||
|
func (h *mcpQueryAllAccountsToolHandler) InputType() reflect.Type {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputType returns the output type for the MCP tool response
|
||||||
|
func (h *mcpQueryAllAccountsToolHandler) OutputType() reflect.Type {
|
||||||
|
return reflect.TypeOf(&MCPQueryAllAccountsResponse{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle processes the MCP call tool request and returns the response
|
||||||
|
func (h *mcpQueryAllAccountsToolHandler) Handle(c *core.WebContext, callToolReq *MCPCallToolRequest, currentConfig *settings.Config, services MCPAvailableServices) (any, []*MCPTextContent, *errs.Error) {
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
accounts, err := services.GetAccountService().GetAllAccountsByUid(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(c, "[query_all_accounts.Handle] failed to get all accounts for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
structuredResponse, response, err := h.createNewMCPQueryAllAccountsResponse(c, accounts)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return structuredResponse, response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *mcpQueryAllAccountsToolHandler) createNewMCPQueryAllAccountsResponse(c *core.WebContext, accounts []*models.Account) (any, []*MCPTextContent, error) {
|
||||||
|
response := MCPQueryAllAccountsResponse{}
|
||||||
|
|
||||||
|
for i := 0; i < len(accounts); i++ {
|
||||||
|
account := accounts[i]
|
||||||
|
|
||||||
|
if account.Hidden || (account.Type == models.ACCOUNT_TYPE_MULTI_SUB_ACCOUNTS && account.ParentAccountId == models.LevelOneAccountParentId) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if account.Category == models.ACCOUNT_CATEGORY_CASH {
|
||||||
|
if response.CashAccounts == nil {
|
||||||
|
response.CashAccounts = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.CashAccounts = append(response.CashAccounts, account.Name)
|
||||||
|
} else if account.Category == models.ACCOUNT_CATEGORY_CHECKING_ACCOUNT {
|
||||||
|
if response.CheckingAccounts == nil {
|
||||||
|
response.CheckingAccounts = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.CheckingAccounts = append(response.CheckingAccounts, account.Name)
|
||||||
|
} else if account.Category == models.ACCOUNT_CATEGORY_SAVINGS_ACCOUNT {
|
||||||
|
if response.SavingsAccounts == nil {
|
||||||
|
response.SavingsAccounts = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.SavingsAccounts = append(response.SavingsAccounts, account.Name)
|
||||||
|
} else if account.Category == models.ACCOUNT_CATEGORY_CREDIT_CARD {
|
||||||
|
if response.CreditCardAccounts == nil {
|
||||||
|
response.CreditCardAccounts = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.CreditCardAccounts = append(response.CreditCardAccounts, account.Name)
|
||||||
|
} else if account.Category == models.ACCOUNT_CATEGORY_VIRTUAL {
|
||||||
|
if response.VirtualAccounts == nil {
|
||||||
|
response.VirtualAccounts = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.VirtualAccounts = append(response.VirtualAccounts, account.Name)
|
||||||
|
} else if account.Category == models.ACCOUNT_CATEGORY_DEBT {
|
||||||
|
if response.DebtAccounts == nil {
|
||||||
|
response.DebtAccounts = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.DebtAccounts = append(response.DebtAccounts, account.Name)
|
||||||
|
} else if account.Category == models.ACCOUNT_CATEGORY_RECEIVABLES {
|
||||||
|
if response.ReceivableAccounts == nil {
|
||||||
|
response.ReceivableAccounts = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.ReceivableAccounts = append(response.ReceivableAccounts, account.Name)
|
||||||
|
} else if account.Category == models.ACCOUNT_CATEGORY_CERTIFICATE_OF_DEPOSIT {
|
||||||
|
if response.CertificateOfDepositAccounts == nil {
|
||||||
|
response.CertificateOfDepositAccounts = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.CertificateOfDepositAccounts = append(response.CertificateOfDepositAccounts, account.Name)
|
||||||
|
} else if account.Category == models.ACCOUNT_CATEGORY_INVESTMENT {
|
||||||
|
if response.InvestmentAccounts == nil {
|
||||||
|
response.InvestmentAccounts = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.InvestmentAccounts = append(response.InvestmentAccounts, account.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := json.Marshal(response)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, []*MCPTextContent{
|
||||||
|
NewMCPTextContent(string(content)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package mcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/models"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MCPQueryAllTransactionCategoriesResponse represents the response structure for querying transaction categories
|
||||||
|
type MCPQueryAllTransactionCategoriesResponse struct {
|
||||||
|
IncomeCategories map[string][]string `json:"incomeCategories" jsonschema_description:"List of income categories, field key is the primary category name, field value is the list of secondary category names"`
|
||||||
|
ExpenseCategories map[string][]string `json:"expenseCategories" jsonschema_description:"List of expense categories, field key is the primary category name, field value is the list of secondary category names"`
|
||||||
|
TransferCategories map[string][]string `json:"transferCategories" jsonschema_description:"List of transfer categories, field key is the primary category name, field value is the list of secondary category names"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type mcpQueryAllTransactionCategoriesToolHandler struct{}
|
||||||
|
|
||||||
|
var MCPQueryAllTransactionCategoriesToolHandler = &mcpQueryAllTransactionCategoriesToolHandler{}
|
||||||
|
|
||||||
|
// Name returns the name of the MCP tool
|
||||||
|
func (h *mcpQueryAllTransactionCategoriesToolHandler) Name() string {
|
||||||
|
return "query_all_transaction_categories"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description returns the description of the MCP tool
|
||||||
|
func (h *mcpQueryAllTransactionCategoriesToolHandler) Description() string {
|
||||||
|
return "Query all transaction categories for the current user in ezBookkeeping."
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputType returns the input type for the MCP tool request
|
||||||
|
func (h *mcpQueryAllTransactionCategoriesToolHandler) InputType() reflect.Type {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputType returns the output type for the MCP tool response
|
||||||
|
func (h *mcpQueryAllTransactionCategoriesToolHandler) OutputType() reflect.Type {
|
||||||
|
return reflect.TypeOf(&MCPQueryAllTransactionCategoriesResponse{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle processes the MCP call tool request and returns the response
|
||||||
|
func (h *mcpQueryAllTransactionCategoriesToolHandler) Handle(c *core.WebContext, callToolReq *MCPCallToolRequest, currentConfig *settings.Config, services MCPAvailableServices) (any, []*MCPTextContent, *errs.Error) {
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
categories, err := services.GetTransactionCategoryService().GetAllCategoriesByUid(c, uid, 0, -1)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(c, "[query_all_transaction_categories.Handle] failed to get categories for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
structuredResponse, response, err := h.createNewMCPQueryAllTransactionCategoriesResponse(c, categories)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return structuredResponse, response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *mcpQueryAllTransactionCategoriesToolHandler) createNewMCPQueryAllTransactionCategoriesResponse(c *core.WebContext, categories []*models.TransactionCategory) (any, []*MCPTextContent, error) {
|
||||||
|
response := MCPQueryAllTransactionCategoriesResponse{
|
||||||
|
IncomeCategories: make(map[string][]string),
|
||||||
|
ExpenseCategories: make(map[string][]string),
|
||||||
|
TransferCategories: make(map[string][]string),
|
||||||
|
}
|
||||||
|
|
||||||
|
categoriesMap := make(map[int64]*models.TransactionCategory, len(categories))
|
||||||
|
|
||||||
|
for i := 0; i < len(categories); i++ {
|
||||||
|
category := categories[i]
|
||||||
|
|
||||||
|
if !category.Hidden {
|
||||||
|
categoriesMap[category.CategoryId] = category
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(categories); i++ {
|
||||||
|
category := categories[i]
|
||||||
|
|
||||||
|
if category.Hidden || category.ParentCategoryId == models.LevelOneTransactionCategoryParentId {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parentCategory, exists := categoriesMap[category.ParentCategoryId]
|
||||||
|
|
||||||
|
if !exists || parentCategory == nil {
|
||||||
|
log.Warnf(c, "[query_all_transaction_categories.createNewMCPQueryAllTransactionCategoriesResponse] category \"id:%d\" has no parent category", category.CategoryId)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if parentCategory.Hidden {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if category.Type == models.CATEGORY_TYPE_INCOME {
|
||||||
|
_, exists := response.IncomeCategories[parentCategory.Name]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
response.IncomeCategories[parentCategory.Name] = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.IncomeCategories[parentCategory.Name] = append(response.IncomeCategories[parentCategory.Name], category.Name)
|
||||||
|
} else if category.Type == models.CATEGORY_TYPE_EXPENSE {
|
||||||
|
_, exists := response.ExpenseCategories[parentCategory.Name]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
response.ExpenseCategories[parentCategory.Name] = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.ExpenseCategories[parentCategory.Name] = append(response.ExpenseCategories[parentCategory.Name], category.Name)
|
||||||
|
} else if category.Type == models.CATEGORY_TYPE_TRANSFER {
|
||||||
|
_, exists := response.TransferCategories[parentCategory.Name]
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
response.TransferCategories[parentCategory.Name] = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.TransferCategories[parentCategory.Name] = append(response.TransferCategories[parentCategory.Name], category.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := json.Marshal(response)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, []*MCPTextContent{
|
||||||
|
NewMCPTextContent(string(content)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package mcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/core"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/errs"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/log"
|
||||||
|
"github.com/mayswind/ezbookkeeping/pkg/settings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MCPAllQueryTransactionTagsResponse represents the response structure for querying transaction tags
|
||||||
|
type MCPAllQueryTransactionTagsResponse struct {
|
||||||
|
Tags []string `json:"tags" jsonschema_description:"List of transaction tags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type mcpQueryAllTransactionTagsToolHandler struct{}
|
||||||
|
|
||||||
|
var MCPQueryAllTransactionTagsToolHandler = &mcpQueryAllTransactionTagsToolHandler{}
|
||||||
|
|
||||||
|
// Name returns the name of the MCP tool
|
||||||
|
func (h *mcpQueryAllTransactionTagsToolHandler) Name() string {
|
||||||
|
return "query_all_transaction_tags"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description returns the description of the MCP tool
|
||||||
|
func (h *mcpQueryAllTransactionTagsToolHandler) Description() string {
|
||||||
|
return "Query transaction tags for the current user in ezBookkeeping."
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputType returns the input type for the MCP tool request
|
||||||
|
func (h *mcpQueryAllTransactionTagsToolHandler) InputType() reflect.Type {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OutputType returns the output type for the MCP tool response
|
||||||
|
func (h *mcpQueryAllTransactionTagsToolHandler) OutputType() reflect.Type {
|
||||||
|
return reflect.TypeOf(&MCPAllQueryTransactionTagsResponse{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle processes the MCP call tool request and returns the response
|
||||||
|
func (h *mcpQueryAllTransactionTagsToolHandler) Handle(c *core.WebContext, callToolReq *MCPCallToolRequest, currentConfig *settings.Config, services MCPAvailableServices) (any, []*MCPTextContent, *errs.Error) {
|
||||||
|
uid := c.GetCurrentUid()
|
||||||
|
tags, err := services.GetTransactionTagService().GetAllTagsByUid(c, uid)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf(c, "[query_all_transaction_tags.Handle] failed to get tags for user \"uid:%d\", because %s", uid, err.Error())
|
||||||
|
return nil, nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
tagNames := make([]string, len(tags))
|
||||||
|
|
||||||
|
for i := 0; i < len(tags); i++ {
|
||||||
|
tagNames[i] = tags[i].Name
|
||||||
|
}
|
||||||
|
|
||||||
|
response := MCPAllQueryTransactionTagsResponse{
|
||||||
|
Tags: tagNames,
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := json.Marshal(response)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, errs.Or(err, errs.ErrOperationFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
return response, []*MCPTextContent{
|
||||||
|
NewMCPTextContent(string(content)),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user