MCP server for managing financial transactions and tags via the Paisa API
paisa-mcp has 9 tools with consistent naming (all verb-based: get_, create_, update_, delete_, search_) and adequate descriptions. All tools have input schemas with type definitions. However, several critical issues reduce the score: (1) jwt_token is exposed as a parameter on every tool, violating secret-injection pattern, credentials must be server-side injected; (2) output schemas are NOT documented, the code lacks explicit documentation of what fields are returned, forcing LLMs to infer structure; (3) error handling is absent, no error guidance, recovery hints, or classification in any tool; (4) parameter descriptions lack format/constraint details required by the rubric; (5) the create_transaction and update_transaction tools accept amount with default=0, which is a dangerous default that could cause silent data loss. The server operates via HTTP (SSE) and is deployable, but lacks production-grade robustness expected for a 70+ score.
Add a new tag with the given label. Args: label (str): The label for the new tag. This field is required. jwt_token (str): Authentication token. Leave empty if not required. Returns: dict: The newly created tag object, containing its ID and label.
Create a new transaction. Args: label (str): The label for the transaction. amount (float): A POSITIVE floating point number for the amount of transaction. type (str): The type of transaction ('income' or 'expense'). tags (list[str], optional): List of tag IDs as strings. date (str, optional): Date of the transaction (ISO format). jwt_token (str): Authentication token. Leave empty if not required. Returns: dict: The newly created transaction object.
Soft delete a tag by its ID. The tag will be marked as deleted but not removed from the database. Args: tagId (str): The unique identifier of the tag to delete. jwt_token (str): Authentication token. Leave empty if not required. Returns: dict: A message indicating the result of the delete operation.
Soft delete a transaction by its ID. Args: transactionId (str): The unique identifier of the transaction to delete. jwt_token (str): Authentication token. Leave empty if not required. Returns: dict: A message indicating the result of the delete operation.
jwt_token exposed as tool parameter on ALL 9 tools. Credentials must be server-side injected via environment variables or vault, never as parameters. Agent traces log all parameters, jwt tokens in params leak into logs and prompt history.
Output schemas are NOT documented. Tools return structured data (dicts, lists) but lack explicit field documentation. LLMs cannot plan downstream tool calls or extract correct data without knowing return structure. The code shows response.json() calls and format_tag() returns, but no docstring documents the output format.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | B | 74 | 2026-07-28+ | v2 |
| 2026-03-09 | F | 45 | - | v1 |
Retrieve all tags from the system. Args: jwt_token (str): Authentication token. Leave empty if not required. Returns: list: A list of tag objects, each containing the tag's ID and label.
Retrieve a single transaction by its ID. Args: transactionId (str): The unique identifier of the transaction. jwt_token (str): Authentication token. Leave empty if not required. Returns: dict: The transaction object if found.
Search transactions by label, tags, date range, select fields, with pagination support. Args: label (str, optional): Search by label. tags (str | None, optional): Comma-separated tag IDs. Use 'None' to get transactions without any tags. startDate (str, optional): Start date (ISO format). endDate (str, optional): End date (ISO format). select (str, optional): Comma-separated fields to include in results. page (int, optional): Page number for pagination (1-based). Defaults to 1. limit (int, optional): Number of results per page. Defaults to 20. type (str, optional): Type of transaction to search (income or expense) jwt_token (str): Authentication token. Leave empty if not required. Returns: list: List of matching transaction objects.
Update the label of an existing tag by its ID. Args: tagId (str): The unique identifier of the tag to update. label (str): The new label for the tag. This field is required. jwt_token (str): Authentication token. Leave empty if not required. Returns: dict: The updated tag object, containing its ID and new label.
Update an existing transaction by its ID. Args: transactionId (str): The unique identifier of the transaction. label (str, optional): New label. tags (list[str], optional): New list of tag IDs. date (str, optional): New date (ISO format). amount (float, optional): POSITIVE floating point number jwt_token (str): Authentication token. Leave empty if not required. Returns: dict: The updated transaction object.
Zero error handling. No error guidance, recovery hints, or error classification in any tool. When an API call fails, the raw response.json() is returned with no actionable guidance. LLMs cannot determine if errors are retryable, user-fixable, or fatal.
create_transaction has amount parameter with default=0, and update_transaction has amount with default=0. Defaults should NOT cause data loss or unintended side effects. A default amount of 0 could silently create zero-value transactions if the LLM omits the parameter.
Parameter descriptions lack format/constraint details. 'date' described as 'ISO format' but no example or pattern provided. 'amount' described as 'POSITIVE floating point number' but no min/max bounds declared. 'tags' described as 'List of tag IDs as strings' but format of tag ID string not specified (UUID, numeric, etc.).
No input validation or error messages. If an LLM passes invalid type for 'type' parameter (e.g. 'Payment' instead of 'income' or 'expense'), or negative amount, there is no clear error message guiding correction.
search_transactions documentation is unclear on behavior of mutually exclusive/dependent parameters. startDate/endDate are optional but no description of what happens if startDate > endDate. tags parameter accepts 'None' string, highly ambiguous, should be an enum or separate boolean parameter.
No pagination guidance in return schema. search_transactions returns a list but does not document whether it returns all matching results, a capped result set, or a paginated response structure. Large result sets risk exhausting context windows.