Skip to content

feat: modernize MCP server and align docs/tests - #3

Merged
klauern merged 6 commits into
mainfrom
codex/full-mcp-upgrade-2026
May 4, 2026
Merged

feat: modernize MCP server and align docs/tests#3
klauern merged 6 commits into
mainfrom
codex/full-mcp-upgrade-2026

Conversation

@klauern

@klauern klauern commented Feb 24, 2026

Copy link
Copy Markdown
Owner

Summary

Modernize the MCP YNAB server to align with newer MCP SDK/spec expectations, fix tool registration and runtime configuration bugs, refresh tests, and update repository conventions/docs.

Why

  • Current lockfile/dependency policy was behind modern MCP SDK usage.
  • categorize_transaction was not exposed as an MCP tool while a private helper was.
  • API key/config resolution happened at import-time, which caused stale config behavior and brittle tests.
  • README and tests had significant drift from actual server behavior.

Changes

MCP server and protocol alignment

  • Expose categorize_transaction as an MCP tool.
  • Remove accidental MCP exposure of _find_transaction_by_id helper.
  • Add MCP tool annotations for read-only/idempotent/destructive hints.
  • Move YNAB_API_KEY lookup to runtime in _get_client.
  • Replace import-time config path constants with runtime config resolution.
  • Add YNABResources(config_dir=...) for deterministic config/test behavior.
  • Use explicit UTF-8 when reading/writing JSON cache and preference files.

Dependencies and modernization

  • Update dependency policy to mcp[cli]>=1.20.0,<2.0.0.
  • Raise pydantic floor to >=2.10.0.
  • Remove unused httpx, xdg, and black dependency entries.
  • Refresh uv.lock (now resolves mcp 1.26.0 locally).
  • Configure pytest async loop scope explicitly to remove deprecation warnings.

Tests and docs

  • Replace placeholder-heavy tests/test_server.py with behavior-focused tests.
  • Add tests for tool exposure, annotations, runtime env handling, and configurable resource storage.
  • Rewrite README to match actual MCP resources/tools and current developer commands.

Repository conventions

  • Add AGENTS/memory-beads repository convention files and symlink CLAUDE.md -> AGENTS.md.

Scope

This PR intentionally combines MCP upgrade work plus repository convention files already present in the working tree, per request to incorporate all current files in a new branch.

Breaking changes

  • Dependency policy tightened for MCP and Pydantic minimum versions.
  • Removed unused dependency declarations (httpx, xdg, black) from project config.
  • No user-facing MCP tool names were removed other than the unintended private helper exposure.

Rollout plan

  1. Merge PR.
  2. Re-sync local env with uv sync.
  3. Restart MCP client integrations using this server.
  4. Monitor first-run behavior for cache/config path resolution under $XDG_CONFIG_HOME or ~/.config.

Testing

Run locally:

uv run ruff check src tests
uv run pytest -q

Observed on this branch:

  • ruff check: pass
  • pytest -q: 10 passed, 1 deselected

Issues

Repository issues are disabled for klauern/mcp-ynab, so no Fixes #... links are available.

Screenshots / Logs

  • UI changes: none
  • Logs: included in testing results above

Summary by CodeRabbit

  • New Features

    • Per-instance persistent preferences/cache and a transaction categorization tool.
  • Documentation

    • Many new and expanded docs: agent workflows, memory bank and context templates, project brief, README updates, and usage guides.
  • Chores

    • Added local version marker, default configuration, metadata for Dolt-backed setup, enhanced ignore rules and merge-driver hint, and dependency/test config updates.
  • Tests

    • Refactored tests to focus on formatting, tool metadata/discovery, and config-driven behavior.

@coderabbitai

coderabbitai Bot commented Feb 24, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0c91fca-5dd7-4327-a481-0601d8400a3f

📥 Commits

Reviewing files that changed from the base of the PR and between 34e81ae and 4881180.

📒 Files selected for processing (7)
  • .beads/.gitignore
  • .beads/.local_version
  • .beads/interactions.jsonl
  • .beads/issues.jsonl
  • .beads/metadata.json
  • .gitignore
  • AGENTS.md

📝 Walkthrough

Walkthrough

Adds repository-local Beads issue tracking and Memory Bank docs; introduces per-instance config directory and UTF‑8 JSON persistence in the MCP YNAB server, explicit tool annotations, a new mutating categorize_transaction tool, dependency/test config updates, and refactored unit tests.

Changes

Beads config & metadata

Layer / File(s) Summary
Data / Ignore rules
.beads/.gitignore, .gitignore
Adds Dolt/Beads ignore rules for runtime artifacts, DB files, locks, credential keys, and backups.
Metadata / Version
.beads/.local_version, .beads/metadata.json
Adds local version 1.0.3 and switches Beads metadata to Dolt-backed configuration with project_id.
Defaults / Config
.beads/config.yaml
Adds default bd CLI settings (issue-prefix, no-db toggle, daemon/auto-flush/import flags, json output, actor, db path, sync-branch, multi-repo keys, integration namespace notes).
Docs / Integration
.beads/README.md, .beads/issues.jsonl
Adds README for Beads usage and two new issues (docstring coverage, duplicate AGENTS.md section).

Repository wiring

Layer / File(s) Summary
Merge driver config
.gitattributes
Registers .beads/issues.jsonl to use the beads merge driver.

Cursor / Memory Bank rules

Layer / File(s) Summary
Decision rules
.cursor/rules/memory-bank.mdc, .cursor/rules/project-setup.mdc
Adds memory-bank rule file and updates a documentation link (CLAUDE.md → AGENTS.md).

Memory Bank documentation

Layer / File(s) Summary
Templates & Guidance
memory_bank_instructions.md, projectbrief.md, productContext.md, activeContext.md, progress.md, systemPatterns.md, techContext.md
Adds a suite of Memory Bank templates, workflows, and context/system/tech documentation files for project state and patterns.

