Skip to main content
Glama
Chart Library

Chart Library

by grahammccain

Chart Library MCP

Market memory for AI, with published research for agents and people.

Version 6.3.3 marks failed public tool calls as MCP errors while preserving their structured recovery details. Invalid requests explain how to correct the inputs; stale publication versions retain instructions to restart from the current overview. Successful response schemas and direct Python calls remain compatible.

Five read-only tools

Tool

Input

Result

market_state

Symbol; optional date

Completed-session state, historical analogs, outcome ranges, transition memory and tape

daily_note

Optional date

Published daily research, selection rule and settled-note tally

research_quality

None

Published calibration receipt, dated sample and qualifications

search_research

Query; optional kind, limit and offset

Relevant publications, IDs, findings, sample receipts, limitations and versions

read_research

Research ID; optional section, offset and version

Overview or an exact source document, with pagination and hashes

Market-state requests need no preliminary search or cohort handle. For published research, search for an ID, then read its evidence.

Example question: “Read AAPL's latest completed-session state. Report the historical analog ranges, sample sizes, session date and limitations.”

Omitting date uses the latest built/published session, not a real-time quote. Dates use YYYY-MM-DD. Missing or thin evidence stays missing or thin.

Related MCP server: TickerAPI

Connect

The primary remote endpoint is https://chartlibrary.io/mcp, using Streamable HTTP and no authentication.

For a command-based client, install or upgrade the package:

python -m pip install --upgrade chartlibrary-mcp

Then use the equivalent of this configuration in your MCP client:

{
  "mcpServers": {
    "chartlibrary": {
      "command": "chartlibrary-mcp"
    }
  }
}

No API key or server-side Python packages are required. An optional CHART_LIBRARY_API_KEY is forwarded as a Bearer token when set. An empty key does not send an Authorization header. CHART_LIBRARY_API_URL can select your own compatible endpoint; only point it at a server you trust with that key.

Python 3.10+ is required. This release uses the MCP 1.x FastMCP interface and pins mcp>=1.28.1,<2.0.0; MCP 2.x is a separate migration.

The equivalent HTTP calls

curl "https://chartlibrary.io/api/v1/state-packet?symbol=AAPL"
curl "https://chartlibrary.io/api/v1/daily"
curl "https://chartlibrary.io/api/v1/calibration"

Use only the call relevant to the question. For a historical state add &date=YYYY-MM-DD. Daily REST requests call the date parameter session.

Research workflow

search_research(query="IONQ noon", kind="casebook")
read_research(research_id="casebook:ionq-noon", section="article",
              version="FULL_VERSION_FROM_SEARCH")

Replace FULL_VERSION_FROM_SEARCH with the full 64-character version from the chosen search result. Updated servers supply documents[section].read_arguments directly; copy those into the tool and follow content.next_read until null. With earlier servers, pass the returned version and next_offset explicitly. Omitting the optional version reads the current publication without checking it against earlier responses. A changed version returns an error explaining how to restart from the current overview; the client does not retry automatically.

Search summaries are discovery metadata, not a source read. Read available guides and relevant limiting findings before making claims. Keyword search ranks document relevance, not market similarity or evidence strength. A partial result means a publication source was unavailable. Withdrawn articles cannot be read. Publication dates and market cutoffs differ; the noon IONQ case must not be merged with completed-session state analogs.

Complete IONQ example · Agent research plan

Read the evidence accurately

  • Preserve dates, sample sizes, informative receipts, warnings and provenance.

  • State-packet excess returns are percentage-point observations relative to a date-matched liquid-stock baseline. Each horizon has its own observed n.

  • Raw historical percentiles are not automatically calibrated forecasts.

  • research_quality audits only the method and population its receipt names. Do not transfer its coverage percentage to every market state or all research.

  • Daily research has its own selection rule and settled-note tally.

  • Historical frequencies are not recommendations to buy or sell.

