Content
# findatamcp
> English · [English](README.en.md)
[](https://www.python.org/)
[](#license)
[](https://modelcontextprotocol.io/)
[](https://github.com/jlowin/fastmcp)
**One call, data and charts ready.**
**findatamcp** enables LLM Agent to obtain both structured data for models and interactive charts for users in a single tool call. The server covers 42 financial data tools (A-share market, financial statements, index funds, macroeconomic indicators), and renders results into scalable K-line, price change dashboard, and fund flow charts through MCP Apps. Data is fetched only once, and users can operate directly in the artifact panel.
---
## Implementation Path
### Overview
```mermaid
flowchart LR
Agent["LLM Agent"] -->|"@mcp.tool call"| Tool["Tool handler"]
Tool --> Envelope["artifact_payload.<br/>finalize_artifact_result"]
Envelope --> TR["ToolResult"]
TR --> Content["content[0].text<br/>markdown preview + trailer"]
TR --> Struct["structuredContent<br/>rows / columns / schema"]
TR --> Meta["meta.ui<br/>= ui://findata/*"]
Struct -->|"rows ≤ 200"| Inline["inline full rows"]
Struct -->|"rows > 200"| Store["data_file_store<br/>.jsonl + schema sidecar"]
Store --> DataURI["data://table/{id}"]
Store --> HTTP["GET /data/{id}.jsonl"]
Content --> LLM["LLM reads summary,<br/>decides next step"]
Meta --> IFrame["sandboxed iframe<br/>ECharts render"]
DataURI --> Next["subsequent step:<br/>resources/read · execute"]
```
### Core Assumptions: Two Failure Modes of LLM Agent in Financial Data Scenarios
The entire project is designed around two observed failure modes:
**Failure Mode ①: Too many tools, LLM can't choose**
Providing 42 tool descriptions at once overwhelms the model with thousands of tokens, causing interference between similar tools (e.g., `get_stock_data` / `get_realtime_price` / `get_historical_data` all relate to market data). This leads to frequent incorrect or repeated attempts.
Solution → **Progressive Disclosure** (implemented in `findatamcp/tools/meta.py`):
- `get_tool_manifest()` returns a tool list grouped into 9 categories, each with only `name + summary`, allowing LLM to preview the catalog.
- `focus_category("Market Data")` hides non-matching tools using FastMCP `ctx.disable_components(match_all=True)` + `enable_components(tags=...)`, keeping only the current category and navigation tools visible.
- `show_all_tools()` restores the full list.
- Each `@mcp.tool(tags={"Market Data"}, ...)` includes a Chinese category tag, with key modules (e.g., `market_statistics` / `macro_data`) providing scenario guidance in docstrings.
Default visibility control limits the number of tools LLM sees to 3–8, expanding to all 42 only when necessary.
**Failure Mode ②: One tool call overwhelms context**
Users ask for data (e.g., "Show CSI 300 daily chart for the past 8 years"), and a naive implementation would return 2000+ rows × 10 columns of JSON, exceeding 30k tokens and making subsequent dialogue impractical.
Solution → **Data pointers in context, not data itself** (`findatamcp/utils/large_data_handler.py` + `findatamcp/resources/large_data.py`):
- Threshold `THRESHOLD = 200 rows`, beyond which data is not inlined.
- Returns `preview (first 5 rows) + summary (date range + latest/min/max/mean values) + resource_uri = data://table/{id}`, allowing LLM to see a brief summary while the full data (2000 rows) is stored in a `.jsonl` artifact file.
- For detailed analysis, LLM can actively `resources/read` the URI; for calculations, it can call `execute` to read the file; frontend UI can drill down via `stock://calc_metrics/...` / `data://`.
### 1. Envelope Contract: content + structuredContent + meta
Each tool outputs a `ToolResult` through `finalize_artifact_result` (`findatamcp/utils/artifact_payload.py`), divided into three layers:
| Layer | Consumer | Content |
| :--- | :--- | :--- |
| `content[0].text` | LLM | Header + first 10 rows markdown table + trailer ("UI rendered" / "remaining rows" / "request full data with as_file=True") |
| `structuredContent` | UI iframe / execute tool | Sole complete data source: `row_count` / `columns=[{name,type}]` / `rows=[…]` / optional `date_range` / optional `path` / optional `download_urls` |
| `meta` | MCP host | `{ui: None}` explicitly disables UI rendering (when `include_ui=False`), otherwise inherits `app=AppConfig(...)` registered `ui://` |
This way, LLM sees concise text + guidance, avoiding large tables; UI and downstream scripts access complete data via `structuredContent.rows`, derived from the same `rows` to prevent drift.
### 2. Four Resource URI Families: UI, Large Data, Entities, and Calculation Byproducts
Besides tools, findatamcp registers four MCP resource types, each URI addressing a specific context issue:
| Scheme | Example | Purpose |
| :--- | :--- | :--- |
| `ui://findata/*` | `ui://findata/kline-chart` | HTML + inline ECharts, rendered in a sandboxed iframe as an interactive component |
| `data://table/{data_id}` | `data://table/7f3e…` | Artifact data (over 200 rows) fetched on demand, avoiding LLM context overload |
| `entity://{stats,search/…,code/…,markets}` | `entity://search/贵州茅台` | Securities entity directory (codes / names / pinyin / aliases / statistics), responding in milliseconds |
| `stock://calc_metrics/{calc_id}[/pair/{a}/{b}]` | `stock://calc_metrics/abc/pair/600519.SH/000858.SZ` | Time-series byproducts and derived metrics (volatility, max drawdown, Sharpe ratio, monthly comparison) |
All share a principle: **Context carries pointers + summaries; actual data fetched on demand**.
### 3. MCP UI: ui:// Resources + iframe postMessage
`findatamcp/resources/ui_apps.py` registers `ui://findata/*` (e.g., `market-dashboard` / `kline-chart` / `moneyflow-chart` / `macro-panel` / `data-table`), returning complete HTML:
- **Zero CDN dependencies**: `static/echarts.min.js` read into memory at server start, inlined into `<script>` tags to meet sandboxed iframe CSP and offline deployment needs.
- **Theme variable propagation**: HTML uses `light-dark()` CSS variables; host sends `ui/notifications/host-context-changed` to sync theme changes.
- **Four handshake messages** (protocol `2025-06-18`):
- `ui/initialize` (host → iframe, iframe responds with `result.protocolVersion + appCapabilities`)
- `ui/notifications/initialized` (iframe → host, reporting readiness)
- `ui/notifications/tool-input` (host → iframe, with input parameters)
- `ui/notifications/tool-result` (host → iframe, with `structuredContent` or `content`, parsed and rendered by iframe)
Tools only need to declare binding relationships with `@mcp.tool(app=AppConfig(ui_uri="ui://findata/kline-chart"))`, and the host will push `structuredContent` to the corresponding iframe.
### 4. Large Data Context Control: Threshold-based Routing + Preview + Resource URI
The core context saver in `findatamcp/utils/large_data_handler.py` handles large data:
- **Threshold `THRESHOLD = 200 rows`**. Below the threshold, full data is inlined; above, it switches to "preview + resource" mode.
- **Preview takes first N rows** (`build_preview_rows`, typically `preview_rows=5`, or recent rows in "tail" mode).
- **Automatic summary** (`_build_summary`): scans rows to identify date ranges and numerical column stats (latest, min, max, mean), enabling LLM to answer questions without reading the full table.
- **UI sampling** (`sample_rows`, max_points=120): downsamples long sequences (e.g., K-line data) before rendering, reducing iframe pressure.
- **Artifact storage** (`data_file_store.store`, `findatamcp/cache/data_file_store.py`): writes `.jsonl` (with date/code columns stringified, `NaN → null`) + schema sidecar, used by downstream AG Grid for type inference. 24h TTL, cleaned up periodically.
- **Returns pointers**: `is_truncated=true` / `data_id` / `resource_uri=data://table/{id}` / `download_urls` / `summary` / `preview` / `schema` / `total_rows`. LLM sees these pointers to decide on further actions.
### 5. LLM Behavior Constraints: Preventing Repeated Calls
UI-rendered tools have an issue: LLM sees only `content.text`, unaware that the iframe has rendered, often leading to repeated calls. `findatamcp/utils/ui_hint.py` and `artifact_payload.build_content_trailer` address this:
```
UI has rendered (ui://findata/kline-chart).
Complete 245 rows written to /workspace/xxx.jsonl.
Users can open this file in the artifact panel for interactive viewing; you can also read it with execute for further analysis.
```
Tool docstrings include `AS_FILE_INCLUDE_UI_DECISION_GUIDE`, exposing the decision table for `as_file` / `include_ui` to LLM, significantly reducing repeated calls.
### 6. Dependency Injection + Tool Registration
`server.py` sets up a one-time dependency injection container at startup:
```python
api = TushareAPI(token, cache=tushare_cache)
db = EntityStore.from_sqlite(db_path)
mcp = FastMCP("findatamcp")
register_market_tools(mcp, api)
register_financial_tools(mcp, api)
register_search_tools(mcp, api, db)
# … 12 register_*_tools
```
Each module's `register_*_tools(mcp, api, [db])` registers `@mcp.tool` / `@mcp.resource` / `@mcp.prompt` with the FastMCP instance. Tests can replace `api` with mocks and `db` with in-memory fixtures.
### 7. Caching Layers
| Layer | Location | Expiration Strategy |
| :--- | :--- | :--- |
| Tushare raw responses | `cache/tushare_cache.py` | By table name + parameter hash, with request frequency-based TTL |
| Calculation results (alignments / technical indicators) | `cache/calc_cache.py` | In-process LRU, cleared on restart |
| File artifacts (`.jsonl` / schema) | `cache/data_file_store.py` | 24h TTL, periodic cleanup |
On the asynchronous side, the Tushare Python SDK is synchronous; `TushareAPI` wraps it with `asyncio.to_thread` to ensure the FastMCP event loop isn't blocked.
### 8. Entity Retrieval: EntityStore + pypinyin
Search tools often map concepts (e.g., "liquor industry", "Industrial Bank", "Ping An") to code lists. `entity_store.py` loads the full securities entity list from SQLite into memory at startup:
- Primary index: `ts_code → entity`
- Inverted index: name / pinyin full spell / pinyin initials / aliases → ts_code set
- Chinese names precomputed with `pypinyin` for multi-form matches
Search uses in-memory indexing + TF ranking, responding in milliseconds without hitting the Tushare API.
---
## Why Choose findatamcp?
LLM Agent meets financial data, and the real bottleneck isn't data itself but two recurring engineering issues:
- **Tool descriptions overwhelm system prompts** — 42 tools laid out at once easily consume thousands of tokens, with similar tools interfering. findatamcp uses **progressive disclosure** to limit visible tools to 3–8 (`get_tool_manifest` → `focus_category` → `show_all_tools`).
- **One tool return fills context** — 2000 rows of daily data as JSON exceeds 30k tokens, making subsequent dialogue impractical. findatamcp uses **200-row threshold routing**: beyond the threshold, only `preview + summary + resource_uri` are returned, with full data stored in `.jsonl` artifacts for on-demand fetching.
- **Data for both models and users** — same `structuredContent` synced to sandboxed iframe via MCP Apps, rendered into scalable K-line / dashboard / fund flow charts; LLM sees only markdown previews and guidance, avoiding repeated calls.
- **Production-ready details** — zero CDN dependencies (ECharts inlined), three-layer caching, asynchronous Tushare calls, PM2 daemon, artifact HTTP download routes, entity fuzzy search (pypinyin + aliases), not just demo-level assembly.
Preview
The structured results of tool calls are returned through the MCP Apps specification (SEP-1865 `2025-06-18`) `ui://` resources are rendered as interactive components in a sandboxed iframe by the client; LLM sees a markdown table summary same data via `content[text`, avoiding repeated calls.
<p align="center">
<="docs/pic/overview.png" alt="A stock market overview card" width="720"><br>
sub><code>get_market_overcode> — Full market paper: number of stocks up, average, PE/P, circular proportion</sub>
>
<p align="center <img src="docs/pic-ui.png" alt="CSI 300 daily K-line"720"><br>
<code>get_historical_data300.SH", include)</code> — Daily K-line + dual moving average + trading scalable and draggable</sub>
>
## Quick Start
```bash
# Python 3+
conda create -n findatamcp python=3.12
conda activate findatamcp
pip install -r requirements.txt
cp .env.example .env
# Edit .env and fillUSHARE_TOKEN
# Run (choose one)
python -m findatamcp.server # Streamable HTTP, recommended
python findatamcp.server_sse # SSE, compatible with Claude Desktop, etc.
./start.sh PM2 daemon
docker compose up -d # Docker deployment (see below)
```
```bash
exportHARE_TOKEN=your_token
docker compose up -d
# Endpoint: http://127.0.1:8006
# Logs: docker logs -f findatamcp
# Artifact files persist in named volume findatamcp-data (mounted to /data in container)
```
Default is Streamable HTTP. When SSE is needed, uncomment the line `command: ["python", "-m", "findatamcp.server_sse"]` in `docker-compose.yml`. The image is based on `3.12-slim` and the build product is about 400 MB.
> ⚠️ `Dockerfile` and `docker-compose.yml` are provided but **not fully verified by CI**. Pleasedocker compose build` and compose up` yourself before production.
### PM2
| Variable | Default Description |
| --- | --- | --- |
| `FIND` | `~/miniforge3/envs/mcp_server/bin/python` | Python interpreter path |
| `FINDATA_M` | Directory where `.config.js` is located | Repository root |
| `FINDATA_LOG_DIR` | `cp-logs` | |
| `FIND_DIR` | `/tmp/findatamcp_data` | Large data artifact directory |
| `_SERVER_HOST` | `127.0.0.1` address |
| `MCP_SERVER_PORT` | `800 | Port |
| `_URL` | `http.0.0.1:8006` | Artifact external link base address |
## Client Access Claude Desktop (SSE)
Library/Application Support/Claude_desktop_config.jsonmacOS) or `%ATA%\Claude\claude_desktop_config.json`):
```json
{
"mcpServers": {
"findatamcp": {
transport": "sse",
url": "http://0.0.1:8006/sse"
}
}
```
Run `python -m findatamcpse` or `./start.sh` on the server restart Claude Desktop to seeamcp in the tool panel is recommended to first adjust_tool_manifest` to view categories, and then `focus_category` to focus.
### Cursor / Continue.dev / VS Code MCP Plugin
Also use SSE:
```json
{
"mcp.s {
"findat": {
"urlhttp://127.0.0.1:8006",
"transport": }
}
### Self-built Agent (px direct connection)
See client example in [docs_GUIDE.md](SE_GUIDE.mdestablish connection → get session call tool).
## Directory Structure
```
findatam├── findatamcp # Main package
│ ├── server.py # Streamable HTTP entry, assemble DI container ├── server_sse # SSE entry
│ config.py # Configuration ├── database.py # SQLite query
│ ├── entity_store.py # Entity index (memory + pypinyin)
│ ├── cache/ # tushare response / calculation / file artifact
│ │ ├── tushare_cache.py
│ │ ├── calc_cache.py
│ │ └── data_file_store.py
│ ├── tools/ # MCP tools ( × 42 @mcp.tool)
│ ├── resources/ # MCP resources
│ │ ├── ui_apps.py ui:// interactive components ( inline ECharts)
│ │ large_data.py # data:// JSONL artifact read
│ │ ├── stock_data.py
│ └── entity_stats.py
│ ├── prompts/ # (stock_analysis, etc.)
│ ├── routes/ # Additional HTTP routes (data download)
└── utils/
│ ├── artifact_payload.py # Unified
│ ├── ui_hint.py # LLM prompt text
│ ├── tushare_api.py
│ ├── data_processing # Alignment / suspension processing ├── technical_indicators.py
│ ├── large_data_handler.py
│ ├── response.py
│ └── errors.py
├── tests/ #
├── docs/ # Documentation (including screenshots)
├── static/ # Front-end resources (ECharts, local packaging pm2.config.js # PM2 deployment configuration
├── start.sh / stop.sh # PM2 lifecycle
├── start_s # SSE foreground startup── requirements.txt
```
## Tool List
There are **42 MCP tools**, divided into modules**:
| Module | Content |
| --- | --- |
| `market_data` | Real, historical K-line, daily line |
| `market_flow` | Capital flow, transaction details |
| `market_statistics` | Number of stocks up and down, sector statistics |
| `financial_data` | Financial statements, indicators, dividends |
| `performance_data` | Performance forecast / report |
| `index_data` | and constituent stocks |
| `fund_data` | Public fund, holdings |
| `sector Industry / concept sector |
macro_data`economic indicators (GDP / PMI / M2 /…) |
| `analysis` | Technical indicators, correlation,| `search` / name / pinyin / alias search |
| `meta Metadata and capability discovery |
Also (`entity_statslarge_data`, `stock `ui_apps`) and prompts_analysis`).
##enarios
**① Desktop research workstation (Claude Desktop / Cursor)**
the SSE endpoint, directly ask in the dialog box: "Help me pull the CSI K-line for the past half year, overlay 5/20 average". The tool two things at the same time: LLM gets a markdown summary knowing "a total of 120 trading days, +3.4% increase, 20-day moving average at 4520", and the sidebar artifact panel synchronously renders a scalable K-line + dual moving average + trading volume. Ask "which day's closing price is the highest", LLM doesn't need to call again, it has the summary.
**② Internal asset management / investment research system data platform**
Useamcp asAI data engine" in intranet, business personnel ask financial statements, number of and down in the whole market, sector capital flow through the company's Over 200 lines of data automatically fall into `.jsonl`, and download routing directly connects to downstream risk control / attribution `/data/{id}.jsonl`; `data://id}` can also let subsequent read and do, the whole process does memory.
**③ Macro Dashboard automatic generation**
' daily / weekly report Agent connects `get_macro_indicator` (GDP / CPI / PMI / M2PR) + `get_sector_flow` + `getview`, LLM writes market on summary, and `ui://findata/macro-panel` directly renders an instrument panel containing multi-indicator line +. No extra front-end needed.
## Configuration
Common variables in `.env`:
```bash
TUSHAREyour_token_here # Required
MCP_SERVER127.0.0.1
MCP_SERVER8006
MCP_TRANSPORT=streamable-http # or sse
LOGINFO
PYTHONUNBUFFER1
```
## Testing
```bash
pytest tests/
```
Covers cache, data processing, market statistics, tool SSE client, end-to-end process.
## Documentation
docs/SSE_GUIDE.md](docs/SSE_GUIDE — SSE deployment access
## License
are Pro data usage please follow [Tushare User Agreement](https://tushare.pro/document/1). This is released under MIT.
MCP Config
Below is the configuration for this MCP Server. You can copy it directly to Cursor or other MCP clients.
mcp.json
Connection Info
You Might Also Like
Vibe-Trading
Vibe-Trading: Your Personal Trading Agent
ai-berkshire
Berkshire in the AI Era: A Value Investment Research Framework Based on...
hexstrike-ai
HexStrike AI is an AI-powered MCP cybersecurity automation platform with 150+ tools.
valuecell
Valuecell is a Python project for efficient data management.
tradingview-mcp
AI-assisted TradingView chart analysis — connect Claude Code to your...
tradingview-mcp
TradingView MCP Server offers real-time market analysis for crypto and stocks.