Project docs & agents

Layer / File(s) Summary
Main docs
README.md
Reworks README structure: requirements, installation, run/dev instructions, MCP resources/tools, and adds Beads merge-driver setup.
Agent guidance
AGENTS.md, CLAUDE.md
Adds AGENTS.md with Claude/MCP guidance and a placeholder CLAUDE.md entry (literal "AGENTS.md").

Build / Test config

Layer / File(s) Summary
Dependency / Tooling changes
pyproject.toml
Updates dependency ranges (mcp, pydantic), removes xdg and black config/dev dependency, and adds pytest-asyncio strict options.

Server implementation (YNAB MCP server)

Layer / File(s) Summary
Config resolution / Persistence shape
src/mcp_ynab/server.py
Adds _resolve_config_dir(config_dir) and per-instance config_dir support; introduces per-instance file paths for preferred_budget_id and budget_category_cache persisted as UTF‑8 JSON.
Tool annotation types
src/mcp_ynab/server.py
Adds READ_ONLY_TOOL, MUTATING_TOOL, and IDEMPOTENT_MUTATING_TOOL constants and applies explicit annotations to many @mcp.tool decorators.
New mutating tool + helpers
src/mcp_ynab/server.py
Adds categorize_transaction(budget_id, transaction_id, category_id, id_type="id") (mutating), _find_transaction_by_id, and IO/error handling for JSON decode errors.
Per-instance API exposure
src/mcp_ynab/server.py
YNABResources constructor accepts optional config_dir; get/set and cache helpers use per-instance paths and UTF‑8 IO.

Tests

Layer / File(s) Summary
Unit tests & refactor
tests/test_server.py
Refactors tests to focused unit tests: formatting, markdown table building, tool discovery/annotations, env-driven client errors, transaction lookup, and per-instance config persistence; reduces fixture-heavy integration scaffolding.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client as Client/API
    participant Server as MCP YNAB Server
    participant FS as Filesystem (config dir)
    participant YNAB as YNAB SDK/API

    Client->>Server: categorize_transaction(budget_id, tx_id, category_id, id_type)
    Server->>FS: _resolve_config_dir(config_dir) -> read preferred/cache (UTF-8 JSON)
    FS-->>Server: preferred_budget_id / cached_categories
    Server->>YNAB: _get_client() -> fetch transactions
    YNAB-->>Server: transactions list
    Server->>Server: _find_transaction_by_id(...) -> modify transaction object
    Server->>YNAB: update transaction via SDK/API
    YNAB-->>Server: update confirmation
    Server->>FS: persist preferred_budget_id / cache (UTF-8 JSON)
    FS-->>Server: write ack
    Server-->>Client: operation result (id/status)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Poem

🐰 Hoppity hop, configs find their nest,

Per-instance files settle, safely pressed.
Beads cradle issues, memory whispers clear,
Tools wear labels—mutate or merely peer.
Tests nod approval; docs applaud the cheer.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: modernize MCP server and align docs/tests' accurately reflects the main changes: MCP server modernization, documentation updates, and test refactoring across the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/full-mcp-upgrade-2026

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/mcp_ynab/server.py (2)

602-605: ⚠️ Potential issue | 🟠 Major

Avoid full transaction scans for default ID lookups.

Line 602 currently pulls a transaction list and then scans it in Python. For the common id path, this scales poorly and can noticeably slow tool calls on larger budgets.

⚡ Suggested change
-        response = transactions_api.get_transactions(budget_id, since_date=since_date)
-        target_transaction = _find_transaction_by_id(
-            response.data.transactions, transaction_id, id_type
-        )
+        if id_type == "id":
+            try:
+                single = transactions_api.get_transaction_by_id(budget_id, transaction_id)
+                target_transaction = single.data.transaction
+            except Exception:
+                target_transaction = None
+        else:
+            response = transactions_api.get_transactions(budget_id, since_date=since_date)
+            target_transaction = _find_transaction_by_id(
+                response.data.transactions, transaction_id, id_type
+            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp_ynab/server.py` around lines 602 - 605, The code currently calls
transactions_api.get_transactions and then scans response.data.transactions with
_find_transaction_by_id which is inefficient for the common id lookup; change
the logic so when id_type == "id" you directly fetch the single transaction from
the API (e.g., use a transactions_api method that returns a transaction by ID or
a get_transaction/get_transaction_by_id-style call with budget_id and
transaction_id) and set target_transaction from that response, and only fall
back to transactions_api.get_transactions + _find_transaction_by_id for non-"id"
id_type values; update references to transactions_api.get_transactions,
_find_transaction_by_id, target_transaction, transaction_id, and id_type
accordingly.

608-613: ⚠️ Potential issue | 🟠 Major

Preserve transaction state when recategorizing.

The ExistingTransaction created at line 609 omits fields like approved, memo, cleared, and flag_color. Since the YNAB API treats omitted fields as unset (using defaults like "unapproved" for approved), this categorize-only operation unintentionally resets transaction state on each call.

🛠️ Suggested fix
            wrapper = PutTransactionWrapper(
                transaction=ExistingTransaction(
                    account_id=target_transaction.account_id,
+                   var_date=target_transaction.var_date,
                    amount=target_transaction.amount,
+                   payee_id=target_transaction.payee_id,
+                   payee_name=target_transaction.payee_name,
                    category_id=category_id,
+                   memo=target_transaction.memo,
+                   cleared=target_transaction.cleared,
+                   approved=target_transaction.approved,
+                   flag_color=target_transaction.flag_color,
                )
            )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp_ynab/server.py` around lines 608 - 613, When building the
ExistingTransaction inside PutTransactionWrapper (created in
PutTransactionWrapper and using ExistingTransaction with
transaction=ExistingTransaction(...)), preserve the original transaction state
by copying over fields from target_transaction instead of omitting them; include
at minimum approved, memo, cleared, and flag_color (and any other stateful
fields present on target_transaction such as payee_id/payee_name if applicable)
so the categorize-only update does not reset those values—replace the current
minimal ExistingTransaction construction with one that sets account_id, amount,
category_id plus the preserved fields drawn from target_transaction.
🧹 Nitpick comments (8)
projectbrief.md (1)

1-17: Unfilled template — populate before merging.

All sections remain placeholder text. If this is intended as living documentation for the project, filling it in at commit time prevents the repo accumulating stale scaffolding that nobody ever revisits.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projectbrief.md` around lines 1 - 17, The Project Brief is a placeholder
template and must be populated before merging: replace the header "# Project
Brief: [Project Name]" with the real project name and fill the "## Overview",
"## Core Requirements", "## Goals", and "## Project Scope" sections with
concrete, actionable content (specific requirements, measurable goals, and clear
in/out of scope boundaries) so this becomes living documentation rather than
scaffolding; ensure each bullet under "## Core Requirements" and "## Goals" is
specific and measurable and that the "## Project Scope" clearly lists included
and excluded items.
progress.md (1)

1-20: Unfilled template — same note as projectbrief.md.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@progress.md` around lines 1 - 20, This file contains an unfilled template;
replace placeholder headings and bracketed items under "Progress: [Project
Name]", "## What Works", "## What's Left to Build", "## Known Issues and
Limitations", and "## Evolution of Project Decisions" with real project-specific
content: set the actual project name in the top heading, list implemented
features under "What Works" (concrete feature names), enumerate remaining tasks
under "What's Left to Build", detail reproducible known issues under "Known
Issues and Limitations", and write a short narrative under "Evolution of Project
Decisions" explaining major decisions and why they changed; remove the bracketed
placeholders (e.g., "[Feature 1]") and ensure each section contains actionable,
specific items.
.beads/.gitignore (1)

