Skip to content
dsh.fish
Bundle

@yangzhe1991/dsh-futu-mcp

DSH plugin: connect to Futu (富途) MCP via OAuth 2.1 and expose its tools to the agent; tokens persisted outside the workspace

Source
yangzhe1991
License
MIT
Updated
Updated 13 days ago

Readme

# @yangzhe1991/dsh-futu-mcp

[English](README.md) | [中文](README.zh.md)

[![npm version](https://img.shields.io/npm/v/@yangzhe1991/dsh-futu-mcp)](https://www.npmjs.com/package/@yangzhe1991/dsh-futu-mcp)
[![npm downloads](https://img.shields.io/npm/dt/@yangzhe1991/dsh-futu-mcp)](https://www.npmjs.com/package/@yangzhe1991/dsh-futu-mcp)
[![license](https://img.shields.io/npm/l/@yangzhe1991/dsh-futu-mcp)](LICENSE)
[![dsh-plugin](https://img.shields.io/badge/dsh-plugin-blue)](https://github.com/deepseek-ai/deepseek-harness)

A DSH (DeepSeek Harness) plugin that connects the agent to the official [Futu (富途) MCP server](https://open.futunn.com/zh-cn/mcp-docs/overview) and registers its tools on `ctx.tools` under `mcp__futu__<toolName>`.

## What it does

- Connects to `https://mcp.futunn.com/mcp` using the [pagination-aware MCP Streamable HTTP transport](https://open.futunn.com/zh-cn/mcp-docs/quick-start) and publishes server tools to the agent as callable tools (`mcp__futu__*`).
- Handles Futu's **OAuth 2.1** authorization completely in-process:
  - RFC 9728 / RFC 8414 discovery, dynamic client registration (RFC 7591), PKCE S256, refresh-token rotation.
  - On first use your browser opens the Futu authorization page; after you authorize, tokens are saved and reused silently afterwards (automatic refresh; a new browser prompt appears only when refresh fails).
- **Scopes are strictly capped to what you configure** (default: read-only `quote:read` + `trade:read`). The plugin intercepts the server's protected-resource metadata (which advertises every scope including `trade:write` order placement) and narrows it to your configured set, so neither client registration nor the authorization request can ever request more than you approved.
- **Deferred authorization — zero Futu traffic until you actually use it**: plugin load only registers the previously synced tool list from the credentials cache (so the agent can see `mcp__futu__*` tools) with **no network, no token refresh, no browser popup**. A conversation that never touches Futu costs nothing. The moment a Futu tool is actually called, the plugin connects; if the token is stale it refreshes silently (only when the refresh token itself is expired/revoked — a 14-day cycle — does the browser login page open, and the same call then retries and succeeds). The one exception: on very first use there is no cached tool list yet, so the first sent message triggers the connect + login, after which the cache exists and everything is call-time-only.

## Behavior

| When | What happens |
|---|---|
| `dsh web` starts / plugin loads | Only reads the credentials file: cached tool list is registered (agent sees `mcp__futu__*`). **No network, no token refresh, no browser.** |
| You chat about anything unrelated | Nothing (zero Futu activity). Tool calls never reach Futu. |
| A Futu tool is actually called | Connect + sync happens now; stale token → silent refresh (no UI). |
| Refresh token expired/revoked (≤14 days) and a Futu tool is called | Login page opens at that moment; after you authorize, the same call retries and succeeds. |
| Very first use (no cached tool list) | Your first sent message triggers connect + login; after authorizing, the tool list is cached for all future starts. |

## Token storage (secure, project-independent)

OAuth tokens, the registered `client_id` and the (non-sensitive) discovery cache are persisted to a credentials file **outside any workspace or project directory**:

- Default: `~/.dsh/credentials/futu-mcp.json` (i.e. `$DSH_HOME/credentials/`), file mode `0600`, directory mode `0700`.
- Override with `credentialFile` in the plugin config (the path is resolved with `~` expansion; keep it inside your home directory, never inside a project).

The access token is short-lived (~2h) and refreshed automatically; the refresh token lives up to 14 days, after which the browser prompt appears again. You can revoke tokens at any time from the [Futu OpenAPI Dashboard](https://open.futunn.com/).

## Install

```bash
dsh plugin --profile web add @yangzhe1991/dsh-futu-mcp
```

Then restart `dsh web`. On the first quote query the browser will open the Futu authorization page — log in and approve the requested scopes. Tip from the official docs: try asking the agent "查一下腾讯的实时股价" to verify.

## Config

Override configuration in your profile's `cordis.patch.yml`:

```yaml
- id: futu-mcp
  config:
    # 授权范围:只读 = quote:read + trade:read(默认)。
    # 需要自选管理/下单撤单时追加 quote:write / trade:write 并重新授权。
    scopes: [quote:read, trade:read]
    # 凭证文件(默认 ~/.dsh/credentials/futu-mcp.json,0600)。
    credentialFile: ~/.dsh/credentials/futu-mcp.json
    # 单次工具调用超时(ms)
    toolCallTimeoutMs: 60000
    # 授权页打开后的提醒间隔(ms)
    authTimeoutMs: 300000
    # 本地回调服务器端口(0 = 自动)
    callbackServerPort: 0
    reconnect:
      enabled: true
      initialDelayMs: 500
      maxDelayMs: 30000
      maxAttempts: 10
```

## Local development

Dev mode (link) — edit your profile's `package.json`:

```json
"dependencies": {
  "@yangzhe1991/dsh-futu-mcp": "link:/path/to/dsh-futu-mcp"
}
```

```bash
pnpm install   # in the profile directory
npm run build  # in the plugin directory, then hard-refresh / restart dsh web
```

`dsh.profile.bundles` must include `@yangzhe1991/dsh-futu-mcp` (run `dsh plugin add` to reconcile, or add it manually the same way as other plugins).

## License

MIT

Install

dsh plugin --profile web add github:yangzhe1991/dsh-futu-mcp

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source