feat: export, technical, DCF-sensitivity and multi-compare tools - #19
Merged
Conversation
Add five MCP tools (23 -> 28), each with a typed model, a manifest entry and offline tests. technical_snapshot gives the price/momentum backdrop a fundamentals tool usually lacks: 50/200-day moving averages and any recent golden/death cross, RSI(14) with Wilder's smoothing (not a simple mean), annualized volatility, one-year max drawdown, beta vs the market index over date-aligned returns, and where the price sits in its 52-week range. It is framed as context, not a signal -- the model docstring and tool description say so, and it emits no buy/sell language -- because a technical readout inside a fundamentals tool is easy for a host LLM to over-read. It is backed by a new sources/yahoo.get_history that follows the file's existing cache/ratelimit conventions and hands out a copy so a caller adding indicator columns can't mutate the cached frame. The analysis is a pure function of a price frame, so it unit-tests offline against synthetic series. dcf_sensitivity shows how much a DCF rests on its two key assumptions: intrinsic value across a discount-rate x terminal-growth grid, plus the break-even growth the market is implying at today's price (found by bisection, since there is no closed form for a two-stage-plus-Gordon model). The trap here is an N+1 storm -- 25 grid cells each calling compute_dcf while get_financials is uncached -- so info/financials/ratios are fetched once and threaded through every call; a test asserts the single statement fetch. compare_companies runs a head-to-head across 2-6 arbitrary tickers rather than a curated group, answering "compare KPIT with Tata Elxsi and Tata Tech" directly. It reuses peers._peer_row for the same currency normalisation, dedupes preserving order, drops unresolvable rows, and names its revenue share as set-relative rather than calling it market share. peer_group_directory lists the curated groups so a client can see how companies are grouped and why. export_report renders a full analysis to an HTML/PDF file via the RFC 3 engine and is the one non-read-only tool (annotated readOnlyHint=False; the server docstring notes the exception). Its path is LLM-controlled, so _safe_export_path confines it to the export directory: anything that resolves outside -- a .. traversal or an absolute path on either OS -- is rejected outright rather than clamped, so the behaviour is identical on Windows and POSIX. Verified end to end by producing a real PDF through the tool, and the manifest-parity test pins the tool list against the server so the two can't drift.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds five MCP tools (23 → 28). Fourth and final RFC; builds on #13 (peers/DCF) and #15 (
export.py), both merged.The tools
technical_snapshotdcf_sensitivitycompare_companiespeer_group_directoryexport_reportThe three things worth reviewing
export_reportis the sharp edge. It's the first tool that writes a file (soserver.py's "every tool is read-only" is now scoped), and itspathis LLM-controlled._safe_export_pathconfines it to the export dir and rejects any path that resolves outside — a..traversal or an absolute path — rather than silently clamping. I hit and fixed a real cross-platform bug here mid-build:Path("/etc/shadow").is_absolute()isFalseon Windows, so my first dual-policy version behaved differently per-OS. Reject-on-escape is uniform and there's a parametrized test across both traversal styles.RSI is Wilder's smoothing, not a rolling mean — pinned by a hand-computed value against the canonical worked example (≈70.53), so a future "simplification" to a rolling mean would fail. RSI of a pure rally is 100 (the divide-by-zero guard), of a pure selloff ~0.
The N+1 guard in
dcf_sensitivity. A 5×5 grid callscompute_dcf25 times andget_financialsis uncached, soinfo/financials/ratiosare fetched once and threaded through every call. A test assertsget_financialsis called exactly once — the deterministic form of a performance test, since wall-clock targets can't run in an offline suite.Verification
ruff+mypyclean; 263 tests pass (was 223). Every new test is offline:technicalagainst synthetic price frames (including engineered golden/death-cross series),sensitivityandmulti_compareagainst a stubbed data facade,server_toolsfor registration + the sandbox + manifest parity (the manifest tool list must equal the registered tools — this repo has drifted there before).End-to-end on live data:
export_report("KPIT", "pdf")through the MCP tool → a real 414 KB%PDF-via headless Chrome, written inside the sandboxed export dir.dcf_sensitivity("KPITTECH.NS")→ 5×5 grid, monotonic (₹1149→₹517 as the discount rate rises), market implying 5.56% growth at ₹554.compare_companies(["KPITTECH.NS","TATAELXSI.NS","TATATECH.NS"])→ Tata Tech largest & fastest-growing, Tata Elxsi highest-margin, KPIT cheapest P/E and best ROE.No breaking changes
All additive: five new tools, new models with defaults,
get_historyadded to the Yahoo source. Every prior tool stays read-only.