2-6: Minor redundancy: *.db?* already covers the explicit patterns below it.

*.db-wal, *.db-shm, and *.db-journal are all matched by *.db?*, so lines 5–6 and the journal entry are redundant. Leaving them is harmless and arguably self-documenting, but worth noting if you prefer minimal gitignore files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.beads/.gitignore around lines 2 - 6, The .gitignore contains redundant
patterns: the glob "*.db?*" already matches the explicit entries "*.db-journal",
"*.db-wal" and "*.db-shm"; to clean this up either remove the explicit patterns
("*.db-journal", "*.db-wal", "*.db-shm") or delete the broader "*.db?*" and keep
the explicit ones if you prefer self-documenting entries—update the file
accordingly so only one of these matching strategies remains.
techContext.md (1)

1-22: Unfilled template — same note as projectbrief.md.

All sections are placeholder text. Consider populating before merge.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@techContext.md` around lines 1 - 22, The techContext.md is a placeholder
template — replace the stub entries under the headings "Technologies Used",
"Development Setup", "Technical Constraints", "Dependencies", and "Tool Usage
Patterns" with concrete project details: list the actual technologies and
versions used, provide step‑by‑step development environment setup and any
required environment variables, enumerate real technical constraints and
rationale, list production and dev dependencies with versions and where they are
declared, and describe how project tools (build, test, CI/CD, linters) are used
in practice so the file is fully populated before merge.
productContext.md (1)

1-13: Unfilled template — same note as projectbrief.md.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@productContext.md` around lines 1 - 13, The Product Context file currently
contains an unfilled template under the headings "## Problem Statement", "##
User Experience Goals", and "## Success Metrics"; replace the placeholder lines
with concrete content: write a concise Problem Statement describing the user
problem this project solves, populate "## User Experience Goals" with 3–5
measurable UX goals (replace [UX Goal 1], etc.), and list 2–4 specific Success
Metrics under "## Success Metrics" (replace [Metric 1], etc.); ensure the
wording aligns with the corresponding entries in projectbrief.md and use the
exact headings "## Problem Statement", "## User Experience Goals", and "##
Success Metrics" so the document remains discoverable and consistent.
systemPatterns.md (1)

1-20: Replace placeholder content or explicitly mark this as a template.

Line 1 through Line 20 still contain unresolved placeholders. If this is meant to be a live memory-bank file, it currently provides no usable architecture context.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@systemPatterns.md` around lines 1 - 20, The document contains unresolved
placeholders under headings like "System Architecture", "Key Technical
Decisions", "Design Patterns in Use", "Component Relationships", and "Critical
Implementation Paths"; either populate each section with concrete,
project-specific information (a short architecture overview, 3–5 concrete
technical decisions with rationale, listed design patterns in use, how major
components interact, and the critical implementation paths) or explicitly mark
the file as a template by replacing placeholder lines with a clear template
header and TODO markers (e.g., "TEMPLATE: fill in project-specific
architecture") so reviewers know it is not production content; ensure the
"System Architecture" and "Critical Implementation Paths" sections at minimum
contain actionable, non-placeholder descriptions.
activeContext.md (1)

1-23: Populate this file with project-specific context (or rename as a template).

Line 1 through Line 23 are placeholders, so this currently can’t serve as actionable active context for contributors/agents.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@activeContext.md` around lines 1 - 23, activeContext.md currently contains
placeholder headings and cannot serve as actionable context; replace
placeholders under the headings "Current Work Focus", "Recent Changes", "Next
Steps", "Active Decisions and Considerations", "Important Patterns and
Preferences", and "Learnings and Project Insights" with concrete,
project-specific details (e.g., current ticket/feature IDs, short status, recent
PRs/commits, imminent tasks, decision rationale and owners, coding/architecture
patterns, and key learnings) or, if you intend it to remain a template, rename
the file to indicate "template" and add a usage note at the top explaining how
and when to populate it and who is responsible for updates so contributors can
rely on it.
pyproject.toml (1)

