> For the complete documentation index, see [llms.txt](https://docs.openalgo.in/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openalgo.in/change-log/release/version-2.0.1.0-released.md).

# Version 2.0.1.0 Released

**Date: 3rd May 2026**

**Major Feature Release: Remote MCP — Self-Hosted OAuth 2.1 + MCP HTTP/SSE Server for ChatGPT, Claude.ai, and Claude Mobile, Plus Per-Purpose 2FA Enforcement, Symbol Search Expansion, Admin Diagnostics, and Zerodha NCO/GLOBAL\_INDEX Support**

This is the biggest release in the 2.0.x line, covering **20+ commits** since v2.0.0.9. The headline change is **Remote MCP** — a self-hosted OAuth 2.1 + Model Context Protocol HTTP/SSE transport that lets hosted AI clients (ChatGPT.com, Claude.ai, Claude iOS / Android) connect to your OpenAlgo install over HTTPS with the same 40 tools the local stdio MCP already exposes. The local stdio MCP (Claude Desktop, Cursor, Windsurf) is **untouched** — Remote MCP is a parallel, opt-in transport, off by default, that ships behind two enabler scripts (one for native Ubuntu, one for Docker) and a full admin operations console at `/admin/remote-mcp`. Alongside Remote MCP, this release lands per-purpose TOTP enforcement (login / MCP / password reset), a multi-exchange Symbol Search expansion (issue #1326), an admin Diagnostics page with downloadable system reports, and Zerodha NCO + GLOBAL\_INDEX exchange support.

***

**Highlights**

* **Remote MCP — Hosted AI Clients via OAuth** — Brand-new `/mcp` HTTP/SSE transport with full OAuth 2.1 + PKCE, Dynamic Client Registration (DCR), JWKS-published RS256 JWTs, refresh-token rotation with reuse-detection family revocation, and per-token-per-scope rate limits. ChatGPT-compatible discovery (`/.well-known/oauth-authorization-server`, `/.well-known/oauth-protected-resource`), claude.ai-compatible scope flow. Off by default; turned on by `install/enable-remote-mcp.sh` (native) or `install/enable-remote-mcp-docker.sh` (Docker).
* **Per-purpose 2FA enforcement** — TOTP can now be required independently for **dashboard login**, **MCP authorization (`write:orders` consent)**, and **password reset** — set the master switch in Profile → TOTP, then pick which purposes apply. Saving requires a fresh TOTP code in the same request to prove authenticator access for both enabling and disabling.
* **Admin → Remote MCP operations console** — Approve / revoke DCR clients, browse the full MCP tool-call audit log (`log/mcp.jsonl`) by tool / scope / outcome, and a one-click **Kill switch** that atomically revokes every refresh token across every approved client.
* **Symbol Search rewrite (#1326)** — Multi-exchange + multi-instrumenttype filtering, CSV download, AmiBroker / TradingView / Python / Excel copy formats, lifted the 500-row hard cap, exchange-only browse mode, and per-user search history.
* **Admin Diagnostics page** — `/admin/diagnostics` shows live system info (Python / Flask versions, DB sizes, broker session state), a paginated error browser over `log/errors.jsonl`, and a one-click downloadable diagnostic bundle (env-redacted) for support tickets.
* **Zerodha NCO + GLOBAL\_INDEX exchanges** — NCO (NSE Commodities) and GLOBAL\_INDEX (US30 / JAPAN225 / HANGSENG, plus GIFTNIFTY from NSE IFSC) now route correctly through the master contract download and quote endpoints. Quote-only on GLOBAL\_INDEX (no orders by exchange convention).
* **"Virtual / Paper" → "Sandbox" rename** — All in-product copy, docs, and SDK examples now uniformly say *Sandbox* (the database, blueprint, and API endpoint names were already `sandbox` — only display strings were inconsistent).
* **Platform version bump** — `2.0.0.9` → `2.0.1.0`. `.sample.env` `ENV_CONFIG_VERSION` `1.0.6` → `1.0.7`. SDK pin (`openalgo==1.0.49`) unchanged.

***

**Remote MCP — feature deep dive**

Remote MCP brings the OpenAlgo MCP toolset to **hosted AI clients** over the public internet. Same 40 tools as the local stdio integration, exposed at `https://yourdomain.com/mcp` with full OAuth 2.1.

**Architecture in one sentence:** the hosted client (ChatGPT / Claude.ai) only ever holds an OAuth Bearer JWT signed by your server's RS256 keypair; tool dispatch on the server side reuses your existing `/api/v1/*` API key (looked up server-side at boot) over loopback. The hosted client never sees your API key or your broker tokens.

**OAuth foundation (Phase 2a + 2c + 2d)** — `e86cf450f`, `0f4f71f0`-derived family, `cb9350f27`:

* RS256 keypair generated and rotated via `utils/oauth_keys.py`; public set published at `/oauth/jwks.json`
* Dynamic Client Registration at `/oauth/register` (RFC 7591) — clients land in a *pending* bucket, gated behind admin approval (default `MCP_OAUTH_REQUIRE_APPROVAL=True`)
* Authorization Code + PKCE-S256 only — `plain` is not advertised; `alg=none` and `alg=HS256` JWTs are rejected by the verifier
* Refresh-token rotation with **family revocation** (RFC 6749 §10.4): single-use, atomic `UPDATE ... WHERE revoked_at IS NULL`, replay of a revoked token revokes the entire family
* `/oauth/revoke` for explicit token retirement, `/oauth/token` for code-exchange and refresh
* Argon2 hashing with `API_KEY_PEPPER` for client\_secret + refresh-token storage in `database/oauth_db.py`

**Per-purpose 2FA (Phase 2b + Phase 2 UI)** — `dbc595e88`, `ff44adf46`:

* Four new boolean columns on `users`: `totp_enabled` + `totp_required_for_login` + `totp_required_for_mcp` + `totp_required_for_password_reset`
* `is_totp_required_for(purpose)` helper centralizes the gating decision
* Login flow: POST `/auth/login` → on TOTP requirement, returns `totp_required` flag + temp ticket → POST `/auth/login/totp` with code
* Configure flow at `/auth/2fa/configure` requires a fresh TOTP in the same request (proves authenticator access for *both* enabling and disabling — closes the "stolen session can disable 2FA" hole)
* React UI: `TwoFactorEnforcement.tsx` profile-page toggle, `Login.tsx` TOTP step, `ResetPassword.tsx` TOTP path

**HTTP transport (Phase 3)** — `7d805af15`:

* JSON-RPC 2.0 dispatcher at `/mcp` (POST) + Server-Sent Events transport for streaming
* Per-token-per-scope sliding-window rate limits (`MCP_RATE_LIMIT_READ` / `MCP_RATE_LIMIT_WRITE`)
* CORS allowlist (default: `https://claude.ai,https://chatgpt.com`) with `WWW-Authenticate` exposed via `Access-Control-Expose-Headers` so browsers can read the realm hint on 401
* Audit log writes to `log/mcp.jsonl` — every tool call with timestamp, JTI, scope, outcome, latency, `params_hash` (raw params are deliberately *not* logged)
* Tool registry (`utils/mcp_tool_registry.py`) maps all 40 tools to scopes (`read:market` / `read:account` / `write:orders`) and exposes FastMCP-generated JSON schemas in the `tools/list` response
* Pre-flight refusal: server refuses to boot with `MCP_HTTP_ENABLED=True` and `FLASK_DEBUG=True` together (debug tracebacks would leak bearer tokens)

**Install integration (Phase 4)** — `b36637b5e`, `90897a550`:

* `install/enable-remote-mcp.sh` — native Ubuntu enabler. Detects all `openalgo-*` systemd services, refuses if `FLASK_DEBUG=True`, backs up `.env`, sets the four MCP keys, runs `upgrade/migrate_all.py`, restarts the service, and probes the discovery / JWKS / `/mcp/healthz` endpoints to confirm boot.
* `install/enable-remote-mcp-docker.sh` — Docker enabler. Walks `/opt/openalgo/*/docker-compose.yaml`, picks a stack, backs up the bind-mounted `.env`, updates keys, restarts the container (whose `start.sh` runs migrations automatically), and runs the same smoke probe.
* `install/Remote-MCP-readme.md` — operator-focused install guide with same-domain Mode 1 (automated) and subdomain Mode 2 (manual nginx + certbot recipe), threat model, and disabling instructions.
* New `docs/userguide/remote-mcp.md` — end-user guide for connecting ChatGPT (Settings → Apps → New App BETA → Advanced OAuth → DCR) and Claude.ai (Settings → Connectors → + → Add custom connector).

**Admin operations (Phase 5)** — `8be942a7c`:

* `/admin/remote-mcp` React page with three tables (Pending / Approved / Revoked) + audit viewer + kill switch
* `GET /admin/api/oauth/clients` lists clients by status; approve/revoke endpoints require typed-string confirmation for destructive actions
* `GET /admin/api/mcp/audit` — paginated audit log over `log/mcp.jsonl` with whitelisted query keys
* `POST /admin/api/mcp/kill-switch` — typed-string confirm, atomically revokes every refresh token across every approved client (read-only access tokens still expire on their existing 15-minute TTL)

**Security audit fixes + ChatGPT compatibility** — `926a597bf`:

* Migrated from deprecated `authlib.jose` → `joserfc` with explicit `algorithms=["RS256"]` pinning
* `MCP_PUBLIC_URL` is a hard requirement when `MCP_HTTP_ENABLED=True` — collapsing it to empty would let JWTs minted on instance A be replayed against instance B's loopback
* `error_detail` in MCP responses replaced with generic *"Tool execution failed"* — the full detail is in the audit log only, so SQL errors / stack traces don't leak to the model
* Added path-relative discovery alias `/mcp/.well-known/oauth-protected-resource` because ChatGPT fetches it that way (RFC 9728 says root-relative is canonical, but ChatGPT does both)
* Default `MCP_RATE_LIMIT_WRITE` raised from 5/min → 50/min based on real ChatGPT/Claude usage patterns
* Removed the vestigial `MCP_OAUTH_LOGIN_AUTH_LEVEL` env var (replaced by the per-purpose 2FA flags)

**CSP and consent screen fixes** — `52c96a11a`, `5f60b4817`, `3175a4104`:

* Per-page CSP on the consent screen sets `form-action` to allow exactly the registered redirect\_uri's origin — fixes the form-submit block when the global CSP middleware would otherwise refuse the cross-origin POST
* Global CSP middleware (`csp.py`) now respects view-set CSP headers (won't overwrite if a header is already set)
* `tools/list` response now includes the real Pydantic-generated JSON schemas (reach into FastMCP's `_tool_manager._tools`) so ChatGPT stops hallucinating parameter names like `product_type` instead of `product`
* Removed Jinja-time `csrf_token()` dependency on the consent template — render the token via `_csrf_token_value()` helper inside the view so timing-of-globals isn't an issue

**Three new admin endpoints + four new database models:**

| New                     | Purpose                                                                                |
| ----------------------- | -------------------------------------------------------------------------------------- |
| `database/oauth_db.py`  | `OAuthClient`, `OAuthRefreshToken` (with `family_id` / `parent_id`), `OAuthSigningKey` |
| `database/user_db.py`   | 4 boolean columns + `find_user_by_exact_username()`                                    |
| `utils/oauth_keys.py`   | RS256 keypair generation + rotation + `public_jwks()`                                  |
| `utils/oauth_tokens.py` | `issue_access_token`, `rotate_refresh_token`, `verify_access_token`                    |

**Default security posture:**

| Setting                         | Default      | Why                                                   |
| ------------------------------- | ------------ | ----------------------------------------------------- |
| `MCP_HTTP_ENABLED`              | `False`      | Off until you opt in                                  |
| `MCP_OAUTH_REQUIRE_APPROVAL`    | `True`       | DCR clients land pending until admin approves         |
| `MCP_OAUTH_WRITE_SCOPE_ENABLED` | `False`      | Order placement unreachable until you flip the switch |
| `MCP_RATE_LIMIT_READ`           | `60/min`     | Per-token sliding window                              |
| `MCP_RATE_LIMIT_WRITE`          | `50/min`     | Per-token sliding window                              |
| `MCP_MAX_ORDER_QTY`             | `0` (no cap) | Recommend setting a sane cap                          |

***

**Symbol Search expansion (#1326)**

* `232c637fb` — `feat(search): lift 500 cap, allow exchange-only browse, add search history`
* `f0c03eede` — `feat(search): multi-exchange/instrumenttype, CSV download, copy formats`

The Symbol Search page now supports filtering across multiple exchanges and multiple instrument types in a single query, browsing an entire exchange without entering a search term, downloading the result set as CSV, and copying selections in AmiBroker / TradingView / Python / Excel formats. The previous 500-row hard cap is gone — large result sets stream incrementally. Per-user search history is preserved across sessions.

***

**Admin / Operations**

* `566113d49` — `feat(admin): add Diagnostics page with system info, error browser, and downloadable report`

`/admin/diagnostics` consolidates the moving parts of "what's going on with this install" into a single React page: Python / Flask / SQLAlchemy versions, database sizes for all six SQLite/DuckDB databases, broker session state, configured env vars (with secrets redacted), a paginated browser over `log/errors.jsonl` with stack traces and Flask request context, and a one-click **Download diagnostic bundle** that produces an env-redacted ZIP suitable for attaching to a support ticket.

***

**Brokers**

**Zerodha**

* `6bc37381e` — `feat(zerodha): support NCO and GLOBAL_INDEX exchanges`

Adds two Zerodha-only exchange codes:

* **NCO** — NSE Commodities (Zerodha's symbol format differs from NCDEX; this fix routes correctly through the master contract download)
* **GLOBAL\_INDEX** — US30, JAPAN225, HANGSENG, plus `GIFTNIFTY` from NSE IFSC. Quote-only by exchange convention; orders are not supported on GLOBAL\_INDEX.

***

**Documentation**

* `199c544d1` — `docs: rename "virtual/paper trading" to "sandbox trading" terminology`
* `6efaf1655` — `docs: rename remaining "virtual" trading terms to "sandbox" equivalents`
* `500e27cbb` — `docs: add Remote MCP user guide for ChatGPT and Claude.ai`
* New `install/Remote-MCP-readme.md` — operator-focused install + threat model
* New `docs/prd/remote-mcp.md` — full product requirements doc with architecture, MUST/SHOULD/COULD security controls

The "virtual / paper trading" rename only touched display strings — the database (`db/sandbox.db`), blueprint (`blueprints/sandbox.py`), and API endpoints (`/api/v1/sandbox/*`) were already named `sandbox`. The rename closes a long-standing inconsistency between in-product copy and the underlying schema.

***

**Configuration changes**

`.sample.env`:

* `ENV_CONFIG_VERSION` `1.0.6` → `1.0.7`
* New section: `MCP_HTTP_ENABLED`, `MCP_PUBLIC_URL`, `MCP_OAUTH_REQUIRE_APPROVAL`, `MCP_OAUTH_WRITE_SCOPE_ENABLED`, `MCP_HTTP_CORS_ORIGINS`, `MCP_HTTP_IP_ALLOWLIST`, `MCP_OAUTH_ACCESS_TTL`, `MCP_OAUTH_REFRESH_TTL`, `MCP_OAUTH_CODE_TTL`, `MCP_RATE_LIMIT_READ`, `MCP_RATE_LIMIT_WRITE`, `MCP_MAX_ORDER_QTY`
* Removed: `MCP_OAUTH_LOGIN_AUTH_LEVEL` (vestigial — replaced by per-purpose 2FA flags)

`pyproject.toml`:

* `version = "2.0.1.0"`
* SDK pin (`openalgo==1.0.49`) unchanged

`utils/version.py`:

* `VERSION = "2.0.1.0"`

***

**Upgrade procedure**

**For existing installs (Native Ubuntu):**

```bash
cd /var/python/openalgo-flask/<deploy-name>/openalgo
sudo ./install/update.sh
# update.sh runs migrate_all.py — schema changes for the OAuth + 2FA columns
# are applied automatically. Remote MCP stays disabled by default.
```

**For existing installs (Docker):**

```bash
cd /opt/openalgo/<domain>
sudo docker compose pull
sudo docker compose up -d
# The container's start.sh runs migrate_all.py before gunicorn boots.
# Remote MCP stays disabled by default.
```

**To enable Remote MCP (after upgrading):**

```bash
# Native Ubuntu
sudo ./install/enable-remote-mcp.sh

# Docker
sudo ./install/enable-remote-mcp-docker.sh
```

Both enabler scripts are idempotent and back up `.env` before any change. They print a one-liner rollback command if the smoke probe fails.

**For local developers (uv):**

```bash
git pull origin main
uv sync
cd frontend && npm install && npm run build
uv run app.py
```

***

**Contributors**

* **@marketcalls (Rajandran)** — release management, Remote MCP architecture and full OAuth 2.1 implementation (5 phases — scaffold → OAuth foundation → 2FA wiring → discovery + JWKS + DCR → /authorize + /token + /revoke → Phase 2 UI → HTTP/SSE transport → install integration → admin UI), security audit + joserfc migration, CSP fixes for the consent flow, ChatGPT-compatibility hardening (path-relative discovery, real tool schemas, generic error\_detail), Docker enabler, install integration, comprehensive PRD + operator docs + end-user docs, Symbol Search rewrite (#1326), Admin Diagnostics page, Zerodha NCO + GLOBAL\_INDEX support, and the "virtual → sandbox" terminology cleanup.

***

**Links**

* **Repository**: <https://github.com/marketcalls/openalgo>
* **Documentation**: <https://docs.openalgo.in>
* **Remote MCP guide**: <https://docs.openalgo.in/mcp/remote-mcp>
* **Discord**: <https://www.openalgo.in/discord>
* **YouTube**: <https://www.youtube.com/@openalgo>
* **Issue tracker**: <https://github.com/marketcalls/openalgo/issues>

***