Research access is free. Service limits and underlying data terms still apply. Honor HTTP 429 and retry guidance; do not assume unlimited throughput.

Existing integrations

Existing tool names stay registered and callable, including pull_comps, search, state_packet, and the cohort inspection tools. To discover the extended menu in a local client, set CHART_LIBRARY_MCP_PROFILE=advanced. This changes discovery, not authorization or access to private research.

The public menu does not expose private fund operations or every experimental state-memory method. Legacy examples in examples/ use the advanced interface.

Build with us

For a custom integration, a larger study, or a product built on the memory, contact Graham McCain — graham@chartlibrary.io.

Developer guide · Methodology · Data terms · Privacy

Local verification

python -m pytest test_vendor_import.py

The import smoke runs without an API key and without application services/ or db/ packages. No package or registry publication is performed by tests.

Available Tools

22 tools
analyzeAnalytic MetricsA
Read-onlyIdempotent
Inspect

Analytic metrics on a cohort or (symbol, date) anchor.

metric values:
  - "anomaly"             — is the pattern unusual vs the symbol's
                              own history? (needs symbol)
  - "volume_profile"      — intraday volume vs historical norms
                              (needs symbol)
  - "crowding"            — cross-symbol crowding indicator
                              (market-wide; no symbol needed)
  - "correlation_shift"   — rolling correlation breakdowns
                              (extra_args: lookback, window, symbols)
  - "earnings_reaction"   — historical earnings gap reactions
                              (needs symbol; extra_args: min_gap)
  - "pattern_degradation" — are signals losing edge vs historical
                              accuracy? (market-wide)
  - "regime_accuracy"     — win rates filtered by current regime
                              (needs symbol)
  - "decompose"           — find slice conditions that separated
                              winners from losers within a cohort
                              (needs cohort_id; horizon, max_slices,
                              explain_slices apply)
  - "clusters"            — cluster a cohort into k forward-return
                              groups (needs cohort_id; horizon, k)

Supply cohort_id (preferred, anchor inherited) OR explicit
symbol+date for the symbol-needing metrics.

Args:
    metric: see list above
    cohort_id: handle from `search` or `cohort` (required for
        decompose, clusters; preferred for symbol-needing metrics)
    symbol, date: explicit anchor when no cohort_id available
    extra_args: per-metric optional kwargs (see metric list)
    horizon: forward horizon in trading days (decompose, clusters,
        regime_accuracy)
    max_slices: max returned slice conditions (decompose)
    explain_slices: include Haiku narrative tying slices together
        (decompose)
    k: cluster count override (clusters; default chosen automatically)

Inputs: metric (required), cohort_id, symbol, date, +5 more. Returns: result

anchor_fetchAnchor Metadata (deprecated → use context(target={symbol,date}))C
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use context(target={"symbol": ..., "date": ...})]

Inputs: symbol (required), date. Returns: result

clustersCluster Cohort (deprecated → use analyze(metric='clusters'))A
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use analyze(metric="clusters", cohort_id=...)]

Inputs: cohort_id (required), horizon, k. Returns: result

cohortCohort IntelligenceA
Read-onlyIdempotent
Inspect

Conditional-distribution analysis — the Chart Library core primitive.