9-9: Add an upper bound for pydantic to future-proof against major-version changes.

While Pydantic 3.0 has not yet been released, adding a constraint like <3 is a best practice that prevents unexpected breaking changes when the next major version becomes available. This is consistent with the principle of explicit version bounds for external dependencies.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pyproject.toml` at line 9, Update the pydantic dependency declaration in
pyproject.toml to add an upper bound to prevent accidental upgrades to the next
major version; locate the existing "pydantic>=2.10.0" entry and change it to
include a <3 upper bound (e.g., "pydantic>=2.10.0,<3") so the project stays
pinned to Pydantic 2.x until you've tested compatibility with 3.x.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.beads/README.md:
- Around line 63-65: Replace the unsafe single-line "curl | bash" install
instruction in .beads/README.md with a pinned, inspect-before-exec workflow:
reference the script URL at a specific commit (pin the raw file URL to a commit
hash), instruct users to download the script (e.g., save as install.sh), verify
its integrity with a provided checksum (SHA256) or GPG signature, inspect the
file before running, and only then run it with "bash install.sh"; update the
README snippet and include the checksum and the pinned URL so users can validate
and review the script prior to execution.

In @.cursor/rules/memory-bank.mdc:
- Around line 1-5: The rule file currently contains only YAML front matter with
alwaysApply: true and no rule body, which forces this empty rule into every AI
call; either add the intended rule content below the closing --- (providing the
rule text/conditions and actions) or change the front-matter key alwaysApply to
false until the body is implemented so the empty rule is not injected globally;
edit the .cursor/rules/memory-bank.mdc file and update the front matter or
append the rule body accordingly.

In @.gitattributes:
- Line 3: The .gitattributes entry declaring merge=beads for .beads/issues.jsonl
requires a corresponding git merge driver registration; add onboarding docs
(README or AGENTS.md) that instruct contributors to register the driver
(register merge.beads.driver and set its name) and show the exact git config
command to run locally, and mention the purpose (Beads JSONL merge driver) so
the .gitattributes merge=beads entry is effective.

In `@memory_bank_instructions.md`:
- Around line 10-20: The Mermaid diagrams (starting with the "flowchart TD"
blocks such as the one containing nodes PB, PC, SP, TC, AC, and P) must be
wrapped in fenced code blocks using ```mermaid before the diagram and ``` after
it so Markdown renderers will display diagrams correctly; locate each bare
"flowchart TD" block (and the similar blocks later in the file) and surround the
entire diagram text with the fenced mermaid code block markers.

---

Outside diff comments:
In `@src/mcp_ynab/server.py`:
- Around line 602-605: The code currently calls
transactions_api.get_transactions and then scans response.data.transactions with
_find_transaction_by_id which is inefficient for the common id lookup; change
the logic so when id_type == "id" you directly fetch the single transaction from
the API (e.g., use a transactions_api method that returns a transaction by ID or
a get_transaction/get_transaction_by_id-style call with budget_id and
transaction_id) and set target_transaction from that response, and only fall
back to transactions_api.get_transactions + _find_transaction_by_id for non-"id"
id_type values; update references to transactions_api.get_transactions,
_find_transaction_by_id, target_transaction, transaction_id, and id_type
accordingly.
- Around line 608-613: When building the ExistingTransaction inside
PutTransactionWrapper (created in PutTransactionWrapper and using
ExistingTransaction with transaction=ExistingTransaction(...)), preserve the
original transaction state by copying over fields from target_transaction
instead of omitting them; include at minimum approved, memo, cleared, and
flag_color (and any other stateful fields present on target_transaction such as
payee_id/payee_name if applicable) so the categorize-only update does not reset
those values—replace the current minimal ExistingTransaction construction with
one that sets account_id, amount, category_id plus the preserved fields drawn
from target_transaction.

---

Nitpick comments:
In @.beads/.gitignore:
- Around line 2-6: The .gitignore contains redundant patterns: the glob "*.db?*"
already matches the explicit entries "*.db-journal", "*.db-wal" and "*.db-shm";
to clean this up either remove the explicit patterns ("*.db-journal",
"*.db-wal", "*.db-shm") or delete the broader "*.db?*" and keep the explicit
ones if you prefer self-documenting entries—update the file accordingly so only
one of these matching strategies remains.

In `@activeContext.md`:
- Around line 1-23: activeContext.md currently contains placeholder headings and
cannot serve as actionable context; replace placeholders under the headings
"Current Work Focus", "Recent Changes", "Next Steps", "Active Decisions and
Considerations", "Important Patterns and Preferences", and "Learnings and
Project Insights" with concrete, project-specific details (e.g., current
ticket/feature IDs, short status, recent PRs/commits, imminent tasks, decision
rationale and owners, coding/architecture patterns, and key learnings) or, if
you intend it to remain a template, rename the file to indicate "template" and
add a usage note at the top explaining how and when to populate it and who is
responsible for updates so contributors can rely on it.

In `@productContext.md`:
- Around line 1-13: The Product Context file currently contains an unfilled
template under the headings "## Problem Statement", "## User Experience Goals",
and "## Success Metrics"; replace the placeholder lines with concrete content:
write a concise Problem Statement describing the user problem this project
solves, populate "## User Experience Goals" with 3–5 measurable UX goals
(replace [UX Goal 1], etc.), and list 2–4 specific Success Metrics under "##
Success Metrics" (replace [Metric 1], etc.); ensure the wording aligns with the
corresponding entries in projectbrief.md and use the exact headings "## Problem
Statement", "## User Experience Goals", and "## Success Metrics" so the document
remains discoverable and consistent.

In `@progress.md`:
- Around line 1-20: This file contains an unfilled template; replace placeholder
headings and bracketed items under "Progress: [Project Name]", "## What Works",
"## What's Left to Build", "## Known Issues and Limitations", and "## Evolution
of Project Decisions" with real project-specific content: set the actual project
name in the top heading, list implemented features under "What Works" (concrete
feature names), enumerate remaining tasks under "What's Left to Build", detail
reproducible known issues under "Known Issues and Limitations", and write a
short narrative under "Evolution of Project Decisions" explaining major
decisions and why they changed; remove the bracketed placeholders (e.g.,
"[Feature 1]") and ensure each section contains actionable, specific items.

In `@projectbrief.md`:
- Around line 1-17: The Project Brief is a placeholder template and must be
populated before merging: replace the header "# Project Brief: [Project Name]"
with the real project name and fill the "## Overview", "## Core Requirements",
"## Goals", and "## Project Scope" sections with concrete, actionable content
(specific requirements, measurable goals, and clear in/out of scope boundaries)
so this becomes living documentation rather than scaffolding; ensure each bullet
under "## Core Requirements" and "## Goals" is specific and measurable and that
the "## Project Scope" clearly lists included and excluded items.

In `@pyproject.toml`:
- Line 9: Update the pydantic dependency declaration in pyproject.toml to add an
upper bound to prevent accidental upgrades to the next major version; locate the
existing "pydantic>=2.10.0" entry and change it to include a <3 upper bound
(e.g., "pydantic>=2.10.0,<3") so the project stays pinned to Pydantic 2.x until
you've tested compatibility with 3.x.

In `@systemPatterns.md`:
- Around line 1-20: The document contains unresolved placeholders under headings
like "System Architecture", "Key Technical Decisions", "Design Patterns in Use",
"Component Relationships", and "Critical Implementation Paths"; either populate
each section with concrete, project-specific information (a short architecture
overview, 3–5 concrete technical decisions with rationale, listed design
patterns in use, how major components interact, and the critical implementation
paths) or explicitly mark the file as a template by replacing placeholder lines
with a clear template header and TODO markers (e.g., "TEMPLATE: fill in
project-specific architecture") so reviewers know it is not production content;
ensure the "System Architecture" and "Critical Implementation Paths" sections at
minimum contain actionable, non-placeholder descriptions.

In `@techContext.md`:
- Around line 1-22: The techContext.md is a placeholder template — replace the
stub entries under the headings "Technologies Used", "Development Setup",
"Technical Constraints", "Dependencies", and "Tool Usage Patterns" with concrete
project details: list the actual technologies and versions used, provide
step‑by‑step development environment setup and any required environment
variables, enumerate real technical constraints and rationale, list production
and dev dependencies with versions and where they are declared, and describe how
project tools (build, test, CI/CD, linters) are used in practice so the file is
fully populated before merge.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bc224a4 and 5cc052c.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (23)
  • .beads/.gitignore
  • .beads/.local_version
  • .beads/README.md
  • .beads/config.yaml
  • .beads/issues.jsonl
  • .beads/metadata.json
  • .cursor/rules/memory-bank.mdc
  • .cursor/rules/project-setup.mdc
  • .gitattributes
  • AGENTS.md
  • CLAUDE.md
  • CLAUDE.md
  • README.md
  • activeContext.md
  • memory_bank_instructions.md
  • productContext.md
  • progress.md
  • projectbrief.md
  • pyproject.toml
  • src/mcp_ynab/server.py
  • systemPatterns.md
  • techContext.md
  • tests/test_server.py

Comment thread .beads/README.md Outdated
Comment thread .cursor/rules/memory-bank.mdc
Comment thread .gitattributes
Comment thread memory_bank_instructions.md
@klauern

klauern commented Feb 24, 2026

Copy link
Copy Markdown
Owner Author

Addressed all review items in commit 43bfda2.

Resolved items:

  1. .beads README install flow now uses pinned commit URL + SHA256 verification + inspect-before-run.
  2. .cursor/rules/memory-bank.mdc now has rule body and .
  3. Added Beads merge-driver onboarding commands in README ( + ).
  4. Wrapped all Mermaid diagrams in with fenced blocks.
  5. now uses when (no full scan in common path).
  6. now preserves transaction state fields (, payee fields, memo, cleared, approved, flag_color, subtransactions) while recategorizing.
  7. Removed redundant explicit db patterns from .
  8. Added upper bound: .
    9-14) Populated all previously placeholder memory/context docs (, , , , , ) with concrete project content.

Verification run locally:

  • All checks passed! -> pass
  • ============================= test session starts ==============================
    platform darwin -- Python 3.12.8, pytest-8.3.4, pluggy-1.5.0
    rootdir: /Users/klauer/dev/python/mcp-ynab
    configfile: pyproject.toml
    testpaths: tests
    plugins: cov-6.0.0, asyncio-0.25.3, anyio-4.8.0
    asyncio: mode=Mode.STRICT, asyncio_default_fixture_loop_scope=function
    collected 13 items / 1 deselected / 12 selected

tests/test_environment.py .. [ 16%]
tests/test_server.py .......... [100%]

======================= 12 passed, 1 deselected in 0.19s ======================= -> 12 passed, 1 deselected

@klauern

klauern commented Feb 24, 2026

Copy link
Copy Markdown
Owner Author

Addressed all review items in commit 43bfda2.

Resolved items:

  1. .beads/README.md install flow now uses pinned commit URL + SHA256 verification + inspect-before-run.
  2. .cursor/rules/memory-bank.mdc now has rule body and alwaysApply: false.
  3. Added Beads merge-driver onboarding commands in README.md (merge.beads.name + merge.beads.driver).
  4. Wrapped all Mermaid diagrams in memory_bank_instructions.md with fenced mermaid blocks.
  5. categorize_transaction now uses get_transaction_by_id when id_type == "id" (no full scan in the common path).
  6. categorize_transaction now preserves transaction state fields (var_date, payee fields, memo, cleared, approved, flag_color, subtransactions) while recategorizing.
  7. Removed redundant explicit db patterns from .beads/.gitignore.
  8. Added pydantic upper bound: >=2.10.0,<3.
  9. Populated projectbrief.md with concrete project scope/requirements.
  10. Populated progress.md with current status/work remaining.
  11. Populated techContext.md with concrete tooling/dependency context.
  12. Populated productContext.md with concrete problem/goals/metrics.
  13. Populated systemPatterns.md with architecture/decision patterns.
  14. Populated activeContext.md with current focus and next steps.

Verification run locally:

  • uv run ruff check src tests -> pass
  • uv run pytest -q -> 12 passed, 1 deselected

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
memory_bank_instructions.md (1)

81-87: ⚠️ Potential issue | 🟡 Minor

Wrap the Act Mode Mermaid block in a fenced code block.

Line 82 starts a Mermaid flowchart without a ```mermaid fence, so it may render as plain text instead of a diagram.

