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
51 changes: 51 additions & 0 deletions src/lampyrid/tools/_annotations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""Shared MCP tool annotation helpers.

This module centralizes FastMCP annotation hints so tool definitions can stay
concise and consistent across domains.
"""

from mcp.types import ToolAnnotations


def readonly_annotations(title: str) -> ToolAnnotations:
"""Build annotations for read-only MCP tools.

Args:
title: Human-friendly tool title for MCP clients.

Returns:
ToolAnnotations: Annotations suitable for FastMCP's ``@tool`` decorator.

"""
return ToolAnnotations(
title=title,
readOnlyHint=True,
idempotentHint=True,
openWorldHint=False,
)


def mutating_annotations(
title: str,
*,
destructive: bool = False,
idempotent: bool = False,
) -> ToolAnnotations:
"""Build annotations for mutating MCP tools.

Args:
title: Human-friendly tool title for MCP clients.
destructive: Whether the tool performs destructive changes.
idempotent: Whether repeated identical calls have no additional effect.

Returns:
ToolAnnotations: Annotations suitable for FastMCP's ``@tool`` decorator.

"""
return ToolAnnotations(
title=title,
readOnlyHint=False,
destructiveHint=destructive,
idempotentHint=idempotent,
openWorldHint=False,
)
16 changes: 13 additions & 3 deletions src/lampyrid/tools/accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
SearchAccountRequest,
)
from ..services.accounts import AccountService
from ._annotations import readonly_annotations


def create_accounts_server(client: FireflyClient) -> FastMCP:
Expand All @@ -32,7 +33,10 @@ def create_accounts_server(client: FireflyClient) -> FastMCP:

accounts_mcp = FastMCP('accounts')

@accounts_mcp.tool(tags={'accounts'})
@accounts_mcp.tool(
tags={'accounts'},
annotations=readonly_annotations('List Accounts'),
)
async def list_accounts(req: ListAccountRequest) -> List[Account]:
"""Retrieve accounts from Firefly III.

Expand All @@ -41,15 +45,21 @@ async def list_accounts(req: ListAccountRequest) -> List[Account]:
"""
return await account_service.list_accounts(req)

@accounts_mcp.tool(tags={'accounts'})
@accounts_mcp.tool(
tags={'accounts'},
annotations=readonly_annotations('Get Account'),
)
async def get_account(req: GetAccountRequest) -> Account:
"""Retrieve detailed account information including current balance and currency.

Use this to verify account details before transactions.
"""
return await account_service.get_account(req)

@accounts_mcp.tool(tags={'accounts'})
@accounts_mcp.tool(
tags={'accounts'},
annotations=readonly_annotations('Search Accounts'),
)
async def search_accounts(req: SearchAccountRequest) -> List[Account]:
"""Find accounts by partial name matching.

Expand Down
31 changes: 25 additions & 6 deletions src/lampyrid/tools/budgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
ListBudgetsRequest,
)
from ..services.budgets import BudgetService
from ._annotations import mutating_annotations, readonly_annotations


def create_budgets_server(client: FireflyClient) -> FastMCP:
Expand All @@ -38,23 +39,32 @@ def create_budgets_server(client: FireflyClient) -> FastMCP:

budgets_mcp = FastMCP('budgets')

@budgets_mcp.tool(tags={'budgets'})
@budgets_mcp.tool(
tags={'budgets'},
annotations=readonly_annotations('List Budgets'),
)
async def list_budgets(req: ListBudgetsRequest) -> List[Budget]:
"""Retrieve your budgets for expense tracking and financial planning.

Filter by active status to see current or all budgets.
"""
return await budget_service.list_budgets(req)

@budgets_mcp.tool(tags={'budgets'})
@budgets_mcp.tool(
tags={'budgets'},
annotations=readonly_annotations('Get Budget'),
)
async def get_budget(req: GetBudgetRequest) -> Budget:
"""Retrieve detailed budget information including name, status, and notes.

Use this to verify budget details before assigning transactions.
"""
return await budget_service.get_budget(req)

@budgets_mcp.tool(tags={'budgets', 'analysis'})
@budgets_mcp.tool(
tags={'budgets', 'analysis'},
annotations=readonly_annotations('Get Budget Spending'),
)
async def get_budget_spending(req: GetBudgetSpendingRequest) -> BudgetSpending:
"""Analyze spending against a budget.

Expand All @@ -63,23 +73,32 @@ async def get_budget_spending(req: GetBudgetSpendingRequest) -> BudgetSpending:
"""
return await budget_service.get_budget_spending(req)