Three depth modes:

  depth="basic" (default, fast ~50ms):
    Returns kNN cohort + outcome distribution (p10/p25/p50/p75/p90,
    win rate, MAE, MFE) + survivorship. Supply cohort_id (refine
    prior cohort) OR query OR symbol+date.

  depth="full" (Layer 3, ~280ms, paid tier):
    Returns the basic outputs PLUS feature importance (which Layer 2
    features separated winners from losers within this cohort),
    regime stratification (outcomes sliced by vol/macro), risk
    profile (drawdown / runup percentiles), cohort tightness
    score, AND a deterministic `summary` block of classification
    flags (verdict_class, edge_class, regime_alignment,
    sample_quality, conviction, swing_factors with framings,
    caveat_flags). Read `summary` first — paraphrase the framings
    in your own voice rather than narrating the raw stats; cite
    numbers in parentheses for support. `summary` is included
    when `include_anchor_metadata=True` (the regime classification
    needs the anchor's metadata). Requires symbol+date+timeframe
    (cohort_id alone isn't enough — the Layer 3 analyzer needs
    the full anchor).

  depth="compare" (~400ms):
    Compare TWO anchors' cohorts side-by-side. Pass symbol+date for
    the primary AND compare_with={"symbol":..., "date":...,         "timeframe":...} for the secondary. Returns both cohorts'
    distributions plus a delta summary.

Filters (Layer 2 metadata constraints):
    vol_regime: list of "low"/"mid"/"high"
    macro_state: list of "bullish"/"neutral"/"bearish"
    has_news: bool
    days_since_earnings / days_since_ath / sector_rs /
        realized_vol / relative_volume: dict with "min" / "max"

Empirical-distribution analysis only. Does NOT predict a single
point return; surfaces what historical analogs did and which features
separated them.

Args:
    symbol, date, timeframe: anchor (default timeframe "1h")
    query: alt to symbol+date, "SYMBOL YYYY-MM-DD"
    cohort_id: refine a stored cohort (basic mode only)
    depth: "basic" | "full" | "compare"
    filters: Layer 2 constraints
    horizons: forward horizons in trading days (default [5, 10] for
        basic, [1, 5, 10] for full)
    cohort_size: target K (10-2000)
    compare_with: secondary anchor for depth="compare"
    include_feature_importance, include_regime_stratification,
        include_risk_profile: full-mode toggles
    include_modes: full-mode only — when True, returns a `modes`
        array clustering the cohort into K outcome playbooks (e.g.
        "smooth uptrend +4.2%", "sharp downtrend -8.1%"). Each
        entry has mode_id, label, n, median_return, win_rate,
        std_return, p25/p75_return, and centroid_cum_returns
        (sparkline-ready trajectory). Sorted ASC by median_return
        so mode_id=0 is the worst realized cluster. Use for
        "playbook" framing — the cohort isn't one outcome, it's
        K distinct historical paths.
    n_modes: number of modes to cluster into (2-6, default 4).
        Adaptive: degrades to fewer modes if cohort_size is small.
    exclude_same_symbol_days: drop same-symbol analogs within N
        calendar days of the anchor (autocorrelation control;
        default 10)
    include_path_stats: include MAE/MFE/realized-vol (basic mode)
    fields: full-mode only — optional allowlist of top-level
        response keys to return. None (default) = full payload.
        Valid: outcome_distribution, feature_importance,
        regime_stratification, risk_profile, cohort_tightness_score,
        cohort_score, combined_conviction, pulse_boost,
        narrative_pulse, cohort_anchors, anchor_metadata. Use to
        slim the JSON when you only need a subset (e.g.
        fields=["outcome_distribution"] drops 97% of bytes).
        anchor, cohort_size_actual, elapsed_ms, warnings are
        always returned.

Inputs: symbol, date, timeframe, query, +14 more. Returns: result

cohort_analyzeCohort Analyze (deprecated → use cohort(depth='full'))B
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use cohort(depth="full", ...)]

Layer 3 cohort intelligence. Forwarded to cohort(depth="full").

Inputs: symbol (required), date (required), timeframe, cohort_size, +6 more. Returns: result

cohort_compareCohort Compare (deprecated → use cohort(depth='compare'))C
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use cohort(depth="compare", compare_with=...)]

Inputs: symbol_a (required), date_a (required), symbol_b (required), date_b (required), +3 more. Returns: result

contextMarket & Symbol ContextA
Read-onlyIdempotent
Inspect

Situational data about a target.