✏️ Proposed fix
 ### Act Mode
+```mermaid
 flowchart TD
     Start[Start] --> Context[Check Memory Bank]
     Context --> Update[Update Documentation]
     Update --> Execute[Execute Task]
     Execute --> Document[Document Changes]
+```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@memory_bank_instructions.md` around lines 81 - 87, The "Act Mode" Mermaid
flowchart block (the flowchart TD containing nodes Start, Context, Update,
Execute, Document) is not wrapped in a fenced code block so it may render as
plain text; wrap the entire Mermaid block with a starting ```mermaid fence
before "flowchart TD" and a closing ``` fence after the last line to ensure it
renders as a diagram.
🧹 Nitpick comments (1)
src/mcp_ynab/server.py (1)

703-711: Use explicit mutating annotations for idempotent write tools.

At Line 703 and Line 710, these tools mutate local state but only set idempotentHint=True. Consider explicitly setting readOnlyHint=False (and destructiveHint=False) for clearer MCP client behavior.

♻️ Proposed refactor
 READ_ONLY_TOOL = types.ToolAnnotations(readOnlyHint=True, idempotentHint=True)
 MUTATING_TOOL = types.ToolAnnotations(readOnlyHint=False, destructiveHint=True)
+IDEMPOTENT_MUTATING_TOOL = types.ToolAnnotations(
+    readOnlyHint=False, idempotentHint=True, destructiveHint=False
+)
...
-@mcp.tool(annotations=types.ToolAnnotations(idempotentHint=True))
+@mcp.tool(annotations=IDEMPOTENT_MUTATING_TOOL)
 async def set_preferred_budget_id(budget_id: str) -> str:
...
-@mcp.tool(annotations=types.ToolAnnotations(idempotentHint=True))
+@mcp.tool(annotations=IDEMPOTENT_MUTATING_TOOL)
 async def cache_categories(budget_id: str) -> str:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp_ynab/server.py` around lines 703 - 711, The MCP tool decorators on
