Makes the MCP server runnable as a network service and hardens its tool
surface. Previously it was stdio-only and had never been executed — the
test file had never run, and 3 of its 5 tests failed on first invocation.
Transport and deployment
FIN_MCP_TRANSPORT selects stdio (default, unchanged) or streamable-http,
which binds FIN_MCP_HOST:FIN_MCP_PORT and serves MCP at FIN_MCP_PATH
(default /mcp) with stateless_http so no session affinity is needed.
Ships as its own image (cli/Dockerfile) and its own Deployment +
ClusterIP Service (deploy/k8s/fin-mcp.yaml) rather than being mounted
into the API app — matching how the other MCP servers in the cluster are
deployed, and keeping the mcp dependency out of the api image. The
manifest is a reference to copy into the Flux repo; it is not applied
from here.
The manifest deliberately has no HTTPRoute. fin-mcp has no auth of its
own and the API runs with FIN_API_TOKEN unset, so with writes enabled
the only thing keeping it safe is being unreachable from outside the
cluster.
Correctness
create_transaction accepted a free-form type string while
POST /api/transactions accepts all six transaction types. A caller
passing type=transfer would write a single leg with no
linked_account_id — an orphan that corrupts balances and that
paired-delete can never clean up. All eight enum-valued parameters are
now Literal-typed, so the client rejects such calls before they are
made. TestInvariantGuards locks this down.
FinClient was constructed eagerly in create_server(), so a missing
FIN_API_URL killed the process before the MCP handshake and surfaced to
the client only as "server failed to start". It is now built lazily on
first use, turning the same misconfiguration into a readable error.
Tool surface
Adds find_account / find_category, which map a name to the integer id
every other tool requires — exact match wins, otherwise all substring
matches are returned so the caller can disambiguate. Publishes accounts
and categories as the resources fin://accounts and fin://categories.
Drops get_openapi_spec: the tools already cover the API and the spec is
too large to justify a tool slot.
The three update_* tools took an untyped dict[str, Any], which produced
a schema with no properties at all; they now take explicit optional
parameters, plus clear_* flags so the ability to null a field survives
the change.
Removes ~48 lines of write-warning boilerplate duplicated across 24
tools — it lives in the server instructions once, and no longer claims
refresh_prices can double-book. Fixes fetch_ticker_price claiming it
blocks for minutes when the client times out at 30s, and
deactivate_account flagging itself destructive while documenting itself
as reversible.
Tests: 37 passing, up from 27 passing / 3 failing. The failures were
harness bugs — mcp >=1.12 returns (content, structured) from call_tool
where the helper assumed a list. Verified against a live API over both
transports: handshake, reads, writes, name resolution, resource reads,
and rejection of the transfer type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L3qYLuu9jgXsCRXmJ18YGY