<p align="center">
  <img src="https://raw.githubusercontent.com/longportapp/longport-mcp/main/docs/logo.png" alt="LongPort" width="120" height="120">
</p>

<h1 align="center">LongPort MCP Server</h1>

<p align="center">
  <a href="https://registry.modelcontextprotocol.io/v0/servers/com.longportapp%2Fmcp/versions"><img alt="Official MCP Registry" src="https://img.shields.io/badge/MCP%20Registry-com.longportapp%2Fmcp-0a66c2"></a>
  <a href="https://smithery.ai/servers/longport-official/longport-mcp"><img alt="Smithery" src="https://smithery.ai/badge/longport-official/longport-mcp"></a>
  <a href="https://lobehub.com/mcp/longport-longport-mcp"><img alt="LobeHub" src="https://lobehub.com/badge/mcp/longport-longport-mcp"></a>
  <a href="https://glama.ai/mcp/servers/longport/longport-mcp"><img alt="longport-mcp MCP server" src="https://glama.ai/mcp/servers/longport/longport-mcp/badges/score.svg"></a>
  <a href="https://github.com/longportapp/longport-mcp/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/license-MIT-blue"></a>
  <a href="https://longportapp.com"><img alt="LongPort" src="https://img.shields.io/badge/brokerage-LongPort-ffe000?labelColor=000"></a>
</p>