set_preferred_budget_id and cache_categories currently only set
idempotentHint=True but these functions mutate local state; update their
annotations to explicitly mark readOnlyHint=False and destructiveHint=False in
the types.ToolAnnotations passed to `@mcp.tool` (keep idempotentHint=True) so the
MCP client understands these are idempotent writes rather than reads; modify the
decorators on the functions named set_preferred_budget_id and cache_categories
accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/mcp_ynab/server.py`:
- Around line 210-217: The _load_json_file function currently only catches
FileNotFoundError, so a malformed JSON will raise json.JSONDecodeError and break
startup; update _load_json_file to also catch json.JSONDecodeError, log a
warning via the module logger (or create one) including the filename and error
details, and return an empty dict {} as a safe fallback so corrupted cache files
do not crash resource loading.
- Around line 604-610: The broad except around
transactions_api.get_transaction_by_id is hiding real API failures; replace the
bare except by catching ynab.exceptions.ApiException (or NotFoundException) and
only treat 404 as "not found" by checking the exception's status (e.g., e.status
if using ApiException) before assigning target_transaction = None; re-raise the
exception for non-404 status codes so auth/network/server errors propagate.
Ensure the try/except references transactions_api.get_transaction_by_id and
target_transaction so the behavior is limited to the missing-transaction case.

---

Duplicate comments:
In `@memory_bank_instructions.md`:
- Around line 81-87: The "Act Mode" Mermaid flowchart block (the flowchart TD
containing nodes Start, Context, Update, Execute, Document) is not wrapped in a
fenced code block so it may render as plain text; wrap the entire Mermaid block
with a starting ```mermaid fence before "flowchart TD" and a closing ``` fence
after the last line to ensure it renders as a diagram.

---

Nitpick comments:
In `@src/mcp_ynab/server.py`:
- Around line 703-711: The MCP tool decorators on set_preferred_budget_id and
cache_categories currently only set idempotentHint=True but these functions
mutate local state; update their annotations to explicitly mark
readOnlyHint=False and destructiveHint=False in the types.ToolAnnotations passed
to `@mcp.tool` (keep idempotentHint=True) so the MCP client understands these are
idempotent writes rather than reads; modify the decorators on the functions
named set_preferred_budget_id and cache_categories accordingly.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc052c and 43bfda2.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (14)
  • .beads/.gitignore
  • .beads/README.md
  • .cursor/rules/memory-bank.mdc
  • README.md
  • activeContext.md
  • memory_bank_instructions.md
  • productContext.md
  • progress.md
  • projectbrief.md
  • pyproject.toml
  • src/mcp_ynab/server.py
  • systemPatterns.md
  • techContext.md
  • tests/test_server.py
✅ Files skipped from review due to trivial changes (1)
  • systemPatterns.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • .beads/.gitignore
  • README.md
  • pyproject.toml
  • projectbrief.md
  • .cursor/rules/memory-bank.mdc
  • .beads/README.md

Comment thread src/mcp_ynab/server.py
Comment thread src/mcp_ynab/server.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/mcp_ynab/server.py (2)

613-622: 404-specific exception handling now correctly scoped.

The previous broad except Exception has been replaced with a targeted except ApiException as exc that inspects exc.status and only swallows 404s, re-raising everything else. This aligns with the official YNAB SDK pattern of from ynab.rest import ApiException and correct status-code-based branching.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp_ynab/server.py` around lines 613 - 622, Replace the broad exception
catching with a targeted ApiException handler around the call to
transactions_api.get_transaction_by_id so that you only swallow 404 responses:
catch ApiException as exc, set target_transaction = None when exc.status == 404,
and re-raise the exception for any other exc.status; ensure this change is
applied in the try/except block that assigns target_transaction from
single_response.data.transaction so other errors are not silently ignored.

