Skip to main content
Glama
larrytao05

Portfolio MCP

by larrytao05

Portfolio Dashboard

A local, read-focused portfolio workspace for viewing brokerage accounts, holdings, transaction activity, and market quotes. It pairs a React frontend with a FastAPI backend and SQLite storage, with fixture data available for offline development.

The dashboard lets you refresh portfolio data, explore account holdings, filter and page through saved activity, and search instruments and view quotes. It currently supports SnapTrade for portfolio data and Schwab for market data; both integrations are strictly read-only.

The guarded order workflow uses fake execution for safe product testing; it does not submit live orders.

Guides

  • Frontend guide explains the React and TypeScript application, including data fetching, testing, and a beginner-friendly code tour.

  • Backend guide explains FastAPI, services, providers, SQLite persistence, migrations, and the MCP server.

Related MCP server: akapal-mcp-portfolio

Architecture

  • dashboard/ contains the React and TypeScript frontend.

  • api_main.py starts the FastAPI API used by the dashboard.

  • main.py starts the MCP server over stdio.

  • portfolio_mcp/ contains provider adapters, application logic, persistence, and API routes.

  • tests/ contains backend behavior tests and alembic/ contains SQLite schema migrations.

Portfolio providers list accounts, retrieve holdings snapshots, and return date-bounded transaction history. Account labels are safe to display, transactions are newest first, and a missing price, market value, or cost basis is represented as null.

Authoritative cross-account portfolio calculations belong in the backend overview read model; the browser renders server-provided totals and coverage rather than rebuilding financial aggregates independently.

Run and verify

uv run python main.py
uv run ruff format --check .
uv run ruff check .
uv run pyright
uv run pytest

Dashboard development

The dashboard frontend lives in dashboard/ and talks to a local FastAPI API.

uv run uvicorn api_main:app --reload
cd dashboard && npm install && npm run dev

The API listens on 127.0.0.1:8000; Vite proxies /api requests from the frontend development server. Portfolio and market-data providers use fixture data by default. To load a configured provider from .env, start the API with:

uv run --env-file .env uvicorn api_main:app --reload

On first launch, select Refresh portfolio in the dashboard to save the provider's current accounts, holdings, and available transaction history. The local SQLite database defaults to the ignored portfolio.db; set PORTFOLIO_DATABASE_URL to use another SQLite location. The dashboard's Activity section can filter saved transactions by account, provider, type, symbol, or date range; repeated refreshes do not duplicate activity.

SnapTrade configuration

The SnapTrade adapter reads these local environment variables:

SNAPTRADE_CLIENT_ID=
SNAPTRADE_CONSUMER_KEY=

The fixture server is the default. To start the live read-only SnapTrade provider, export the values and select it explicitly:

export PORTFOLIO_PROVIDER=snaptrade
export SNAPTRADE_CLIENT_ID='your-client-id'
export SNAPTRADE_CONSUMER_KEY='your-consumer-key'
uv run python main.py

Use PORTFOLIO_PROVIDER=fixture to return to fictional data. Never commit .env or share credential values.

Schwab market-data configuration

The instrument-search and quote workflow uses fixture data unless explicitly configured otherwise. After completing Schwab's approved production OAuth flow, add the locally stored credentials and select the Schwab adapter:

SCHWAB_CALLBACK_URL defaults to https://127.0.0.1:8182; keep the explicit setting below when it matches the callback URL registered with Schwab.

MARKET_DATA_PROVIDER=schwab
SCHWAB_CLIENT_ID=
SCHWAB_CLIENT_SECRET=
SCHWAB_REFRESH_TOKEN=
SCHWAB_CALLBACK_URL=https://127.0.0.1:8182

To replace an expired refresh token, run:

uv run --env-file .env python -m portfolio_mcp.schwab_oauth

Open the printed authorization URL, complete Schwab authorization, and paste the full redirect URL when prompted. The command prints a replacement SCHWAB_REFRESH_TOKEN; manually replace that value in .env.

Start the API with uv run --env-file .env uvicorn api_main:app --reload. This enables only read-only Schwab instrument search and quote requests; it does not place, preview, cancel, or modify orders. Leave MARKET_DATA_PROVIDER=fixture for offline development and tests. Never commit .env or share credential values.

Available Tools

3 tools
get_holdingsA

Get holdings and reported valuations for a portfolio account.

