Data Mcp — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Data Mcp (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A unified financial data library with an MCP server for code-writing agents.
When an agent queries the MCP (e.g. "equity daily prices"), it receives:
The MCP never fetches live data — it is a documentation server so agents can write correct calls to the findata library.
data-mcp/
├── findata/ Data library
│ ├── equity_prices.py get_equity_prices() yfinance wrapper
│ ├── sp500_composition.py get_sp500_composition() fja05680/sp500 (local git clone)
│ ├── fama_french.py get_fama_french_factors() Ken French Data Library
│ ├── fred.py get_fred_series() FRED macroeconomic series
│ ├── cboe_volatility.py get_cboe_volatility_indices() VIX / VVIX
│ ├── coingecko.py get_coingecko_ohlcv() CoinGecko public API
│ ├── file_reader.py get_file_data() CSV / Parquet / Excel
│ └── bloomberg.py get_bloomberg_ticks() blpapi — tick-by-tick
│ get_bloomberg_bars() blpapi — intraday OHLCV
│ get_bloomberg_data() blpapi — historical / reference
├── findata_mcp/
│ └── server.py Tool registry + MCP handlers
├── Dockerfile
├── docker-compose.yml
├── .github/workflows/docker.yml GHCR build + push on every push to main
├── pyproject.toml
└── README.mdThe recommended way to run findata-mcp is via Docker. The image is published to GHCR on every push to main and includes Codex CLI baked in.
codex auth login # opens browser → saves to ~/.codex/auth.jsoncurl -O https://raw.githubusercontent.com/lakshya-aga/data-mcp/main/docker-compose.yml
docker compose up -ddocker-compose.yml mounts ~/.codex read-only so the container inherits your Codex session with no interactive prompts. Named volumes keep generated files and data across restarts.
docker logs data-mcp-findata-mcp-1
# should show: findata-mcp starting on :8000Add to ~/Library/Application Support/Claude/claude_desktop_config.json:
{
"mcpServers": {
"findata": {
"url": "http://localhost:8000/sse"
}
}
}import asyncio
from mcp.client.sse import sse_client
from mcp.client.session import ClientSession
async def main():
async with sse_client("http://localhost:8000/sse") as (r, w):
async with ClientSession(r, w) as s:
await s.initialize()
res = await s.call_tool("search_tools", {"query": "equity daily prices", "top_k": 3})
print(res.content[0].text)
asyncio.run(main())from agents.mcp import MCPServerSse
mcp = MCPServerSse(url="http://localhost:8000/sse")| Tool | Description |
|---|---|
search_tools | Natural-language query → matching function docs + code examples |
get_tool_doc | Full reference for one function by exact name |
list_all_tools | All wrapper functions with summaries and tags |
request_data_source | Ask Codex to implement and register a new data wrapper |
res = await s.call_tool("search_tools", {"query": "fama french factors", "top_k": 2})res = await s.call_tool("get_tool_doc", {"tool_name": "get_equity_prices"})res = await s.call_tool("request_data_source", {
"description": "get World Bank GDP per capita using the wbdata library"
})Codex writes findata/<module>.py, updates server.py, and hot-reloads the new function into the live registry — no restart needed.
| Variable | Description |
|---|---|
OPENAI_API_KEY | Codex auth — skips OAuth if set (alternative to host auth mount) |
FRED_API_KEY | Required for get_fred_series. Free at fred.stlouisfed.org |
CODEX_CLI_PATH | Override Codex binary path (defaults to codex on PATH) |
BLOOMBERG_HOST | Bloomberg API host for the get_bloomberg_* functions (default localhost) |
BLOOMBERG_PORT | Bloomberg API port (default 8194) |
BLOOMBERG_TIMEOUT_MS | Per-event wait before a Bloomberg request is abandoned (default 30000) |
BLOOMBERG_AUTH | setAuthenticationOptions string for Server API / B-PIPE, e.g. AuthenticationType=OS_LOGON. Leave unset for a desktop Terminal |
get_equity_pricesfrom findata.equity_prices import get_equity_prices
df = get_equity_prices(
tickers=["AAPL", "MSFT"],
start_date="2024-01-01",
end_date="2024-12-31",
fields=["Close"],
frequency="1d", # 1d 5d 1wk 1mo 3mo
)get_fama_french_factorsfrom findata.fama_french import get_fama_french_factors
df = get_fama_french_factors(factor_model="5", start_date="2010-01-01", end_date="2020-12-31")
# columns: Mkt-RF, SMB, HML, RMW, CMA, RFget_fred_seriesfrom findata.fred import get_fred_series
df = get_fred_series(["CPIAUCSL", "UNRATE"], start_date="2015-01-01", end_date="2024-12-31")get_coingecko_ohlcvfrom findata.coingecko import get_coingecko_ohlcv
df = get_coingecko_ohlcv("bitcoin", vs_currency="usd", days=90)
# columns: open, high, low, close, volumeget_cboe_volatility_indicesfrom findata.cboe_volatility import get_cboe_volatility_indices
df = get_cboe_volatility_indices(symbols=["^VIX", "^VVIX"], start_date="2020-01-01", end_date="2024-12-31")get_sp500_compositionfrom findata.sp500_composition import get_sp500_composition
members = get_sp500_composition("2024-12-31") # list[str], ~503 tickersget_bloomberg_ticks — tick-level data for any securityRequires a Bloomberg Terminal, SAPI or B-PIPE entitlement and the SDK:
pip install blpapi --index-url https://bcms.bloomberg.com/pip/simple/from findata.bloomberg import get_bloomberg_ticks
# Every trade print in one US cash session, in New York time
ticks = get_bloomberg_ticks(
"AAPL US Equity",
"2024-06-03 09:30:00",
"2024-06-03 16:00:00",
tz="America/New_York",
)
# index = time (tz-aware)
# columns: security, type, value, size, conditionCodes, exchangeCode
# Trades AND top-of-book quotes, one hour per sub-request
book = get_bloomberg_ticks(
"ESZ5 Index",
"2024-06-03 13:30:00",
"2024-06-03 20:00:00",
event_types=["TRADE", "BID", "ASK"],
chunk="1h",
)
# VWAP straight from the prints
trades = ticks[ticks["type"] == "TRADE"]
vwap = (trades["value"] * trades["size"]).sum() / trades["size"].sum()The security string is passed to Bloomberg verbatim, so anything the Terminal quotes works — "AAPL US Equity", "ESZ5 Index", "EURUSD Curncy", "TY1 Comdty", "US912810TW33 Govt".
Long windows are sliced into chunk-sized sub-requests (default one day) and concatenated, because Bloomberg truncates an oversized intraday response silently rather than erroring. Bloomberg's own limits still apply: intraday tick history reaches back roughly 140 days, and daily volume caps are tied to your entitlement.
Reuse one session when looping over a universe — it avoids a connection handshake per name:
from findata.bloomberg import BloombergSession, get_bloomberg_ticks
with BloombergSession() as bbg:
frames = {
sym: get_bloomberg_ticks(sym, start, end, session=bbg)
for sym in universe
}Aggregate ticks into bars locally, at any pandas offset:
from findata.bloomberg import ticks_to_bars
bars = ticks_to_bars(ticks, rule="5min")
# columns: open, high, low, close, ticks, volume, vwapget_bloomberg_barsfrom findata.bloomberg import get_bloomberg_bars
# 5-minute bars, aggregated server-side (much lighter than raw ticks)
bars = get_bloomberg_bars(
"AAPL US Equity",
"2024-06-03 09:30:00",
"2024-06-03 16:00:00",
interval=5, # minutes, 1-1440
tz="America/New_York",
)
# columns: security, open, high, low, close, volume, numEvents, valueget_bloomberg_datafrom findata.bloomberg import get_bloomberg_data
# Historical daily series -> MultiIndex columns (field, security)
df = get_bloomberg_data(
tickers=["AAPL US Equity", "MSFT US Equity"],
fields=["PX_LAST", "VOLUME"],
start_date="2024-01-01",
end_date="2024-12-31",
)
close = df["PX_LAST"]
# Reference data -> rows=securities, cols=fields
ref = get_bloomberg_data(
tickers=["AAPL US Equity"],
fields=["CUR_MKT_CAP", "GICS_SECTOR_NAME"],
request_type="ReferenceDataRequest",
overrides={"BEST_FPERIOD_OVERRIDE": "1BF"},
)get_file_datafrom findata.file_reader import get_file_data
df = get_file_data("data/prices.parquet", tickers=["AAPL"], start_date="2023-01-01", end_date="2023-12-31")pytest tests/ -v~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.