Expense Tracker MCP Server with async PostgreSQL support for managing user authentication, transactions, and financial reporting
The Expense Tracker MCP server has significant gaps in definition quality. While tool names follow a verb-first convention and most include basic descriptions, the overall execution falls short of production standards. Parameter descriptions are present but often generic ('User ID (required)' with no context on format or role). Input schemas are visible and properly typed, but output schemas are minimally documented, responses show status/message wrappers but lack guidance on actual data structures returned. Error handling is basic: responses return success/error status but rarely provide actionable recovery guidance. Several critical security concerns exist: authentication tokens appear as required parameters in most tools (violating secret-injection pattern), and password parameters are exposed in function signatures. Tool composition reveals redundancy and unclear separation of concerns (e.g., 'add_transaction' vs 'bulk_add_transactions' differs only in input count, not semantic structure). Only 6/15 tools have descriptions exceeding 50 characters; many are terse ('Get top transaction categories' for get_top_transaction_categories). Parameter descriptions often fail to explain constraints, formats, or relationships, 'frequency (str, optional): Recurrence frequency (daily, weekly, monthly, none, yearly)' lists values but doesn't explain when to use each or what 'none' means in practical terms.
Add a new expense (debit) to the database. Creates a new expense record with required and optional fields. Generates a unique transaction ID and automatically sets creation timestamp. Args: token (str): Valid JWT token (required) amount (float): Expense amount in currency (required) category (str): Expense category/type (required) tags (str): Tags for expense classification (required) payment_method (str): Payment method used (required) status (str): Expense status (required) frequency (str, optional): Recurrence frequency (daily, weekly, monthly, none, yearly) transaction_date (str, optional): Date in YYYY-MM-DD format. Defaults to current date notes (str, optional): Additional notes or description Returns: dict: {"result": {"status": "success", "message": "Expense added successfully"}}
Add multiple transactions at once. Args: token (str): JWT authentication token transactions (List[dict]): List of transaction objects, each containing: - amount (float): Required - category (str): Required - tags (str): Required - payment_method (str): Required - status (str): Required - transaction_type (str): Required ('expense' or 'credit') - frequency (str, optional): 'none', 'daily', 'weekly', 'monthly', 'yearly' - transaction_date (str, optional): 'YYYY-MM-DD' format - notes (str, optional) Returns: dict: Summary of added transactions with success/failure counts
Change user password. Updates password after verifying old password. Requires valid user_id. Args: user_id (str): User ID (required) old_password (str): Current password (required) new_password (str): New password meeting security requirements (required) Returns: dict: Success or error status
Authentication tokens exposed as required tool parameters across 12+ tools (token in add_transaction, bulk_add_transactions, get_all_transactions, get_selected_transactions, get_total_transactions, etc.). This violates secret-injection pattern: tokens should be server-side only, never passed in tool calls. Secrets in parameters are logged, traced, and risk exposure in prompt history.
Password parameters (old_password, new_password) appear in function signatures in change_password and reset_password. While hashing occurs server-side, passwords should never be tool parameters, they belong in secure input channels or server-side injection only. Current design risks logging passwords in agent traces.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | C | 63 | 2026-07-28+ | v2 |
| 2026-03-09 | F | 32 | - | v1 |
Permanently delete user account and all associated data. Removes the user account and all their transactions from the database. This action cannot be undone. Requires email to be verified before deletion. Args: token (str): Valid JWT authentication token (required) Returns: dict: Status confirming account deletion or error message Warning: - This operation is irreversible - All user transactions will be permanently deleted (CASCADE) - User must be logged in with valid token - Email must be verified before account can be deleted
Send 6-digit password reset code to email Args: email(str): User's registered email address Returns: dict: Status of reset code sending
Retrieve all transactions for authenticated user from the database. Fetches all transaction records sorted by date in descending order (newest first). Returns complete details for each transactions including amount, category, date, tags, notes, payment method, status, and frequency. Returns: dict: Transactions list with status and message
Get all transactions within a specified date range. Retrieves transactions between start_date and end_date (inclusive). Useful for viewing transactions for specific time periods. Results are sorted by date in descending order. Args: start_date (str): Start date in YYYY-MM-DD format (required) end_date (str): End date in YYYY-MM-DD format (required) Returns: dict: Transactions list in date range with status and message
Get top transaction categories
Calculate total transactions amount with optional filters. Sums up all transactions amounts based on provided filters. Useful for understanding total transactions in a date range or by category.
Authenticate user and get JWT token. Verifies username and password. Returns JWT token on successful login. Token is valid for 24 hours. Args: username (str): Username (required) password (str): Password (required) Returns: dict: User ID, token, and success status
Register a new user account. Creates a new user with username, email, and password. Password is hashed for security. Returns JWT token on successful registration. Args: username (str): Unique username (required) email (str): Valid email address (required) password (str): Password with minimum 8 chars, uppercase, lowercase, digit (required) Returns: dict: User ID, token, and success status
Reset password using 6-digit code from email Args: code(str): 6-digit code from password reset email new_password (str): New password (min 8 chars, uppercase, lowercase, digit) Returns: dict: Password reset status
Send 6-digit verification code to user's email Args: token(str): JWT Authentication token Returns: dict: Status of email sending
Verify user email with 6-digit code from email. Args: code (str): 6-digit verification code from email Returns: dict: Verification status
Verify JWT token validity. Checks if token is valid and not expired. Args: token (str): JWT token to verify (required) Returns: dict: Token validity and user info
Output schemas not documented for any tool. Tools return nested dict structures like {"result": {"status": "...", "message": "..."}} but the description does not explain this structure, what fields agents should expect, or how to parse results. LLM cannot plan downstream actions without knowing return shape.
Minimal error guidance. Error responses return {"result": {"status": "error", "message": "..."}} but messages are often terse ('Invalid username or password', 'User not found') with no recovery hint. LLM cannot determine if error is retryable, user-fixable, or fatal, or what to try next.
Parameter descriptions lack specificity. Examples: 'User ID (required)', does not explain format (UUID? Int? String?), role, or how agent obtains it. 'Password meeting security requirements (required)', redundant; description should state the actual requirements inline ('min 8 chars, uppercase, lowercase, digit'). Vague descriptions force LLMs to guess.
Enum-like parameters not declared as enums. 'status' in add_transaction, 'frequency', and 'payment_method' list allowed values in descriptions ('daily, weekly, monthly, none, yearly') but are typed as free-form strings. This invites hallucinated values. These should be JSON Schema enums.
No idempotency guarantees documented. add_transaction and bulk_add_transactions create records with timestamps and generated IDs. If an agent retries on transient failure, will duplicate transactions be created? Idempotency pattern not declared, risking double-charges and data inconsistency.
Destructive tool (delete_account) has no confirmation or dry-run capability. The operation is irreversible ('This action cannot be undone', 'All user transactions will be permanently deleted (CASCADE)') but tool is directly executable. Agents can delete accounts without explicit user confirmation, risking catastrophic errors.
Composition issue: bulk_add_transactions differs from add_transaction only in accepting an array of transactions vs a single one. This violates DRY; semantically, they should be the same tool with a parameter for batch size or a unified array-accepting interface. Current design forces LLM to reason about when to use which.
Parameter 'user_id' appears as optional in several read-only tools (get_all_transactions, get_selected_transactions, get_total_transactions) but is never explained. How does the tool work without user context? Does it infer from token? Return all users' data? Missing dependency explanation.
get_total_transactions and get_top_transaction_categories have trivial descriptions ('Calculate total transactions amount with optional filters', 'Get top transaction categories'). Descriptions under 50 chars lack context for when to use them vs alternatives. No explanation of what 'top' means (by frequency? by total amount?) or how categories are ranked.