ParametersJSON Schema
NameRequiredDescriptionDefault
account_idYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A3.5/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must carry the full behavioral disclosure burden. It conveys a read-only retrieval via 'Get', but it does not describe pagination, data freshness, authorization requirements, or any other runtime behavior. This leaves a meaningful transparency gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence with no redundant wording. Every word contributes to identifying the tool's purpose and scope.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple, has an output schema, and the description states the core action and resource. However, with no annotations and no usage or behavioral detail beyond the purpose, an agent gets only the minimum viable context and may still need to infer details about account selection and response behavior.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 0% description coverage, so the description must compensate. It does add context by framing account_id as belonging to a 'portfolio account', but it does not explain where the ID comes from, its format, or any constraints beyond the schema's required flag. For a single simple parameter this is adequate but not rich.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Get') and resource ('holdings and reported valuations for a portfolio account'), making the tool's purpose immediately clear. It is also distinguishable from sibling tools like list_accounts and get_transactions based on the resource named.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'for a portfolio account' implies the tool is used after an account is identified, but it does not explicitly say when to choose this over list_accounts or get_transactions. No alternatives or exclusion conditions are stated, so usage guidance is only implied rather than explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_transactionsB

Get account transactions in an inclusive ISO-8601 date range.

ParametersJSON Schema
NameRequiredDescriptionDefault
end_dateYes
account_idYes
start_dateYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.2/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden. It only mentions a read operation and date range, but provides no detail on output format, pagination, errors, or permissions. For a read-only tool this is a notable gap.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single, tightly written sentence conveys the essential action and scope with zero filler. It is appropriately front-loaded and easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is minimal but sufficient for a simple read operation with self-explanatory parameter names. However, without annotations or output schema details, it lacks context on response structure and edge cases, leaving some ambiguity for an agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description only clarifies the date range format ('ISO-8601'). account_id is left entirely unexplained, and start_date/end_date semantics are only partially addressed. The description does not compensate for the missing schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description specifies a clear verb ('Get'), a concrete resource ('account transactions'), and a precise scope ('inclusive ISO-8601 date range'). It clearly distinguishes this tool from siblings list_accounts and get_holdings, which cover different resources.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description offers no guidance on when to use this tool versus alternatives. It implies a read operation for transactions, but does not state exclusions or mention that list_accounts/get_holdings are for other data types.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_accountsA

List connected taxable brokerage and Roth IRA accounts.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It only says 'List connected taxable brokerage and Roth IRA accounts,' which conveys a read-only intent but reveals nothing about authentication requirements, rate limits, data freshness, or potential errors. The description is nearly synonymous with the tool name, adding only the account types and 'connected' qualifier.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence that directly states the action and resource. Every word earns its place, and there is no redundant or irrelevant content. For a zero-parameter tool, this is appropriately concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (no parameters, no nested objects, and an output schema exists), the description provides sufficient context for an agent to invoke it correctly. It lacks any mention of prerequisites or edge cases, but for a simple account listing operation, these are not critical. The output schema covers return values, so no additional description is required there.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so a baseline of 4 applies. There are no parameter semantics to clarify, and the description does not need to compensate for any schema gaps. The empty schema is fully consistent with the tool's simple purpose.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb 'List' and a clear resource ('connected taxable brokerage and Roth IRA accounts'), and distinguishes itself from sibling tools get_holdings and get_transactions by focusing on accounts rather than holdings or transactions. An agent can determine exactly what this tool does without ambiguity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly implies when to use this tool: when the agent needs to list connected taxable brokerage and Roth IRA accounts. It does not explicitly exclude alternatives or mention when to use get_holdings or get_transactions, but the resource distinction is clear and no conflicting context is present.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 3 tool updatesv0.1.0
    • First observedget_holdings
    • First observedget_transactions
    • First observedlist_accounts

TDQS

A3.8/5.0

Scored across 3 tools

Disambiguation5/5

Each tool targets a distinct resource: accounts, holdings, and transactions. There is no overlap in their purposes, making selection unambiguous.

Naming Consistency5/5

All names follow a consistent verb_noun pattern using 'list' or 'get' followed by the resource. Minor verb variation is acceptable and does not introduce confusion.

Tool Count5/5

Three tools is well-scoped for a read-only portfolio server. Each tool covers a necessary aspect without redundancy or bloat.

Completeness4/5

The set covers core portfolio reading: account discovery, holdings, and transactions. Missing features like account-level performance or cash balances are minor gaps that can be worked around.

Maintenance

ActivityMaintained
ResponsivenessResponsive

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables local prototyping of Cubi integrations with a mock HTTP server, MCP tools for lifecycle management, and a browser UI for workflow testing without real sandbox credentials.
    1
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    MCP server exposing portfolio and brokerage data through SSE transport, with tools for account summaries, holdings, quotes, market data, and order previews, backed by mock data with live quote fallback.
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    Serves read-mostly MCP tools across ten mock verticals and investment use cases, each backed by bundled SQLite databases, with optional proxying of investment tools to a user-supplied banking API. It enables clients to query realistic vertical data through OAuth-protected MCP endpoints over HTTP or WebSocket.
    -
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables read-only inspection of ad-bid recommendations, scenario analysis, and explanation of threshold decisions using synthetic fixtures, without requiring live ad accounts or credentials.
    -