target accepts four shapes:
  - "market" (default): SPY/QQQ regime + sector rotation +
                          breadth + macro
  - "SYMBOL" (e.g. "NVDA"): ticker metadata + sector + market cap
  - {"symbol": "NVDA", "date": "2024-08-05"}: anchor metadata —
    sector, cap, point-in-time regime, news, days_since_earnings,
    etc. Lightweight; no kNN.
  - "system": DB coverage stats (embedding count, daily bar count,
    date range)

Args:
    target: "market" | "SYMBOL" | {symbol, date} | "system"

Inputs: target. Returns: result

decision_briefDecision BriefA
Read-onlyIdempotent
Inspect

One-call decision-grade orchestrator. Use this as your DEFAULT first call for any (symbol, date) anchor question.

Composes cohort_analyze (depth=full) + anchor metadata + Layer 5 memory
(symbol_intelligence) + narrative pulse into a single structured brief.

── HOW TO TURN THIS INTO A GOOD ANSWER ──────────────────────────
Read `summary` first. It contains deterministic classification flags:

    verdict_class:    bullish | lean_bull | coin_flip | lean_bear | bearish | broken
    edge_class:       trivial | small | meaningful | large
    regime_alignment: tailwind | neutral | headwind
    sample_quality:   thin | ok | strong
    conviction:       low | med | high
    swing_factors:    [ { factor, direction, framing } ]
    caveat_flags:     [ thin_in_regime_sample, regime_was_derived, ... ]

Paraphrase the `framing` strings in your OWN voice — do not quote them
verbatim. Cite the raw numbers in parentheses, don't enumerate every
structured field. Lead with the verdict, then the context, then the
swing factors as things to watch, then conviction.

Example (your voice may differ):
  "Honestly, this NVDA setup is a coin flip. In-regime cohort (n=21)
   printed +0.09% median over 5d (52% wins) — basically identical to
   other regimes (+0.27%, 52%). Sector is lagging hard (-10.4 RS),
   which is muting reads. Soft sample size — directional, not thesis."

── RESPONSE STRUCTURE ───────────────────────────────────────────
  summary                       (read this first — see above)
  current_regime                (anchor's regime label + features)
  cohort_total                  (all analogs, all regimes)
  in_current_regime             (subset matching the anchor's regime)
  outside_current_regime        (rest, weighted average)
  conditional_edge              (median_lift_pp + win_rate_lift_pp)
  thesis_invalidation_triggers  (top 5 features, narrative-ready with
                                 interpretation strings — quote these
                                 when explaining what would flip the read)
  feature_importance            (top 20 features ranked by within-cohort
                                 importance, compact shape — use this
                                 when the user asks about a specific
                                 feature or you need depth beyond the
                                 top 5. Mirrors the full attribution
                                 view the /intelligence UI shows.)
  n_features_total              (count of all features computed; if
                                 greater than 20 the rest are available
                                 via /api/v1/cohort_analyze directly)
  memory_context                (Layer 5 prior observations)
  narrative_context             (news pulse)
  conviction                    (legacy — same as summary.conviction)

When to use this vs the primitives:
  - decision_brief: agents asking "what should I know about this anchor?"
  - cohort (depth='full'): when you need full feature_importance / raw stats
  - analyze (metric=...): single metric drill-downs

Args:
    symbol, date, timeframe: anchor (timeframe default "1h" — current; "1d" lags ~3 days)
    cohort_size: target K (default 300)
    horizon_days: forward horizon for the headline read (default 5)
    include_memory: include Layer 5 prior-observations context (default True)
    include_narrative: include news pulse / narrative-change context (default True)

Inputs: symbol (required), date (required), timeframe, cohort_size, +3 more. Returns: result

decomposeDecompose Cohort (deprecated → use analyze(metric='decompose'))A
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use analyze(metric="decompose", cohort_id=...)]

Inputs: cohort_id (required), horizon, max_slices, explain. Returns: result

discoverDiscover SetupsA
Read-onlyIdempotent
Inspect

What's interesting across the market today.

Three modes:

  mode="picks" (default):
    Top picks ranked by cohort score. Default limit 20, lookback 3
    days, horizon 5.

  mode="daily_setups":
    Tomorrow's brief — top picks pre-enriched with full-cohort
    statistics, top-3 features, and yesterday's calibration recap,
    all in one response. Replaces the multi-call discovery dance.
    Default top=3, timeframe="1d".

  mode="risk_adjusted":
    Today's picks ranked by Sharpe-like score. Default min_sharpe
    0.3.

Args:
    mode: "picks" | "daily_setups" | "risk_adjusted"
    limit: max picks returned (mode="picks")
    lookback_days: scan window in days (mode="picks")
    horizon: forward horizon for ranking (mode="picks")
    top: number of pre-enriched setups (mode="daily_setups")
    timeframe: cohort timeframe (mode="daily_setups")
    date: ISO date override (mode="risk_adjusted"; default today)
    min_sharpe: minimum Sharpe threshold (mode="risk_adjusted")
    fields: daily_setups only — optional allowlist of top-level
        response keys. Valid: setups, yesterday_recap. as_of_date
        and cohort_timeframe are always returned. Use to drop
        yesterday_recap when you only need today's picks.

Inputs: mode, limit, lookback_days, horizon, +5 more. Returns: result

discover_picksDiscover Picks (deprecated → use discover(mode='picks'))C
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use discover(mode="picks", ...)]

