An MCP server for tracking and managing expense entries with SQLite database backend
ExpenseTracker has three well-structured tools with clear, actionable descriptions and complete input schemas. Tool names follow verb_noun convention (add_, list_, summarize). All parameters have type definitions and descriptions. However, output schemas are not formally documented in the code, responses are inferred from return type hints rather than explicit schema definitions. Error handling is present but minimal; error messages don't guide recovery (e.g., 'Database error: <str(e)>' is opaque to LLMs). No input validation beyond what SQLite provides, no parameter constraints (enums for categories), and no discussion of idempotence or side effects. The server lacks tool annotations (readOnlyHint, destructiveHint) despite having both read-only and destructive tools. Overall, the definitions are solid for a sample server but lack the polish and guardrails of production tools.
Add a new expense entry to the database. Args: date: Expense date in YYYY-MM-DD format amount: Expense amount category: Expense category subcategory: Optional subcategory note: Optional note/description Returns: Dict with status, id (if successful), and message
List expense entries within an inclusive date range. Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format Returns: List of expense dictionaries
Summarize expenses by category within an inclusive date range. Args: start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format category: Optional category filter Returns: List of summary dictionaries by category
Output schemas not formally documented. Tool descriptions state 'Returns: Dict/List' but lack field-level documentation. LLMs cannot plan downstream operations without knowing response structure (e.g., does list_expenses return 'expense_id' or 'id'? does summarize include 'count' or 'total_count'?). Per pattern:tool, 100% of A+ tools document return types with field names and types.
No tool annotations. add_expense is destructive (writes to DB) and list_expenses/summarize are read-only, but neither @mcp.tool() registration nor responses carry readOnlyHint or destructiveHint metadata. Agents cannot distinguish safe-to-retry ops from irreversible ones without this signal. Per pattern:command-tool, state-modifying tools must declare their mutation class.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | B | 76 | 2026-07-28+ | v2 |
| 2026-03-09 | C | 62 | - | v1 |
Error messages do not guide recovery. add_expense returns generic 'Database error: <str(e)>' on sqlite3.OperationalError (other than readonly). An LLM cannot act on this, is it retryable? User-fixable? Fatal? Per pattern:recovery-guide, errors must categorize as retryable/user-fixable/fatal and suggest next steps (e.g., 'Invalid date format. Use YYYY-MM-DD').
No input validation or parameter constraints. category is a free-form string with no enum, even though the categories resource lists fixed options (Food & Dining, Transportation, etc.). LLMs will hallucinate invalid categories. Per pattern:constrained-input, category should be an enum or validated against the categories resource.
Date parameters lack format constraints in schema. date, start_date, end_date are strings with descriptions saying 'YYYY-MM-DD format' but no regex or minLength/maxLength in the JSON Schema. LLMs frequently pass malformed dates; formal constraints in the schema (pattern: '^\d{4}-\d{2}-\d{2}$') are machine-parseable.
amount parameter (add_expense) lacks range constraints. No minimum (must be > 0?) or maximum. Unbounded numbers let LLMs pass absurd values (negative amounts, 1e20) that corrupt data.
list_expenses and summarize return errors as dict/list items within the result list ({'status': 'error', 'message': ...}) instead of raising structured exceptions. This pollutes the result, an LLM expecting a list of expense dicts will misparse an error dict. Per pattern:response-shaper, errors should be distinct from success responses (e.g., via HTTP status codes or a {success: bool, data/error} wrapper).
No pagination support. list_expenses has no limit or offset/page parameters. A database with thousands of expenses will return all rows, blowing context window. Per pattern:paginated-result, tools returning lists must accept limit and offset and return a total count or next_cursor.
Idempotence not discussed. add_expense has no deduplication or idempotency key. If an agent retries after a transient failure, a duplicate expense is silently created. Per pattern:idempotent-operation, irreversible tools should support an optional idempotency_key or document retry behavior.
Confirmation or dry-run pattern absent. add_expense modifies state without confirmation. Per pattern:confirmation-request, irreversible operations should support dry_run or require explicit confirmation, especially in agents that may hallucinate values.
No batch/upsert variants. If an agent needs to add 10 expenses, it must call add_expense 10 times sequentially, wasting tokens and latency.