@budgets_mcp.tool(tags={'budgets', 'analysis'})
@budgets_mcp.tool(
tags={'budgets', 'analysis'},
annotations=readonly_annotations('Get Budget Summary'),
)
async def get_budget_summary(req: GetBudgetSummaryRequest) -> BudgetSummary:
"""Comprehensive overview of all budget performance with totals and spending analysis.

Perfect for monthly reviews and financial dashboards.
"""
return await budget_service.get_budget_summary(req)

@budgets_mcp.tool(tags={'budgets', 'analysis'})
@budgets_mcp.tool(
tags={'budgets', 'analysis'},
annotations=readonly_annotations('Get Available Budget'),
)
async def get_available_budget(req: GetAvailableBudgetRequest) -> AvailableBudget:
"""Check unallocated budget available for new budgets or unexpected expenses.

Shows money set aside but not assigned to specific budgets.
"""
return await budget_service.get_available_budget(req)

@budgets_mcp.tool(tags={'budgets', 'create'})
@budgets_mcp.tool(
tags={'budgets', 'create'},
annotations=mutating_annotations('Create Budget'),
)
async def create_budget(req: CreateBudgetRequest) -> Budget:
"""Create a new budget for expense tracking and financial planning.

Expand Down
21 changes: 17 additions & 4 deletions src/lampyrid/tools/insights.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
TransferInsightResult,
)
from ..services.insights import InsightService
from ._annotations import readonly_annotations


def create_insights_server(client: FireflyClient) -> FastMCP:
Expand All @@ -34,7 +35,10 @@ def create_insights_server(client: FireflyClient) -> FastMCP:

insights_mcp = FastMCP('insights')

@insights_mcp.tool(tags={'insights', 'expenses', 'analysis'})
@insights_mcp.tool(
tags={'insights', 'expenses', 'analysis'},
annotations=readonly_annotations('Get Expense Insight'),
)
async def get_expense_insight(req: GetExpenseInsightRequest) -> ExpenseInsightResult:
"""Analyze expenses for a time period with optional grouping.

Expand All @@ -50,7 +54,10 @@ async def get_expense_insight(req: GetExpenseInsightRequest) -> ExpenseInsightRe
"""
return await insight_service.get_expense_insight(req)

@insights_mcp.tool(tags={'insights', 'income', 'analysis'})
@insights_mcp.tool(
tags={'insights', 'income', 'analysis'},
annotations=readonly_annotations('Get Income Insight'),
)
async def get_income_insight(req: GetIncomeInsightRequest) -> IncomeInsightResult:
"""Analyze income for a time period with optional grouping.

Expand All @@ -64,7 +71,10 @@ async def get_income_insight(req: GetIncomeInsightRequest) -> IncomeInsightResul
"""
return await insight_service.get_income_insight(req)

@insights_mcp.tool(tags={'insights', 'transfers', 'analysis'})
@insights_mcp.tool(
tags={'insights', 'transfers', 'analysis'},
annotations=readonly_annotations('Get Transfer Insight'),
)
async def get_transfer_insight(req: GetTransferInsightRequest) -> TransferInsightResult:
"""Analyze transfers for a time period with optional account breakdown.

Expand All @@ -78,7 +88,10 @@ async def get_transfer_insight(req: GetTransferInsightRequest) -> TransferInsigh
"""
return await insight_service.get_transfer_insight(req)

@insights_mcp.tool(tags={'insights', 'summary', 'analysis'})
@insights_mcp.tool(
tags={'insights', 'summary', 'analysis'},
annotations=readonly_annotations('Get Financial Summary'),
)
async def get_financial_summary(req: GetFinancialSummaryRequest) -> FinancialSummary:
"""Get a complete financial overview for a time period.

Expand Down
51 changes: 41 additions & 10 deletions src/lampyrid/tools/transactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
UpdateTransactionRequest,
)
from ..services.transactions import TransactionService
from ._annotations import mutating_annotations, readonly_annotations


def create_transactions_server(client: FireflyClient) -> FastMCP:
Expand All @@ -40,7 +41,10 @@ def create_transactions_server(client: FireflyClient) -> FastMCP:

transactions_mcp = FastMCP('transactions')

