Client¶
The async HTTP client handles all communication with the YNAB API. Every request flows through YNABClient, which provides authentication, rate limit enforcement, error parsing, response unwrapping, and milliunit-to-dollar conversion.
Key behaviors¶
- Rate limiting: Checks remaining quota before each request and raises
ToolErrorif exhausted. - Authentication: Bearer token injected via the httpx client at construction time.
- Error parsing: YNAB error responses are parsed into
YNABAPIErrorwith structured fields. - Data unwrapping: Automatically extracts the
dataenvelope from YNAB API responses. - Milliunit conversion: Converts milliunit fields (amounts in thousandths) to dollar values in responses.
- Delta caching: Supports server knowledge-based delta requests for supported endpoints.
client
¶
YNAB API client wrapping httpx with auth, rate limiting, and conversion.
Provides YNABClient, the single point of contact with the YNAB API.
All requests go through this client, which handles:
- Rate limit checking before each request
- Bearer token authentication via the injected httpx client
- YNAB error response parsing into
YNABAPIError - Data envelope unwrapping (
response["data"]) - Milliunit-to-dollar conversion on response fields
- Delta request caching for supported endpoints
MILLIUNIT_FIELDS
module-attribute
¶
MILLIUNIT_FIELDS: frozenset[str] = frozenset({
"balance",
"cleared_balance",
"uncleared_balance",
"budgeted",
"activity",
"amount",
"goal_target",
"goal_overall_left",
"goal_under_funded",
"allocation",
"spent",
"income",
"to_be_budgeted",
})
Field names that contain milliunit values in YNAB API responses.
CacheStore
¶
In-memory cache for YNAB delta-capable endpoints.
Stores full response data and server_knowledge values per
budget+endpoint combination. Merges delta responses by
replacing/adding changed entities in the cached list.
Attributes:
-
_entries(dict[str, CacheEntry]) –Internal dict mapping cache keys to CacheEntry instances.
Initialize an empty cache store.
Source code in src/ynaa_mcp/cache.py
get_knowledge
¶
Return stored server_knowledge for a cache key, or None.
Parameters:
-
cache_key(str) –The cache key in
{budget_id}:{resource}format.
Returns:
-
int | None–The server_knowledge integer, or None if not cached.
Source code in src/ynaa_mcp/cache.py
get_cached_data
¶
Return cached response data, or None if not cached.
Parameters:
-
cache_key(str) –The cache key in
{budget_id}:{resource}format.
Returns:
-
dict[str, Any] | None–The cached data dict, or None if not cached.
Source code in src/ynaa_mcp/cache.py
update
¶
Store or replace a cache entry with full response data.
Parameters:
-
cache_key(str) –The cache key in
{budget_id}:{resource}format. -
server_knowledge(int) –The YNAB server knowledge value.
-
data(dict[str, Any]) –The full response data dict.
Source code in src/ynaa_mcp/cache.py
merge_delta
¶
Merge delta entities into existing cached data.
For each top-level key in delta_data that is a list of dicts
with id fields: updates existing entities, adds new ones, and
removes those with deleted=True.
For category_groups (grouped response): merges at group level
first, then merges categories within each group.
If no existing entry exists, stores delta_data as a fresh entry.
Parameters:
-
cache_key(str) –The cache key in
{budget_id}:{resource}format. -
server_knowledge(int) –The new server knowledge value.
-
delta_data(dict[str, Any]) –The delta response data dict.
Source code in src/ynaa_mcp/cache.py
invalidate
¶
Remove a specific cache entry.
Parameters:
-
cache_key(str) –The cache key to remove.
invalidate_budget
¶
Remove all cache entries for a budget.
Parameters:
-
budget_id(str) –The YNAB budget ID whose entries to remove.
Source code in src/ynaa_mcp/cache.py
invalidate_for_mutation
¶
Invalidate cache entries affected by a mutation at the given path.
Extracts the budget ID and resource type from the API path,
invalidates the direct resource, then invalidates any cross-resource
entries defined in :data:CROSS_INVALIDATION_MAP.
Parameters:
-
path(str) –The API path of the mutation (e.g.,
/budgets/b1/transactions/t1).
Source code in src/ynaa_mcp/cache.py
get_ttl
¶
Return cached data for a TTL key, or None if missing/expired.
Expired entries are deleted on access.
Parameters:
-
key(str) –The TTL cache key.
Returns:
-
dict[str, Any] | None–The cached data dict, or None if not cached or expired.
Source code in src/ynaa_mcp/cache.py
set_ttl
¶
Store data with a time-to-live expiration.
Parameters:
-
key(str) –The TTL cache key.
-
data(dict[str, Any]) –The data dict to cache.
-
ttl_seconds(float) –Number of seconds until the entry expires.
Source code in src/ynaa_mcp/cache.py
YNABAPIError
¶
Bases: Exception
Raised when the YNAB API returns an error response.
Attributes:
-
status_code–HTTP status code from the YNAB API.
-
error_id–YNAB-specific error identifier (e.g., "404.2").
-
name–YNAB error name (e.g., "resource_not_found").
-
detail–Human-readable error description from YNAB.
Initialize a YNABAPIError.
Parameters:
-
status_code(int) –HTTP status code from the YNAB API.
-
error_id(str) –YNAB-specific error identifier.
-
name(str) –YNAB error name.
-
detail(str) –Human-readable error description.
Source code in src/ynaa_mcp/errors.py
RateLimiter
¶
Sliding window rate limiter that tracks request timestamps.
Tracks time.monotonic() timestamps in a deque and prunes entries
older than :data:WINDOW_SECONDS. Denies new requests once the
count reaches :data:DENY_THRESHOLD.
Attributes:
-
_timestamps(deque[float]) –Deque of monotonic timestamps for requests in the window.
Initialize the rate limiter with an empty timestamp deque.
Source code in src/ynaa_mcp/rate_limiter.py
check
¶
Check whether a new request is allowed.
Prunes expired timestamps, then checks the count against
:data:DENY_THRESHOLD.
Returns:
-
tuple[bool, int, float | None]–A 3-tuple of: -
allowed: True if a request can proceed. -current_count: Number of requests in the window. -retry_after: Seconds until the oldest request expires from the window (None if allowed).
Source code in src/ynaa_mcp/rate_limiter.py
YNABClient
¶
YNABClient(
http_client: AsyncClient,
rate_limiter: RateLimiter,
*,
cache: CacheStore | None = None,
)
Async client for the YNAB API with rate limiting and conversion.
Accepts an injected httpx.AsyncClient (created during server lifespan)
and a RateLimiter. Does NOT create its own HTTP client.
Attributes:
-
_http–The injected httpx async client.
-
_rate_limiter–The rate limiter instance.
-
_cache–Optional delta cache store for reducing API calls.
Initialize the YNAB client with injected dependencies.
Parameters:
-
http_client(AsyncClient) –An httpx.AsyncClient pre-configured with base URL and authorization headers.
-
rate_limiter(RateLimiter) –A RateLimiter instance for tracking API usage.
-
cache(CacheStore | None, default:None) –Optional CacheStore for delta request caching.
Source code in src/ynaa_mcp/client.py
validate_token
async
¶
Validate the YNAB personal access token by calling GET /user.
Returns:
-
str–The authenticated user's ID string.
Source code in src/ynaa_mcp/client.py
request
async
¶
Send a request to the YNAB API with rate limiting and conversion.
Steps
- Check rate limiter -- raise ToolError if denied
- Send HTTP request
- Record timestamp in rate limiter
- Parse error responses into YNABAPIError
- Unwrap the
dataenvelope - Convert milliunit fields to dollars
Parameters:
-
method(str) –HTTP method (GET, POST, PUT, PATCH, DELETE).
-
path(str) –API path relative to base URL (e.g.,
/user). -
**kwargs(Any, default:{}) –Additional keyword arguments passed to httpx (Any required -- httpx accepts diverse parameter types).
Returns:
-
dict[str, Any]–The unwrapped
datadict from the YNAB response with -
dict[str, Any]–milliunit fields converted to dollar amounts.
Raises:
-
ToolError–If the rate limiter denies the request.
Source code in src/ynaa_mcp/client.py
get
async
¶
get(path: str, **kwargs: JSONValue) -> dict[str, Any]
Send a GET request, using delta cache when available.
For delta-capable endpoints, injects last_knowledge_of_server
when cached knowledge exists, and merges delta responses into the
cache. Strips server_knowledge from returned data.
Parameters:
-
path(str) –API path relative to base URL.
-
**kwargs(JSONValue, default:{}) –Additional keyword arguments passed to httpx.
Returns:
-
dict[str, Any]–The unwrapped and converted response data, with
-
dict[str, Any]–server_knowledgestripped if present.
Source code in src/ynaa_mcp/client.py
post
async
¶
post(path: str, **kwargs: JSONValue) -> dict[str, Any]
Send a POST request to the YNAB API.
Parameters:
-
path(str) –API path relative to base URL.
-
**kwargs(JSONValue, default:{}) –Additional keyword arguments passed to httpx.
Returns:
-
dict[str, Any]–The unwrapped and converted response data.
Source code in src/ynaa_mcp/client.py
put
async
¶
put(path: str, **kwargs: JSONValue) -> dict[str, Any]
Send a PUT request to the YNAB API.
Parameters:
-
path(str) –API path relative to base URL.
-
**kwargs(JSONValue, default:{}) –Additional keyword arguments passed to httpx.
Returns:
-
dict[str, Any]–The unwrapped and converted response data.
Source code in src/ynaa_mcp/client.py
delete
async
¶
delete(path: str, **kwargs: JSONValue) -> dict[str, Any]
Send a DELETE request to the YNAB API.
Parameters:
-
path(str) –API path relative to base URL.
-
**kwargs(JSONValue, default:{}) –Additional keyword arguments passed to httpx.
Returns:
-
dict[str, Any]–The unwrapped and converted response data.
Source code in src/ynaa_mcp/client.py
patch
async
¶
patch(path: str, **kwargs: JSONValue) -> dict[str, Any]
Send a PATCH request to the YNAB API.
Parameters:
-
path(str) –API path relative to base URL.
-
**kwargs(JSONValue, default:{}) –Additional keyword arguments passed to httpx.
Returns:
-
dict[str, Any]–The unwrapped and converted response data.
Source code in src/ynaa_mcp/client.py
cache_key_from_path
¶
Extract a cache key from an API path, or None if not delta-cacheable.
Matches paths like /budgets/{budget_id}/{resource} where resource
is in :data:DELTA_ENDPOINTS.
Parameters:
-
path(str) –The API path (e.g.,
/budgets/abc-123/transactions).
Returns:
-
str | None–A cache key like
abc-123:transactions, or None if the path -
str | None–is not a delta-capable endpoint.
Source code in src/ynaa_mcp/cache.py
strip_server_knowledge
¶
Return a copy of data with the server_knowledge key removed.
Parameters:
-
data(dict[str, Any]) –The response data dict that may contain
server_knowledge.
Returns:
-
dict[str, Any]–A shallow copy of
datawithout theserver_knowledgekey.
Source code in src/ynaa_mcp/cache.py
milliunits_to_dollars
¶
Convert YNAB milliunits to a dollar amount.
Parameters:
-
milliunits(int) –Amount in YNAB milliunits (1000 milliunits = $1.00).
Returns:
-
float–The equivalent dollar amount as a float.
Examples:
Source code in src/ynaa_mcp/converters.py
_is_milliunit_field
¶
Check whether a response field name contains milliunit values.
Matches exact field names in :data:MILLIUNIT_FIELDS plus any key
ending with _balance or _amount for forward compatibility.
Parameters:
-
key(str) –The field name to check.
Returns:
-
bool–True if the field contains milliunit values.