Inputs: limit, lookback_days, horizon. Returns: result

explainExplain CohortA
Read-onlyIdempotent
Inspect

Narrative + rankings derived from a stored cohort.

style values:
  - "filter_ranking"    — rank candidate filters by how much each
                          one shifts the distribution at the given
                          horizon. Use to discover conditional
                          structure before calling `cohort` with the
                          winning filter.
  - "prose"             — plain-English summary of the cohort
                          outcome (Claude Haiku).
  - "position_guidance" — exit-signal recommendation for an open
                          position. Derives symbol+entry_date from
                          the cohort anchor.
  - "risk_ranking"      — today's risk-adjusted picks (Sharpe-like)
                          from forward tests.

Args:
    cohort_id: handle from `search` or `cohort`
    style: see list above (default "filter_ranking")
    horizon: forward horizon in trading days (default 5)

Inputs: cohort_id (required), style, horizon. Returns: result

get_daily_setupsDaily Setups (deprecated → use discover(mode='daily_setups'))C
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use discover(mode="daily_setups", ...)]

Inputs: top, timeframe. Returns: result

narrativeNews IntelligenceA
Read-onlyIdempotent
Inspect

News intelligence — narrative-change signals layered on price.

Two modes:

  mode="pulse" (default):
    Single-symbol narrative pulse — frequency anomaly, tone shift,
    sentiment-price misalignment (priced-in vs narrative-change),
    FinBERT sentiment composite. Surfaces catalysts that price
    hasn't yet reflected. Needs symbol.

  mode="alerts":
    Market-wide narrative anomalies — top tickers right now where
    sentiment and price are most divergent. Default min_pulse 0.30,
    limit 30.

Args:
    mode: "pulse" | "alerts"
    symbol: ticker (mode="pulse" only)
    min_pulse: minimum narrative_change_score threshold
        (mode="alerts")
    limit: max alerts returned (mode="alerts")

Inputs: mode, symbol, min_pulse, limit. Returns: result

narrative_alertsNarrative Alerts (deprecated → use narrative(mode='alerts'))D
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use narrative(mode="alerts", ...)]

Inputs: min_pulse, limit. Returns: result

narrative_pulseNarrative Pulse (deprecated → use narrative(mode='pulse'))A
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use narrative(mode="pulse", symbol=...)]

Inputs: symbol (required). Returns: result

portfolioPortfolio AnalysisA
Read-onlyIdempotent
Inspect

Portfolio-level analysis OR per-symbol track-record + Layer 5 memory.