@transactions_mcp.tool(tags={'transactions', 'create'})
@transactions_mcp.tool(
tags={'transactions', 'create'},
annotations=mutating_annotations('Create Withdrawal'),
)
async def create_withdrawal(req: CreateWithdrawalRequest) -> Transaction:
"""Record expenses and spending.

Expand All @@ -50,7 +54,10 @@ async def create_withdrawal(req: CreateWithdrawalRequest) -> Transaction:
transaction = await transaction_service.create_withdrawal(req)
return transaction

@transactions_mcp.tool(tags={'transactions', 'create'})
@transactions_mcp.tool(
tags={'transactions', 'create'},
annotations=mutating_annotations('Create Deposit'),
)
async def create_deposit(req: CreateDepositRequest) -> Transaction:
"""Record income and money received.

Expand All @@ -60,7 +67,10 @@ async def create_deposit(req: CreateDepositRequest) -> Transaction:
transaction = await transaction_service.create_deposit(req)
return transaction

@transactions_mcp.tool(tags={'transactions', 'create'})
@transactions_mcp.tool(
tags={'transactions', 'create'},
annotations=mutating_annotations('Create Transfer'),
)
async def create_transfer(req: CreateTransferRequest) -> Transaction:
"""Move money between your own accounts.

Expand All @@ -69,7 +79,10 @@ async def create_transfer(req: CreateTransferRequest) -> Transaction:
transaction = await transaction_service.create_transfer(req)
return transaction

@transactions_mcp.tool(tags={'transactions', 'create', 'bulk'})
@transactions_mcp.tool(
tags={'transactions', 'create', 'bulk'},
annotations=mutating_annotations('Create Bulk Transactions'),
)
async def create_bulk_transactions(req: CreateBulkTransactionsRequest) -> BulkCreateResult:
"""Efficiently create multiple transactions in one operation.

Expand All @@ -78,7 +91,10 @@ async def create_bulk_transactions(req: CreateBulkTransactionsRequest) -> BulkCr
"""
return await transaction_service.create_bulk_transactions(req)

@transactions_mcp.tool(tags={'transactions', 'query'})
@transactions_mcp.tool(
tags={'transactions', 'query'},
annotations=readonly_annotations('Get Transaction'),
)
async def get_transaction(req: GetTransactionRequest) -> Transaction:
"""Retrieve complete transaction details.

Expand All @@ -87,15 +103,21 @@ async def get_transaction(req: GetTransactionRequest) -> Transaction:
"""
return await transaction_service.get_transaction(req)

@transactions_mcp.tool(tags={'transactions', 'query'})
@transactions_mcp.tool(
tags={'transactions', 'query'},
annotations=readonly_annotations('Get Transactions'),
)
async def get_transactions(req: GetTransactionsRequest) -> TransactionListResponse:
"""Retrieve transaction history with flexible filtering and pagination.

Essential for financial analysis, spending pattern review, and account activity monitoring.
"""
return await transaction_service.get_transactions(req)

@transactions_mcp.tool(tags={'transactions', 'query'})
@transactions_mcp.tool(
tags={'transactions', 'query'},
annotations=readonly_annotations('Search Transactions'),
)
async def search_transactions(req: SearchTransactionsRequest) -> TransactionListResponse:
"""Search transactions with powerful filtering options.

Expand All @@ -105,7 +127,10 @@ async def search_transactions(req: SearchTransactionsRequest) -> TransactionList
"""
return await transaction_service.search_transactions(req)

@transactions_mcp.tool(tags={'transactions', 'manage'})
@transactions_mcp.tool(
tags={'transactions', 'manage'},
annotations=mutating_annotations('Delete Transaction', destructive=True),
)
async def delete_transaction(req: DeleteTransactionRequest) -> bool:
"""Permanently remove a transaction.

Expand All @@ -114,15 +139,21 @@ async def delete_transaction(req: DeleteTransactionRequest) -> bool:
"""
return await transaction_service.delete_transaction(req)

@transactions_mcp.tool(tags={'transactions', 'manage'})
@transactions_mcp.tool(
tags={'transactions', 'manage'},
annotations=mutating_annotations('Update Transaction'),
)
async def update_transaction(req: UpdateTransactionRequest) -> Transaction:
"""Modify transaction details such as amounts, descriptions, dates, accounts, etc.

Useful for correcting imported data or updating incomplete information.
"""
return await transaction_service.update_transaction(req)

@transactions_mcp.tool(tags={'transactions', 'manage', 'bulk'})
@transactions_mcp.tool(
tags={'transactions', 'manage', 'bulk'},
annotations=mutating_annotations('Bulk Update Transactions'),
)
async def bulk_update_transactions(req: BulkUpdateTransactionsRequest) -> BulkUpdateResult:
"""Efficiently update multiple transactions in one operation.

Expand Down
Loading
Loading