Official MCP server for the [LongPort](https://longportapp.com) brokerage. **145 tools** across real-time quotes, options, order routing, fundamentals, analyst ratings, calendars, IPO, price alerts, DCA plans, portfolio analytics and community sharelists — covering **US and HK markets**. Built with Rust using [rmcp](https://github.com/anthropics/rmcp) and [axum](https://github.com/tokio-rs/axum).

## Features

- **145 MCP tools** across 13 categories: quotes, trading, fundamentals, screener, market data, calendars, IPO, portfolio, alerts, content, account statements, DCA, and community sharelists
- **Stateless architecture** -- each request carries a Bearer token forwarded directly to the LongPort SDK; no server-side sessions or database
- **OAuth 2.1 resource metadata** compliant with RFC 9728, pointing clients to LongPort OAuth for authorization
- **JSON response transformation** -- field names normalized to snake_case, timestamps converted to RFC 3339, internal counter_id values mapped to human-readable symbols
- **Prometheus metrics** for monitoring tool calls, latency, and errors
- **Configurable** via CLI arguments or a JSON config file (CLI takes precedence)

## Connect from an MCP client

LongPort operates a hosted endpoint at `https://mcp.longportapp.com`, so most users don't need to run their own server — just point your MCP client at it and complete OAuth when prompted. Authorization is auto-discovered via RFC 9728.

### Claude Desktop

Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or the equivalent on your OS:

```json
{
  "mcpServers": {
    "longport": {
      "url": "https://mcp.longportapp.com"
    }
  }
}
```

Restart Claude Desktop. On first tool invocation it will open a browser to complete the LongPort OAuth flow.

### Claude Code

```bash
claude mcp add --transport http longport https://mcp.longportapp.com
```

### Zed

Add to your Zed `settings.json` (open with `zed: open settings`):

```json
{
  "context_servers": {
    "longport": {
      "url": "https://mcp.longportapp.com"
    }
  }
}
```

On first use, Zed will open a browser to complete the LongPort OAuth flow.

### Cursor / Cline / Windsurf / other MCP clients

Point the client at `https://mcp.longportapp.com` using transport `streamable-http`. OAuth is auto-discovered via RFC 9728; no manual token required.

---

## Self-hosting

Prefer running your own instance? Use Docker or build from source.

### Docker (recommended)

```bash
docker run -p 8443:8443 \
  -v /path/to/certs:/certs:ro \
  ghcr.io/longportapp/longport-mcp \
  --bind 0.0.0.0:8443 \
  --base-url https://mcp.example.com \
  --tls-cert /certs/cert.pem \
  --tls-key /certs/key.pem
```

> **Important:** When deploying to a public network, you **must** set `--base-url` to the externally reachable URL of your server (e.g. `https://mcp.example.com`). This URL is returned in the OAuth protected resource metadata and used by MCP clients to discover the authorization server. If not set, it defaults to `http://localhost:{port}` which will not work for remote clients.

### Build from source

```bash
cargo build --release
./target/release/longport-mcp
```

### Configure

Create a config file at `~/.longport/mcp/config.json` (optional):

```json
{
  "bind": "127.0.0.1:8000",
  "base_url": "https://mcp.example.com",
  "log_dir": "/var/log/longport-mcp"
}
```

## Configuration

| Option | Config Key | CLI Flag | Default | Description |
|--------|-----------|----------|---------|-------------|
| Bind address | `bind` | `--bind` | `127.0.0.1:8000` | HTTP server listen address |
| Base URL | `base_url` | `--base-url` | auto | Public base URL for resource metadata |
| Log directory | `log_dir` | `--log-dir` | *(stderr)* | Directory for rolling log files |
| TLS certificate | `tls_cert` | `--tls-cert` | *(none)* | PEM certificate file for HTTPS |
| TLS private key | `tls_key` | `--tls-key` | *(none)* | PEM private key file for HTTPS |

CLI arguments override config file values. The config file is read from `~/.longport/mcp/config.json` (override with `LONGPORT_MCP_CONFIG_DIR`).

When `tls_cert` and `tls_key` are both set, the server runs HTTPS. Otherwise it falls back to HTTP. The `base_url` defaults to `https://localhost:{port}` with TLS or `http://localhost:{port}` without.

### Environment Variables

These are **advanced settings** — most users do not need to change them. They are primarily useful for connecting to non-production LongPort environments or debugging SDK internals.

| Variable | Default | Description |
|----------|---------|-------------|
| `LONGPORT_MCP_CONFIG_DIR` | `~/.longport/mcp` | Config file directory |
| `LONGPORT_HTTP_URL` | `https://openapi.longportapp.com` | LongPort API base URL (also used for OAuth metadata) |
| `LONGPORT_QUOTE_WS_URL` | `wss://openapi-quote.longportapp.com/v2` | Quote WebSocket endpoint |
| `LONGPORT_TRADE_WS_URL` | `wss://openapi-trade.longportapp.com/v2` | Trade WebSocket endpoint |
| `LONGPORT_MCP_LOG_PAYLOADS` | *(unset)* | `1` lifts the payload log caps (see [Logging and customer data](#logging-and-customer-data)). Never set this in production |
| `LONGPORT_LOG_PATH` | *(none)* | SDK internal log path. **Leave unset in production** — the SDK writes unfiltered request/response bodies there (see [Logging and customer data](#logging-and-customer-data)) |

## Logging and customer data

MCP requests and responses carry customer data — cash balances, positions, order history — and the upstream SDK frames carry access tokens. None of it belongs in a log file, so the server enforces a cap on the log targets that would print it, independent of `RUST_LOG`:

| Target | Cap | What it would otherwise print |
|--------|-----|-------------------------------|
| SDK HTTP client | `warn` | OpenAPI request and full response bodies (INFO) |
| SDK WebSocket client | `warn` | Every WebSocket frame, auth token included (INFO) |
| SDK trade module | `warn` | Order push events (INFO) |
| `rmcp` | `info` | Decoded MCP requests and full tool results (DEBUG), raw JSON-RPC frames (TRACE) |

The exact target names are listed in `src/logging.rs`; they carry the SDK's upstream crate names rather than `longport_*`.

Raising verbosity is therefore safe: `RUST_LOG=debug` (or even `trace`) gives you this server's own logs without turning customer data into log lines. Two things do defeat it, both off by default:

- `LONGPORT_MCP_LOG_PAYLOADS=1` removes the caps. Use it only against a test account on a local machine.
- `LONGPORT_LOG_PATH` makes the SDK install a private subscriber that writes its own INFO events — request and response bodies included — into that directory, where this server's filter does not apply. The server logs a warning at startup when it is set.

## Authentication

The server expects a LongPort OAuth access token in the `Authorization: Bearer <token>` header. On missing or invalid auth, it returns 401 with a `WWW-Authenticate` header pointing to the protected resource metadata endpoint, which in turn directs MCP clients to the LongPort OAuth authorization server.

## Claude Code integration

The one-liner in [Connect → Claude Code](#claude-code) gets you connected. Below are the extra commands you'll reach for while developing against this server.

```bash
# Hosted — use this unless you have a reason not to
claude mcp add --transport http longport https://mcp.longportapp.com

# Local self-hosted instance (see Self-hosting above)
claude mcp add --transport http longport-local http://localhost:8000/mcp

# Inspect
claude mcp list                         # registered servers
claude mcp get longport               # config + auth status of one server
claude mcp remove longport            # unregister

# Re-trigger OAuth (e.g. after token revocation on the LongPort side)
claude mcp logout longport
```

On the first tool invocation, Claude Code reads the `WWW-Authenticate` challenge from the server, fetches `/.well-known/oauth-protected-resource` (RFC 9728), and opens your browser for the LongPort OAuth flow. Access tokens are cached per-session and refreshed automatically.

## API Endpoints

| Method | Path | Description |
|--------|------|-------------|
| GET | `/.well-known/oauth-protected-resource` | Protected Resource Metadata (RFC 9728) |
| GET | `/metrics` | Prometheus metrics |
| POST/GET/DELETE | `/mcp` | MCP Streamable HTTP endpoint (requires Bearer token) |

## Tool Categories

| Category | Count | Description |
|----------|-------|-------------|
| **Quote** | 32 | Real-time and historical quotes, candlesticks, depth, brokers, options, warrants, watchlists, capital flow, market temperature, short positions, option volume |
| **Trade** | 14 | Order submission/cancellation/replacement, positions, balance, executions, cash flow, margin |
| **Fundamental** | 19 | Financial statements, business segments, institutional views, industry peers, earnings snapshot, dividends, EPS forecasts, valuations, company info, shareholders, corporate actions |
| **Market** | 10 | Market status, industry rank, broker holdings, A/H premium, trade statistics, anomalies, index constituents |
| **IPO** | 8 | IPO subscriptions, calendar, listed stocks, order detail, profit/loss analysis |
| **Content** | 8 | News, discussion topics, filing details |
| **DCA** | 9 | Dollar-cost averaging plan create/update/pause/resume/stop, execution history, statistics, and support check |
| **Sharelist** | 8 | Community sharelist CRUD, member add/remove/sort, popular lists |
| **Alert** | 5 | Price alert CRUD (add, delete, enable, disable, list) |
| **ATM** | 3 | Bank cards, withdrawal records, deposit records |
| **Portfolio** | 3 | Exchange rates, profit/loss analysis with optional date range |
| **Search** | 2 | News search, community topic search |
| **Statement** | 2 | Account statement listing and export |
| **Calendar** | 1 | Finance calendar events (earnings, dividends, IPOs, macro data, market closures) |
| **Utility** | 1 | Current UTC time |

## Prometheus Metrics

| Metric | Type | Description |
|--------|------|-------------|
| `mcp_tool_calls_total` | Counter | Total tool invocations (label: `tool_name`) |
| `mcp_tool_call_duration_seconds` | Histogram | Tool call latency (label: `tool_name`) |
| `mcp_tool_call_errors_total` | Counter | Tool call error count (label: `tool_name`) |

## Project Structure

```
src/
  main.rs              CLI args, config loading, axum server setup
  auth/
    mod.rs             Router composition, MCP service wiring
    metadata.rs        Protected Resource Metadata (RFC 9728)
    middleware.rs       Bearer token extraction middleware
  tools/
    mod.rs             MCP tool definitions and ServerHandler impl
    quote.rs           Quote tools (SDK QuoteContext)
    trade.rs           Trade tools (SDK TradeContext)
    fundamental.rs     Fundamental data, business segments, industry peers (HTTP API)
    market.rs          Market data, industry rank, broker holdings, anomalies (HTTP API)
    ipo.rs             IPO subscriptions, calendar, orders, profit/loss (HTTP API)
    search.rs          News and topic search (HTTP API)
    atm.rs             Bank cards, withdrawals, deposits (HTTP API)
    calendar.rs        Finance calendar (HTTP API)
    portfolio.rs       Portfolio analytics (HTTP API)
    dca.rs             Dollar-cost averaging / recurring investment (HTTP API)
    sharelist.rs       Community sharelist management (HTTP API)
    alert.rs           Price alerts (HTTP API)
    content.rs         News, topics, filings (SDK ContentContext + HTTP)
    statement.rs       Account statements (HTTP API)
    http_client.rs     Shared HTTP client helpers
    parse.rs           Parameter parsing helpers
  serialize/           JSON transformation (snake_case, timestamps, counter_id -> symbol)
  counter.rs           Symbol <-> counter_id bidirectional conversion (ST/ETF/IX/BK)
  metrics.rs           Prometheus metric definitions and /metrics handler
  error.rs             Unified error type (thiserror)
```

## Development

```bash
# Format
cargo +nightly fmt

# Lint
cargo clippy

# Test
cargo test
```

## License

See [LICENSE](LICENSE) for details.
