An MCP server for managing expense tracking with SQLite database backend
This FastMCP Expense Tracker server has 5 tools with inconsistent quality. All tools have basic descriptions (10-60 chars) and declared input schemas with type information. However, descriptions are terse and lack LLM-optimization guidance; parameter descriptions are minimal; output schemas are not documented; error handling is weak (mostly bare string returns with no recovery guidance); and SQL injection vulnerabilities exist in edit_expense() where field names are interpolated directly into the query. The server demonstrates a functional but below-baseline implementation typical of community projects.
Add a new expense entry to the database.
Delete an expense by its ID.
Edit a specific field of an expense. Allowed fields: date, amount, category, title, description
List expense entries within an inclusive date range.
Summarize expenses by category within an inclusive date range.
SQL Injection vulnerability in edit_expense(): field parameter is directly interpolated into UPDATE query (line: query = f"UPDATE expenses SET {field} = ? WHERE id = ?"). Although the function checks allowed_fields whitelist, this pattern is dangerous and violates secure coding practices. An attacker could bypass this if the whitelist is ever misconfigured.
Output schemas are not documented. Tools return plain dicts/lists (e.g. add_expense returns {"status": "ok", "id": ...}, list_expenses returns list of dicts, delete_expense/edit_expense return bare strings). LLMs cannot predict the structure of results, they must guess or fail. This violates the baseline that 100% of A+ tools have documented return types.
Error handling lacks recovery guidance. delete_expense and edit_expense return bare error messages (e.g. "No expense found with ID {expense_id}") with no suggestion of what the LLM should do next (e.g., list_expenses to find valid IDs). Per pattern:recovery-guide, errors must tell the LLM what to do.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | C | 67 | 2026-07-28+ | v2 |
| 2026-03-09 | F | 46 | - | v1 |
Inconsistent return types. add_expense returns a dict, list_expenses returns a list of dicts, delete_expense/edit_expense return strings. This heterogeneity forces the LLM to parse multiple output formats and wastes tokens. Production tools standardize on structured responses.
Parameter descriptions are terse and lack LLM-optimization. E.g., date field: "Date of the expense", no format specification (ISO 8601?), no validation range. These are 15-30 chars and generic.
edit_expense has inconsistency between declared schema and implementation. Schema declares field as a free-form string, but docstring says "Allowed fields: date, amount, category, title, description". Schema should declare field as an enum ['date', 'amount', 'category', 'title', 'description'], not a string, to prevent hallucinated invalid field names.
No pagination support. list_expenses and summarize lack offset/limit parameters. If an expense log grows to 1000+ entries, these tools will return all of them, exploding the context window.
No idempotency guarantees. add_expense does not check for duplicate (date, amount, category, note) combinations. If an agent retries after ambiguous failure, a duplicate expense is silently created. Per pattern:idempotent-operation, repeated calls with the same input should produce the same result.
No confirmation/dry-run for destructive operations. delete_expense irreversibly deletes records with no rollback or confirmation step. Per pattern:confirmation-request, agents make mistakes, destructive tools should support confirmation.
Tool descriptions lack WHEN context. E.g., summarize description is "Summarize expenses by category within an inclusive date range." This does not explain when to call it vs list_expenses, or whether it returns totals per category vs per subcategory. LLMs benefit from WHEN guidance.