213-228: UTF-8 encoding and JSONDecodeError fallback correctly applied.

Both _load_json_file and _save_json_file now explicitly specify encoding="utf-8", and _load_json_file catches json.JSONDecodeError with a warning log and a safe {} fallback. The test_load_json_file_handles_invalid_json test in the test file validates this path end-to-end.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp_ynab/server.py` around lines 213 - 228, _save_json_file currently
writes directly and can fail silently on missing directories or
write/serialization errors; update _save_json_file to ensure the target
directory exists (Path(filename).parent.mkdir(parents=True, exist_ok=True)) and
wrap the open/json.dump in a try/except that catches OSError and TypeError (or
json.JSONEncodeError if available) and logs an error via logger.error with the
filename and exception details, re-raising or returning as appropriate for your
error semantics; reference the function name _save_json_file to locate the
change and mirror the logging style used in _load_json_file.
🧹 Nitpick comments (5)
src/mcp_ynab/server.py (2)

233-237: MUTATING_TOOL (destructiveHint=True) may be too strong for categorize_transaction.

destructiveHint and idempotentHint are only meaningful when readOnlyHint is false, and destructiveHint flags operations that can irreversibly destroy or overwrite data. Re-categorizing a transaction is a reversible update: running it twice with the same category_id produces no additional state change. IDEMPOTENT_MUTATING_TOOL (readOnlyHint=False, idempotentHint=True, destructiveHint=False) would be a more accurate annotation and would prevent MCP clients from showing unnecessary destructive-action confirmation prompts.

♻️ Proposed fix
-@mcp.tool(annotations=MUTATING_TOOL)
+@mcp.tool(annotations=IDEMPOTENT_MUTATING_TOOL)
 async def categorize_transaction(

Also applies to: 581-581

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp_ynab/server.py` around lines 233 - 237, The MUTATING_TOOL annotation
is too strong for reversible actions like categorize_transaction; update usages
so that categorize_transaction (and the other occurrence currently using
MUTATING_TOOL) use IDEMPOTENT_MUTATING_TOOL instead (i.e., replace references to
MUTATING_TOOL with IDEMPOTENT_MUTATING_TOOL for the categorize_transaction
handler/function and the second location noted) so the tool is marked
readOnlyHint=false, idempotentHint=true, destructiveHint=false.

44-54: Module-level YNABResources() creates ~/.config/mcp-ynab on every import.

_resolve_config_dir calls config_dir.mkdir(parents=True, exist_ok=True) unconditionally. Because ynab_resources = YNABResources() is at module level (Line 294), importing mcp_ynab.server in any context — including the test suite running in CI — creates the directory under $HOME/.config. Tests are properly isolated via tmp_path, but the side effect still pollutes the developer/CI home directory.

Consider deferring directory creation to first write (e.g., inside set_preferred_budget_id / cache_categories) rather than on init.

Also applies to: 294-294

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/mcp_ynab/server.py` around lines 44 - 54, Module import currently creates
~/.config/mcp-ynab because _resolve_config_dir() always calls
config_dir.mkdir(...) and YNABResources() is instantiated at module level as
ynab_resources; change to defer directory creation: remove the unconditional
mkdir from _resolve_config_dir (or stop calling _resolve_config_dir during
module import) and instead ensure the directory is created lazily inside
write-path methods such as YNABResources.set_preferred_budget_id and
YNABResources.cache_categories (call mkdir(parents=True, exist_ok=True) just
before writing files); also consider removing the module-level instantiation
ynab_resources or replace it with a lazy getter to avoid side effects on import.
tests/test_server.py (3)

253-260: Incomplete field preservation assertions — var_date, payee_id, payee_name, and subtransactions not verified.

The test name and PR description both emphasize that categorize_transaction preserves all existing transaction fields, but only six of the ten fields carried through ExistingTransaction are actually asserted. The payee and date fields (var_date, payee_id, payee_name) and subtransactions are set on DummyTransaction and passed through ExistingTransaction in server.py (lines 633–642) but never checked here.

♻️ Proposed additional assertions
 assert captured["flag_color"] == "blue"
+assert captured["var_date"] == "2026-02-01"
+assert captured["payee_id"] == "payee-1"
+assert captured["payee_name"] == "Coffee Shop"
+assert captured["subtransactions"] == [{"amount": -500, "category_id": "cat-old"}]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_server.py` around lines 253 - 260, The test currently asserts only
six fields after calling server.categorize_transaction but omits var_date,
payee_id, payee_name, and subtransactions which are set on DummyTransaction and
passed through ExistingTransaction; update the test to also assert that
captured["var_date"] matches the DummyTransaction var_date, captured["payee_id"]
matches payee_id, captured["payee_name"] matches payee_name, and
captured["subtransactions"] matches the DummyTransaction.subtransactions so all
fields preserved by categorize_transaction/ExistingTransaction are verified.

