Connect your AI agent to ftInvstr
ftInvstr speaks the Model Context Protocol — Anthropic's open standard for letting AI agents discover and call tools on remote servers. Connect Claude, Cursor, Claude Code, Windsurf, VS Code, ChatGPT, or any Python agent — they all consume the same endpoint and the same 49 tools. Once connected, your AI can browse 60+ Indian equity strategies, fetch full stats, design and run real backtests end-to-end, and cancel jobs mid-flight.
01 Get your API key
- Log in at ftinvstr.in (or sign up if you haven't).
- Go to Profile → API Keys.
- Click Generate Key, label it (e.g. "Claude Desktop · MacBook").
- Copy the plaintext key shown. Keys look like
fti_aB3cD4eF5gH6iJ7…
02 Configure your AI client
Pick your client below — same endpoint, same Bearer header, format varies. After pasting the snippet, replace YOUR_KEY_HERE with the key from step 1.
The easiest setup — no API key, no JSON config, no Node.js. claude.ai handles the auth handshake automatically via OAuth.
- Open claude.ai. Click your profile icon (top-right) → Settings.
- In the left sidebar, click Connectors (or Custom Integrations, depending on your account's UI version).
- Click Add custom connector.
- Fill in:
- Name:
ftInvstr - Remote MCP server URL:
https://ftinvstr.in/mcp/
- Name:
- Leave the OAuth Client ID and OAuth Client Secret fields empty under Advanced settings — our server uses Dynamic Client Registration, no manual provisioning.
- Click Add, then click Connect on the new entry.
- You'll be redirected to ftinvstr.in. Sign in if prompted, then click Allow access on the consent screen.
- claude.ai redirects you back. Open a new chat and click the 🔧 tools icon — you should see ftinvstr · 49 tools listed.
Test prompt:
To revoke access later: in claude.ai → Settings → Connectors → remove the ftInvstr entry. That deletes the connector locally; the matching token on our side will simply expire (or you can also email us to force-revoke).
Edit your Claude Desktop config file (create it if missing):
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
Claude Desktop only speaks stdio. Use the mcp-remote bridge to reach our HTTPS endpoint. Requires Node.js (brew install node or nodejs.org).
Fully quit Claude Desktop (Cmd+Q / right-click tray → Quit, not just close the window) and reopen. The 🔧 tools icon should now list ftinvstr with 49 tools.
Open Settings → MCP, or edit ~/.cursor/mcp.json directly:
Cursor speaks HTTP natively for remote MCP servers, so no Node bridge needed. Restart Cursor; the tool appears in the agent's tool list.
One-shot CLI command from any terminal:
Confirm with claude mcp list. The tool becomes available immediately in the next claude session — no restart needed.
Edit ~/.codeium/windsurf/mcp_config.json:
Restart Windsurf. The Cascade panel will list ftinvstr alongside other configured servers.
VS Code's GitHub Copilot Chat (1.96+) supports MCP via settings.json. Open Command Palette → "Preferences: Open User Settings (JSON)":
Reload window (Cmd+Shift+P → "Developer: Reload Window"). Copilot Chat agent mode will show ftinvstr tools.
ChatGPT supports remote MCP servers via the Connectors UI.
- Open Settings → Connectors → Add custom connector.
- Set Server URL to
https://ftinvstr.in/mcp/ - Under Authentication, choose "Bearer token" and paste your
fti_…key. - Save and enable the connector for the conversations where you want it.
The official mcp Python SDK works with any HTTP MCP server. Compatible with OpenAI Agents SDK, LangChain MCP adapter, and custom agents.
For OpenAI Agents SDK: pass the same headers when creating the MCP server connection (see openai-agents-python).
For LangChain: use langchain-mcp-adapters with the same URL + Bearer header.
Any HTTP client speaks JSON-RPC 2.0 directly. Useful for debugging or custom integrations.
Required protocol: MCP spec 2024-11-05, Streamable HTTP transport, JSON-RPC 2.0 framing. Initial initialize handshake is required before tools/call.
03 Verify the connection
In a new chat with your AI client, ask it to list its tools or run this prompt:
The agent should make two tool calls (search_strategies then get_strategy_stats) and return a structured answer in seconds.
04 Designing & running a backtest via your agent
Backtest submissions use an async pattern — your agent submits a job, polls for status, and fetches the result when done. Typical flow:
Example prompts that exercise the whole flow:
- "Design a quality-plus-momentum strategy on NIFTY500, validate it, run a 2020–2025 monthly backtest, and when it's done show me year-by-year returns vs NIFTY."
- "Take my last backtest, list its 10 worst trades with days held, then clone it with max_positions=20 and compare."
- "Show the drawdown periods of my backtest and tell me how long each took to recover."
job_id so your agent knows what to poll (or cancel via stop_backtest).
get_expression_help are listed (with descriptions, examples, and field categories) at /function-docs/. Useful while you're sketching an expression before letting the agent run with it.
05 Full transparency — audit everything the model did
ftInvstr's MCP is built transparency-first: every piece of information behind a backtest or strategy is queryable. Nothing is a black box — your agent can reconstruct and verify every number it is shown.
| Question | Tool that answers it |
|---|---|
| "Show me every trade it made, with prices and charges." | get_trades / get_trades_on_date — actual fills incl. STT, exchange, DP, stamp, GST per day. |
| "Why did it buy this stock that day?" | get_rebalance_decision + get_trade_attribution — the persisted scorecard (score, rank, threshold) the model acted on, never recomputed. |
| "Which factor drove the pick — momentum or quality?" | get_factor_contribution — per-term factor scores captured at decision time, with share %. |
| "What did it consider but NOT buy, and why?" | get_skipped_trades / get_eligibility — above-threshold non-buys with the reason (held / position cap / cash). |
| "Is this number reproducible?" | Every trade and decision is stamped with the data_version, engine_version and strategy_version it was produced under. |
| "How concentrated / risky is it right now?" | get_concentration_metrics, get_sector_exposure, get_market_cap_exposure, get_turnover, get_holding_period_stats. |
More things you can simply ask your AI:
- "Which strategies have CAGR above 25% with drawdown under 30%? Compare the top three side by side."
- "What does strategy X hold right now, how concentrated is it, and what's its sector split?"
- "Show X's monthly returns for 2025 and its three worst drawdowns ever."
- "How did X do against NIFTY over the last year — and what's the alpha year by year?"
- "Was Reliance ever considered by strategy X? At what rank, and why isn't it in the portfolio?"
- "Find strategies most correlated to X, and tell me when X rebalances next."
- "List every trade X made in May with the exact friction charges, and verify the fill prices were that day's open."
06 The 49 tools your agent can call
Generated live from the server's tool registry — this list is always current. Every response carries a scope / data / meta envelope with the engine version, truncation flag, and drill-down hints for what to call next.
Discovery & light reads (rate limit: 600 calls/hour per API key)
| Tool | What it returns |
|---|---|
| list_strategies | List active public strategies on ftInvstr. Returns name, display_name, factor family, rebalance frequency, and any achievement badges (Best Sharpe / Lowest DD / Highest CAGR). |
| search_strategies | Filter the strategy catalog by metric thresholds. Returns strategies matching ALL provided filters. Omit a filter to leave it open. |
| get_strategy_metadata | Lightweight strategy card: display name, description, owner, active flag, universe, live-since date, version, visibility. |
| list_my_backtests | All backtests YOU have submitted via MCP, newest first — durable history, not limited to the last 7 days. |
| get_trading_calendar | NSE trading calendar: last completed trading day, trading days in a window, and the next N trading days (holidays excluded). |
| get_next_rebalance_date | When a strategy rebalances next (estimate from its frequency and the trading calendar) plus its last rebalance date. |
| list_factors_available | All data fields usable inside expressions, tagged by family (price / fundamental / ml_prediction / sentiment / flows). Searchable. |
| get_expression_help | Reference content for the ftInvstr expression DSL. Call this before building expressions. Topics: "overview" (DSL shape + canonical example), "fields" (all data fields you can rank on), "functions" (all operators + signatures), "functions:<name>" (detail for one operator, e.g. functions:rank), "examples" (4 working expressions). |
| validate_expression | Syntax-check an expression without queuing a backtest. Returns {ok: true} if the expression parses + all fields are known, or {ok: false, error: "..."} with a specific reason. Cheap — use before every submit_backtest. |
| get_backtest_status | Poll a submitted backtest. Returns {status: queued|running|done|failed, progress?: {description, percent}}. Backtests typically take 15-30 minutes. Poll every 30 seconds. |
Deep analytics — trades, performance, risk, decisions (rate limit: 300 calls/hour per API key)
| Tool | What it returns |
|---|---|
| get_strategy_stats | Get full backtest stats for one strategy. Returns CAGR, Sharpe, Sortino, Calmar, max drawdown, total return, trade count, year-by-year, plus equity curve (NAV indexed to 100), drawdown curve, and monthly returns when include_curves=true (default). |
| get_holdings | Get the actual portfolio held by a strategy right now — list of positions with ticker, company name, shares held, latest price, market value, and weight % of portfolio. |
| get_rebalance_history | Aggregate rebalance activity for one strategy since it became active. Returns date + buy/sell counts per rebalance — NO specific tickers (preserves strategy IP). Optional `days` arg narrows to the most recent N days. Capped at 1000 rebalances. |
| get_trades | Paginated log of every trade the model actually executed (fills, not intentions): date, company, side, shares, price, value. Filter by date window / ticker / side. Newest first. |
| get_trades_on_date | Audit one day: every fill on that date plus the actual friction charges applied (STT, exchange, DP, stamp, GST). Returns buys/sells with company names, turnover, charges. |
| get_equity_curve | Equity (portfolio value) time series with period return and CAGR. Downsampled to max_points; meta.payload_truncated tells you when. Same numbers as the strategy page chart. |
| get_monthly_returns | Calendar-month returns (bucket last value vs previous bucket last). Optional year filter. |
| get_yearly_returns | Calendar-year simple returns plus the compounded CAGR. |
| get_underwater_curve | Drawdown-from-peak series in % (engine convention: (value-peak)/peak), plus max drawdown and current underwater depth. |
| get_drawdown_periods | Distinct peak-to-trough-to-recovery drawdown episodes, deepest first: peak/trough/recovery dates, depth %, length in days. |
| get_rolling_returns | Trailing-window return series (default 365 calendar days): best/worst window, share of positive windows, downsampled curve. |
| get_strategy_config | The strategy expression + run config (universe, mode, rebalance frequency, stops). Owner always; others only if the owner left settings public — else a clear denial. |
| get_backtest_config | Exact submitted config of one of YOUR backtests — durable, survives the 7-day pointer expiry. |
| get_concentration_metrics | Portfolio concentration right now: top-1/3/5/10 weight, HHI, effective number of positions (1/HHI). |
| get_sector_exposure | Current portfolio weight by sector (Yahoo taxonomy). |
| get_market_cap_exposure | Current portfolio weight by market-cap bucket: Large (top 100 by mcap rank), Mid (101-250), Small (251+). |
| get_trade_pnl | Realized PnL per closed round-trip (FIFO lot matching): entry/exit dates+prices, pnl, % return, days held. Summary: total realized, win rate, avg win/loss %. |
| get_holding_period_stats | How long positions are held: avg/median days, winners vs losers hold time, open position count. FIFO basis. |
| get_turnover | Monthly portfolio turnover %: traded value / average equity. Includes annualized average. |
| get_holdings_history | Portfolio breadth over time (number of positions per snapshot date). |
| get_benchmark_comparison | Strategy vs NIFTY over a window, both rebased to 100: returns, CAGRs, alpha, and a sampled overlay curve. |
| get_alpha_vs_benchmark | Year-by-year table: strategy return, NIFTY return, alpha. |
| compare_strategies | Side-by-side summary stats for 2-4 active strategies. |
| get_similar_strategies | Most-correlated active strategies (calendar-month return correlation, last 24 months). |
| get_universe_members | List members of a universe. Friendly labels (NIFTY100/500/...) are top-N slices by latest market-cap rank; engine universe names list the actual collection. |
| get_cash_balance_history | Recent uninvested-cash series for a strategy (engine retains only the most recent run window for this series). |
| get_ml_predictions_used | For ML-driven strategies: the latest model predictions for the current holdings, restricted to the predicted_* fields the expression actually uses. |
| get_rebalance_decision | The ranked scorecard the model actually acted on for one evening (persisted at decision time, never recomputed): scores, ranks, held flags, thresholds, version stamps. |
| get_trade_attribution | Why each fill happened: joins a fill date's trades to the prior evening's decision scorecard (score + rank at pick time). |
| get_skipped_trades | Candidates above the buy threshold that were NOT bought, with the inferred reason: already held, below position cap, or blocked at execution (cash/price). |
| get_eligibility | Was a specific stock considered on a date — and at what score and rank? Answers "why isn't X in the portfolio". |
| get_factor_contribution | Which factor drove each pick: per-term scores captured at decision time for multi-factor expressions (e.g. momentum term vs quality term inside add(...)), with share %. |
| get_investment_modes | Structured spec of every backtest execution mode: the extra parameters each investment_mode accepts, their defaults, valid ranges, and (for regime params) when they apply. Call this before submit_backtest to choose a mode and its settings — modes not listed here cannot be run, and params not listed for a mode are ignored or rejected. Returns {common_parameters, modes:[...]}. |
| get_backtest_result | Fetch full stats for a completed backtest. Same shape as get_strategy_stats — CAGR / Sharpe / DD / year-by-year + equity curve + drawdown curve + monthly returns. Returns an error if the job is not yet done. |
Backtest workflow & writes (rate limit: 60 calls/hour per API key)
| Tool | What it does |
|---|---|
| set_strategy_visibility | OWNER ONLY: toggle whether your strategy expression/config is public. Bumps the strategy version (audit trail). |
| activate_strategy | OWNER ONLY: activate your strategy. dashboard_visibility=false (default true) = "My Strategy only": goes live instantly, private and off-dashboard, no review. true = request public dashboard listing via admin review. |
| clone_backtest | Resubmit one of your backtests with the exact stored config, optionally overriding expression / universe / dates / mode / frequency / max_positions. Returns a new job_id. |
| submit_backtest | Queue a real backtest. Returns {job_id, status: "queued", eta_seconds}. Poll get_backtest_status(job_id) until status="done", then call get_backtest_result(job_id). Limits: 1 concurrent job per user (serialized); free tier = 5 backtests/month, Pro = unlimited; expression ≤ 5000 chars. start_date ≥ 2018-01-01 because fundamentals data (qr, pl, bl, cf, rto and ML predictions derived from them) is sparse before 2018. Pure technical / price-based expressions (Close, Volume, momentum, beta) work back to 2015 but the same floor still applies until per-expression date relaxation ships. |
| stop_backtest | Cancel an in-flight backtest you submitted. Revokes the underlying Celery task (SIGTERM to the worker child) and cleans up the partial strategy DB + CONFIG file. Returns {stopped: true} on success or an error if the job is unknown, already terminal, or belongs to another user. Idempotent. |
Investment modes (passed to submit_backtest)
| Mode | Behaviour |
|---|---|
| max_position_legacy | Default. Long-only, equal-weight slot-based top-N with score-driven swaps. Matches what most platform strategies use. |
| equal_weight_top_n | Long-only, strict equal weights across top-N each rebalance. |
| score_weighted_top_n | Long-only, weights proportional to score within top-N. |
| daily_top_n_long_short | 50/50 long top-N vs short bottom-N. |
| beta_neutral_top_n | Long-short scaled to net-zero beta vs NIFTY. |
| sector_neutral_top_n | Paired long-short within each sector. |
| regime_switching_dynamic | Regime-aware: long-only in bull/sideways, neutral or short in bear. |
07 Limits & safety
- 10 submit_backtest calls per day, per user. Resets at midnight UTC. Counter is per-user, shared across all your API keys.
- One concurrent backtest per user. A second
submit_backtestwhile one is in flight is rejected with the existingjob_id. Callstop_backtestto free the slot or wait it out. - Expression length cap: 5000 characters.
- Universes: seven friendly index labels —
NIFTY50,NIFTY100(default),NIFTY200,NIFTY500,NIFTY1000,NIFTY2000,NIFTYALL. Each = top-N of the broad listed pool ranked by market cap. Same expression across NIFTY50 vs NIFTY500 vs NIFTYALL gives you small-cap vs mid-cap vs full-market behaviour. - Date range:
start_date ≥ 2018-01-01. This floor exists because fundamentals data (qr, pl, bl, cf, rto and ML predictions derived from them) is sparse before 2018. Technical / price-only strategies (Close, Volume, momentum operators, beta) work back to 2015 — let us know if you need an earlier start for a pure-technical expression. - Per-category hourly caps: light reads 600/h, deep analytics 300/h, writes 60/h — over-limit calls get a clean
retry_after_secondsmessage.submit_backtestadditionally consumes the daily quota above.
08 Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
| 401 Unauthorized | Missing or invalid Bearer header. Re-copy from /profile/api-keys/. Header is case-sensitive: Authorization: Bearer fti_.... |
| Tools don't appear in Claude Desktop | Forgot to fully quit + reopen (Cmd+Q, not just close the window). Check ~/Library/Logs/Claude/mcp*.log on macOS. |
| command not found: npx | Node.js not installed locally. Install from nodejs.org or brew install node on macOS. |
| "You already have a backtest in flight" | Serialize gate — one job per user at a time. Poll get_backtest_status on the returned existing_job_id until done, or call stop_backtest. |
| "daily submit limit reached" | Hit the 10/day cap. Resets at midnight UTC. Read tools still work. |
| Backtest finished with 0 trades | Most common cause: a hard binary filter inside multiply(score, gt(...)) or multiply(score, lt(...)) zeroes out every stock because the gated field is sparse. Use the field as a weighted factor (multiply(rank('field'), 0.3)) inside an add(...) instead of as a hard gate. |
| "unknown or expired job_id" | Job records have a 7-day TTL in Redis. After that, results are only fetchable via list_strategies if the strategy was kept. |
| validate_expression rejects a field | Field name must match the catalog exactly. Call get_expression_help(topic="fields") or browse /function-docs/ for the full list. Field names are case-sensitive and use single quotes. |
| Tool returns empty list | Filters too strict. Try list_strategies() with no arguments first to confirm connectivity. |
| "Cannot reach server" | Corporate firewall blocks ftinvstr.in. Try a different network or whitelist the host. |
09 Security & disclaimers
- Treat keys like passwords. Anyone with the key can submit backtests as you and read your account.
- Use one key per AI client / device so you can revoke selectively. The "Last used" timestamp helps you spot stale or compromised keys.
- If a key leaks, delete it immediately from /profile/api-keys/.
- Expressions you submit and the resulting CONFIG / strategy_name labels are tied to your
user_idin Redis. Other users cannot see, status, result, or stop your jobs. - ftInvstr is a quantitative research tool — not investment advice. We are not SEBI-registered investment advisers.
- All results are historical backtests on Indian equity data. Past performance does not predict future returns. The bias-safe fundamentals layer (lagged by reporting filings) is always enforced — you can't accidentally peek into the future.