Two modes:

  mode="basic" (default):
    Multi-holding conditional distribution. Runs per-holding cohorts
    in parallel, weight-averages the distributions, ranks tail
    contributors (weight × p10, most negative first). PM-agent
    primitive. Pass holdings=[{symbol, weight, date}].

  mode="symbol_intel":
    Per-symbol track record + Layer 5 memory — what does Chart
    Library know about this single symbol across all prior
    analyses? Returns prior cohort_observations, feature_reliability
    learned for the symbol, and the symbol's per-pattern accuracy
    history. Pass symbol=X, lookback_days=N.

Args:
    holdings: list of {symbol, weight, date} (mode="basic")
    symbol: ticker (mode="symbol_intel")
    mode: "basic" | "symbol_intel"
    horizons: forward horizons (mode="basic"; default [5, 10])
    top_k_per_holding: cohort size per holding (mode="basic")
    include_path_stats: include MAE/MFE (mode="basic"; slower)
    lookback_days: history window (mode="symbol_intel"; default 365)

Inputs: holdings, symbol, mode, horizons, +3 more. Returns: result

report_feedbackReport FeedbackA
Idempotent
Inspect

File an error or improvement suggestion to Chart Library.

Use this when something looks wrong (unexpected response shape,
surprising statistics, an error you can describe), or when you
spot a missing capability that would have unblocked you. Reports
land in Graham's inbox and feed the roadmap.

Args:
    message: free-text description (required)
    endpoint: which API endpoint, if any (e.g. "cohort_analyze")
    symbol: associated ticker, if any
    severity: "low" | "medium" | "high"

Inputs: message (required), endpoint, symbol, severity. Returns: result

similar_cohortsSimilar Cohorts (deprecated → use search(mode='similar'))A
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use search(mode="similar", symbol=..., date=...)]

Inputs: symbol (required), date (required), timeframe, top_k. Returns: result

symbol_intelligenceSymbol Intelligence (deprecated → use portfolio(mode='symbol_intel'))A
Read-onlyIdempotent
Inspect

[DEPRECATED in v5 — use portfolio(mode="symbol_intel", symbol=...)]

Inputs: symbol (required), lookback_days. Returns: result

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    C2.9/5.0

    Scored across 22 tools

    Disambiguation2/5

    Many tools have overlapping purposes (analyze vs cohort, decision_brief vs cohort+context) and deprecated tools (11 of 22) with similar names (cohort_analyze vs cohort) cause confusion. Descriptions help but an agent could easily misselect between analyze and cohort for metrics.

    Naming Consistency3/5

    Active tools use a mix of single-word verbs (analyze, discover) and nouns (cohort, context), but the deprecated tools (e.g., clusters, decompose) break the pattern. The naming is not completely chaotic but lacks a strict verb_noun convention.

    Tool Count2/5

    22 tools is too many for a focused API, especially with 11 deprecated ones that are essentially dead weight. The active set (~11 tools) would be appropriate, but the server includes all deprecated tools, making it noisy and heavy.

    Completeness4/5

    The non-deprecated tools cover the key operations: pattern search, cohort analysis, metrics, narratives, portfolio analysis, and feedback. Minor gaps (e.g., direct bar fetching) but the core domain of chart-based decision support is well covered.

    Maintenance

    ActivityStale
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      A
      quality
      D
      maintenance
      Financial intelligence for AI agents. 31 tools across 8 data sources — regime, derivatives, stablecoin flows, momentum, volatility, macro, DeFi, weather patterns, political cycles, seasonality. The context layer between your agent and a bad trade.
      31
      22 npm
      9
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      Agent-ready financial intelligence tools for AI agents. Two curated tools — get_stock_snapshot and get_company_metrics — that combine multiple data sources, derive signals (UNDERVALUED, STRONG, ACCELERATING), and pre-compute the math. One call, one agent-friendly response.
      3
      82 npm
      1
      MIT