216-216: Optional: same RUF012 mutable class attribute as above.

♻️ Proposed fix
-    subtransactions = [{"amount": -500, "category_id": "cat-old"}]
+    subtransactions: ClassVar[list] = [{"amount": -500, "category_id": "cat-old"}]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_server.py` at line 216, The test defines a mutable
class/module-level list named subtransactions which can be mutated across tests
(RUF012); change it to be created fresh where used (e.g., move the [{"amount":
-500, "category_id": "cat-old"}] literal into the test function or into a helper
function like make_subtransactions()) so each test gets a new list instance
instead of sharing the module-level subtransactions variable.

126-126: Optional: annotate mutable class attribute to silence RUF012.

subtransactions = [] on a class body is flagged by Ruff (RUF012). While there's no mutation risk here because DummyTransaction is only ever instantiated once per test, the fix is a one-liner:

♻️ Proposed fix
+from typing import ClassVar
 ...
 class DummyTransaction:
     id = "tx-1"
     ...
-    subtransactions = []
+    subtransactions: ClassVar[list] = []
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_server.py` at line 126, Annotate the mutable class attribute to
silence RUF012: change the class-level declaration subtransactions = [] inside
DummyTransaction to be explicitly typed as a ClassVar, e.g. subtransactions:
ClassVar[list] = [], and add the necessary import for ClassVar from typing if
missing; this keeps the attribute as a shared class container but satisfies the
linter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/mcp_ynab/server.py`:
- Around line 613-622: Replace the broad exception catching with a targeted
ApiException handler around the call to transactions_api.get_transaction_by_id
so that you only swallow 404 responses: catch ApiException as exc, set
target_transaction = None when exc.status == 404, and re-raise the exception for
any other exc.status; ensure this change is applied in the try/except block that
assigns target_transaction from single_response.data.transaction so other errors
are not silently ignored.
- Around line 213-228: _save_json_file currently writes directly and can fail
silently on missing directories or write/serialization errors; update
_save_json_file to ensure the target directory exists
(Path(filename).parent.mkdir(parents=True, exist_ok=True)) and wrap the
open/json.dump in a try/except that catches OSError and TypeError (or
json.JSONEncodeError if available) and logs an error via logger.error with the
filename and exception details, re-raising or returning as appropriate for your
error semantics; reference the function name _save_json_file to locate the
change and mirror the logging style used in _load_json_file.

---

Nitpick comments:
In `@src/mcp_ynab/server.py`:
- Around line 233-237: The MUTATING_TOOL annotation is too strong for reversible
actions like categorize_transaction; update usages so that
categorize_transaction (and the other occurrence currently using MUTATING_TOOL)
use IDEMPOTENT_MUTATING_TOOL instead (i.e., replace references to MUTATING_TOOL
with IDEMPOTENT_MUTATING_TOOL for the categorize_transaction handler/function
and the second location noted) so the tool is marked readOnlyHint=false,
idempotentHint=true, destructiveHint=false.
- Around line 44-54: Module import currently creates ~/.config/mcp-ynab because
_resolve_config_dir() always calls config_dir.mkdir(...) and YNABResources() is
instantiated at module level as ynab_resources; change to defer directory
creation: remove the unconditional mkdir from _resolve_config_dir (or stop
calling _resolve_config_dir during module import) and instead ensure the
directory is created lazily inside write-path methods such as
YNABResources.set_preferred_budget_id and YNABResources.cache_categories (call
mkdir(parents=True, exist_ok=True) just before writing files); also consider
removing the module-level instantiation ynab_resources or replace it with a lazy
getter to avoid side effects on import.

In `@tests/test_server.py`:
- Around line 253-260: The test currently asserts only six fields after calling
server.categorize_transaction but omits var_date, payee_id, payee_name, and
subtransactions which are set on DummyTransaction and passed through
ExistingTransaction; update the test to also assert that captured["var_date"]
matches the DummyTransaction var_date, captured["payee_id"] matches payee_id,
captured["payee_name"] matches payee_name, and captured["subtransactions"]
matches the DummyTransaction.subtransactions so all fields preserved by
categorize_transaction/ExistingTransaction are verified.
- Line 216: The test defines a mutable class/module-level list named
subtransactions which can be mutated across tests (RUF012); change it to be
created fresh where used (e.g., move the [{"amount": -500, "category_id":
"cat-old"}] literal into the test function or into a helper function like
make_subtransactions()) so each test gets a new list instance instead of sharing
the module-level subtransactions variable.
- Line 126: Annotate the mutable class attribute to silence RUF012: change the
class-level declaration subtransactions = [] inside DummyTransaction to be
explicitly typed as a ClassVar, e.g. subtransactions: ClassVar[list] = [], and
add the necessary import for ClassVar from typing if missing; this keeps the
attribute as a shared class container but satisfies the linter.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43bfda2 and 34e81ae.

📒 Files selected for processing (2)
  • src/mcp_ynab/server.py
  • tests/test_server.py

Auto-committed by bd doctor --fix
- mcp-ynab-00h: raise docstring coverage to 80% in server.py
- mcp-ynab-3w6: remove duplicate Session Completion section in AGENTS.md
@klauern
klauern merged commit acb8976 into main May 4, 2026
1 check was pending
@klauern
klauern deleted the codex/full-mcp-upgrade-2026 branch May 4, 2026 03:05
klauern added a commit that referenced this pull request May 4, 2026
- Add module docstrings to mcp_ynab/__init__.py, __main__.py, server.py
- Add docstrings to AsyncYNABClient methods and YNABResources class+__init__
- Wire interrogate into pyproject.toml with fail-under=80 and add it
  to the dev dependency group
- Add 'task docstrings' command

Coverage rose from 81.8% to 100% (was 43.64% when CodeRabbit flagged it
on PR #3).

Closes mcp-ynab-00h.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant