ftInvstr
Setup Guide · MCP Server v0.2

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.

endpoint: https://ftinvstr.in/mcp/

01 Get your API key

  1. Log in at ftinvstr.in (or sign up if you haven't).
  2. Go to Profile → API Keys.
  3. Click Generate Key, label it (e.g. "Claude Desktop · MacBook").
  4. Copy the plaintext key shown. Keys look like fti_aB3cD4eF5gH6iJ7…
The plaintext is shown only once. Store it somewhere safe before leaving the page. The server only keeps a hash — we cannot recover it.

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.

  1. Open claude.ai. Click your profile icon (top-right) → Settings.
  2. In the left sidebar, click Connectors (or Custom Integrations, depending on your account's UI version).
  3. Click Add custom connector.
  4. Fill in:
    • Name: ftInvstr
    • Remote MCP server URL: https://ftinvstr.in/mcp/
  5. Leave the OAuth Client ID and OAuth Client Secret fields empty under Advanced settings — our server uses Dynamic Client Registration, no manual provisioning.
  6. Click Add, then click Connect on the new entry.
  7. You'll be redirected to ftinvstr.in. Sign in if prompted, then click Allow access on the consent screen.
  8. claude.ai redirects you back. Open a new chat and click the 🔧 tools icon — you should see ftinvstr · 49 tools listed.

Test prompt:

"Use the ftinvstr tool — show me Indian equity strategies with Sharpe above 0.9."

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).

{ "mcpServers": { "ftinvstr": { "command": "npx", "args": [ "-y", "mcp-remote", "https://ftinvstr.in/mcp/", "--header", "Authorization:Bearer YOUR_KEY_HERE" ] } } }

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:

{ "mcpServers": { "ftinvstr": { "url": "https://ftinvstr.in/mcp/", "headers": { "Authorization": "Bearer YOUR_KEY_HERE" } } } }

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:

# Add the ftInvstr MCP server (user scope, available in every project) claude mcp add --transport http ftinvstr https://ftinvstr.in/mcp/ \ --header "Authorization: Bearer YOUR_KEY_HERE"

Confirm with claude mcp list. The tool becomes available immediately in the next claude session — no restart needed.

Edit ~/.codeium/windsurf/mcp_config.json:

{ "mcpServers": { "ftinvstr": { "serverUrl": "https://ftinvstr.in/mcp/", "headers": { "Authorization": "Bearer YOUR_KEY_HERE" } } } }

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)":

{ "github.copilot.chat.mcp.servers": { "ftinvstr": { "type": "http", "url": "https://ftinvstr.in/mcp/", "headers": { "Authorization": "Bearer YOUR_KEY_HERE" } } } }

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.

  1. Open Settings → Connectors → Add custom connector.
  2. Set Server URL to https://ftinvstr.in/mcp/
  3. Under Authentication, choose "Bearer token" and paste your fti_… key.
  4. 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.

# pip install mcp import asyncio from mcp import ClientSession from mcp.client.streamable_http import streamablehttp_client async def main(): headers = {"Authorization": "Bearer YOUR_KEY_HERE"} async with streamablehttp_client("https://ftinvstr.in/mcp/", headers=headers) as (r, w, _): async with ClientSession(r, w) as sess: await sess.initialize() tools = await sess.list_tools() print([t.name for t in tools.tools]) result = await sess.call_tool("search_strategies", {"min_cagr": 25, "min_sharpe": 0.8}) print(result.content[0].text) asyncio.run(main())

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.

# List all available tools curl -X POST https://ftinvstr.in/mcp/ \ -H "Authorization: Bearer YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' # Call a tool (search strategies with CAGR > 25%) curl -X POST https://ftinvstr.in/mcp/ \ -H "Authorization: Bearer YOUR_KEY_HERE" \ -H "Content-Type: application/json" \ -d '{ "jsonrpc":"2.0","id":2,"method":"tools/call", "params":{"name":"search_strategies","arguments":{"min_cagr":25}} }'

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:

"Use the ftinvstr tool to find Indian equity strategies with CAGR above 30% and Sharpe above 0.9, then show me their year-by-year returns."

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:

1. // agent learns the DSL once per session get_expression_help(topic="overview") get_expression_help(topic="fields") // 276 fields get_expression_help(topic="functions") // 36 operators get_expression_help(topic="examples") 2. // build expression locally expr = "order(normalize(rank('ROCE %')), 0.7, 0.3, 1)" 3. // fast syntax / field check — no queue burn validate_expression(expr) // → {ok: true} 4. // submit. Returns immediately with a job_id. submit_backtest( expression=expr, universe="NIFTY500", // top 500 by mkt cap start="2020-01-01", end="2025-12-31", investment_mode="max_position_legacy", rebalance_frequency="monthly", max_positions=10 ) // → {job_id, status, universe, stocks_count, eta_seconds} 5. // poll every ~30s. Returns queued / running / done / failed. get_backtest_status(job_id) 6. // when done, fetch full stats + curves get_backtest_result(job_id) // same shape as get_strategy_stats 7. // then audit it — every tool below also takes backtest_id=job_id get_trades(backtest_id=job_id, from="2024-01-01") // every fill, paginated get_equity_curve(backtest_id=job_id) // the curve behind the CAGR get_drawdown_periods(backtest_id=job_id) // worst episodes + recovery get_trade_pnl(backtest_id=job_id) // per-round-trip realized PnL clone_backtest(backtest_id=job_id, universe="NIFTY100") // rerun with one change // (optional) cancel mid-flight stop_backtest(job_id)

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."
Serialized per user. You can only have one backtest in flight at a time. If you submit a second while one is running, the call is rejected with the existing job_id so your agent knows what to poll (or cancel via stop_backtest).
Want to browse the DSL yourself? The same 276 fields and 36 operators your agent reads via 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.

QuestionTool 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.
Try it: "Audit strategy X: pick its last rebalance, show me the decision scorecard, which factor drove the top pick, what it skipped and why, and the friction charges on the fills." — five tool calls, full paper trail.

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)

ToolWhat it returns
list_strategiesList active public strategies on ftInvstr. Returns name, display_name, factor family, rebalance frequency, and any achievement badges (Best Sharpe / Lowest DD / Highest CAGR).
search_strategiesFilter the strategy catalog by metric thresholds. Returns strategies matching ALL provided filters. Omit a filter to leave it open.
get_strategy_metadataLightweight strategy card: display name, description, owner, active flag, universe, live-since date, version, visibility.
list_my_backtestsAll backtests YOU have submitted via MCP, newest first — durable history, not limited to the last 7 days.
get_trading_calendarNSE trading calendar: last completed trading day, trading days in a window, and the next N trading days (holidays excluded).
get_next_rebalance_dateWhen a strategy rebalances next (estimate from its frequency and the trading calendar) plus its last rebalance date.
list_factors_availableAll data fields usable inside expressions, tagged by family (price / fundamental / ml_prediction / sentiment / flows). Searchable.
get_expression_helpReference 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_expressionSyntax-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_statusPoll 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)

ToolWhat it returns
get_strategy_statsGet 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_holdingsGet 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_historyAggregate 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_tradesPaginated 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_dateAudit 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_curveEquity (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_returnsCalendar-month returns (bucket last value vs previous bucket last). Optional year filter.
get_yearly_returnsCalendar-year simple returns plus the compounded CAGR.
get_underwater_curveDrawdown-from-peak series in % (engine convention: (value-peak)/peak), plus max drawdown and current underwater depth.
get_drawdown_periodsDistinct peak-to-trough-to-recovery drawdown episodes, deepest first: peak/trough/recovery dates, depth %, length in days.
get_rolling_returnsTrailing-window return series (default 365 calendar days): best/worst window, share of positive windows, downsampled curve.
get_strategy_configThe 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_configExact submitted config of one of YOUR backtests — durable, survives the 7-day pointer expiry.
get_concentration_metricsPortfolio concentration right now: top-1/3/5/10 weight, HHI, effective number of positions (1/HHI).
get_sector_exposureCurrent portfolio weight by sector (Yahoo taxonomy).
get_market_cap_exposureCurrent portfolio weight by market-cap bucket: Large (top 100 by mcap rank), Mid (101-250), Small (251+).
get_trade_pnlRealized 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_statsHow long positions are held: avg/median days, winners vs losers hold time, open position count. FIFO basis.
get_turnoverMonthly portfolio turnover %: traded value / average equity. Includes annualized average.
get_holdings_historyPortfolio breadth over time (number of positions per snapshot date).
get_benchmark_comparisonStrategy vs NIFTY over a window, both rebased to 100: returns, CAGRs, alpha, and a sampled overlay curve.
get_alpha_vs_benchmarkYear-by-year table: strategy return, NIFTY return, alpha.
compare_strategiesSide-by-side summary stats for 2-4 active strategies.
get_similar_strategiesMost-correlated active strategies (calendar-month return correlation, last 24 months).
get_universe_membersList 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_historyRecent uninvested-cash series for a strategy (engine retains only the most recent run window for this series).
get_ml_predictions_usedFor ML-driven strategies: the latest model predictions for the current holdings, restricted to the predicted_* fields the expression actually uses.
get_rebalance_decisionThe 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_attributionWhy each fill happened: joins a fill date's trades to the prior evening's decision scorecard (score + rank at pick time).
get_skipped_tradesCandidates above the buy threshold that were NOT bought, with the inferred reason: already held, below position cap, or blocked at execution (cash/price).
get_eligibilityWas a specific stock considered on a date — and at what score and rank? Answers "why isn't X in the portfolio".
get_factor_contributionWhich 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_modesStructured 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_resultFetch 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)

ToolWhat it does
set_strategy_visibilityOWNER ONLY: toggle whether your strategy expression/config is public. Bumps the strategy version (audit trail).
activate_strategyOWNER 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_backtestResubmit one of your backtests with the exact stored config, optionally overriding expression / universe / dates / mode / frequency / max_positions. Returns a new job_id.
submit_backtestQueue 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_backtestCancel 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)

ModeBehaviour
max_position_legacyDefault. Long-only, equal-weight slot-based top-N with score-driven swaps. Matches what most platform strategies use.
equal_weight_top_nLong-only, strict equal weights across top-N each rebalance.
score_weighted_top_nLong-only, weights proportional to score within top-N.
daily_top_n_long_short50/50 long top-N vs short bottom-N.
beta_neutral_top_nLong-short scaled to net-zero beta vs NIFTY.
sector_neutral_top_nPaired long-short within each sector.
regime_switching_dynamicRegime-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_backtest while one is in flight is rejected with the existing job_id. Call stop_backtest to 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_seconds message. submit_backtest additionally consumes the daily quota above.

08 Troubleshooting

SymptomLikely cause / fix
401 UnauthorizedMissing or invalid Bearer header. Re-copy from /profile/api-keys/. Header is case-sensitive: Authorization: Bearer fti_....
Tools don't appear in Claude DesktopForgot to fully quit + reopen (Cmd+Q, not just close the window). Check ~/Library/Logs/Claude/mcp*.log on macOS.
command not found: npxNode.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 tradesMost 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 fieldField 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 listFilters 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_id in 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.
Ready to connect? Generate your first key →
Go to API Keys